Compare commits
106 Commits
graingert-
...
7.1.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4645bcd449 | ||
|
|
fadfb4f346 | ||
|
|
ab96ea88e8 | ||
|
|
fc0e024b11 | ||
|
|
8f5088f412 | ||
|
|
aae93d6127 | ||
|
|
71b79fcda5 | ||
|
|
89f7518cb1 | ||
|
|
88fc45bd57 | ||
|
|
d0b53d6ba7 | ||
|
|
9b2c6cedc7 | ||
|
|
13fd967f54 | ||
|
|
f5cdd18b6a | ||
|
|
d7f27c4687 | ||
|
|
7133c546f8 | ||
|
|
6f8c4fa742 | ||
|
|
121b69276a | ||
|
|
b9d00fba13 | ||
|
|
da7daaac92 | ||
|
|
0b65f0bb6f | ||
|
|
9b3affc006 | ||
|
|
ffb9d2bcea | ||
|
|
55189ebda6 | ||
|
|
44bdd94c9e | ||
|
|
199367168b | ||
|
|
1eb8434529 | ||
|
|
3a975e8569 | ||
|
|
549e3136cc | ||
|
|
ec055cf772 | ||
|
|
550a42d717 | ||
|
|
9c6cf93717 | ||
|
|
9a16e69835 | ||
|
|
bf765cc3c8 | ||
|
|
0e7a6a769e | ||
|
|
8ae6237b3e | ||
|
|
7127b56a7d | ||
|
|
b8ce513e95 | ||
|
|
315695f53f | ||
|
|
a1027af168 | ||
|
|
9c3bc106dd | ||
|
|
c19571f30d | ||
|
|
a749e08c42 | ||
|
|
d89c13d670 | ||
|
|
be9f8b1d05 | ||
|
|
afa3bbd0e9 | ||
|
|
dc0812610d | ||
|
|
c2aa80132a | ||
|
|
55e7a71446 | ||
|
|
2b0899a4d1 | ||
|
|
eb55d4c945 | ||
|
|
4f9863abf8 | ||
|
|
f70e370e35 | ||
|
|
835de75819 | ||
|
|
68f6121882 | ||
|
|
31759f7af4 | ||
|
|
796574f3b8 | ||
|
|
07ea99c5a0 | ||
|
|
5f18e9d1ab | ||
|
|
643cc79759 | ||
|
|
ce4a11a364 | ||
|
|
f7e57ae839 | ||
|
|
d3a27deb92 | ||
|
|
cb5fd75bea | ||
|
|
440db691ec | ||
|
|
dc36836dd2 | ||
|
|
85c83cd37d | ||
|
|
91d84e9256 | ||
|
|
ab014a1a0d | ||
|
|
0379346675 | ||
|
|
2f8ae29c17 | ||
|
|
a7d7676f7b | ||
|
|
df9df55749 | ||
|
|
2f2f1a601e | ||
|
|
5c04f3a1a2 | ||
|
|
078733c005 | ||
|
|
3a7ead6bcf | ||
|
|
6d75333780 | ||
|
|
ddbb998aed | ||
|
|
0ec5886ad5 | ||
|
|
f2469fca37 | ||
|
|
94ec0f8ad8 | ||
|
|
5ef96fdb53 | ||
|
|
7a501fb313 | ||
|
|
1769c66def | ||
|
|
840c418de6 | ||
|
|
6461e2e385 | ||
|
|
b55b7f1ad4 | ||
|
|
d9794ed3cf | ||
|
|
8b33683cbf | ||
|
|
1d2e50faa6 | ||
|
|
6820ab2bd4 | ||
|
|
78356dc353 | ||
|
|
f1c27608ec | ||
|
|
0ceaa57d9d | ||
|
|
93fad3286b | ||
|
|
b9462ed7d0 | ||
|
|
0ffe9e0742 | ||
|
|
6f2c1ec035 | ||
|
|
a65c47a1a4 | ||
|
|
30d995ed25 | ||
|
|
10a14d1318 | ||
|
|
f4cfc596c6 | ||
|
|
f1df8074b3 | ||
|
|
7d4d1ecde6 | ||
|
|
1dbffcc0b4 | ||
|
|
d53a5fb371 |
56
.github/workflows/deploy.yml
vendored
Normal file
56
.github/workflows/deploy.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
# These tags are protected, see:
|
||||
# https://github.com/pytest-dev/pytest/settings/tag_protection
|
||||
- "[0-9]+.[0-9]+.[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||
|
||||
|
||||
# Set permissions at the job level.
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
|
||||
deploy:
|
||||
if: github.repository == 'pytest-dev/pytest'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade build tox
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_token }}
|
||||
|
||||
- name: Publish GitHub release notes
|
||||
env:
|
||||
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
sudo apt-get install pandoc
|
||||
tox -e publish-gh-release-notes
|
||||
@@ -1,4 +1,4 @@
|
||||
name: main
|
||||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -187,46 +187,3 @@ jobs:
|
||||
fail_ci_if_error: true
|
||||
files: ./coverage.xml
|
||||
verbose: true
|
||||
|
||||
deploy:
|
||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
needs: [build]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade build tox
|
||||
|
||||
- name: Build package
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@master
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.pypi_token }}
|
||||
|
||||
- name: Publish GitHub release notes
|
||||
env:
|
||||
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
sudo apt-get install pandoc
|
||||
tox -e publish-gh-release-notes
|
||||
@@ -1,6 +1,6 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.1.0
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
@@ -37,7 +37,7 @@ repos:
|
||||
- flake8-typing-imports==1.12.0
|
||||
- flake8-docstrings==1.5.0
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v2.7.1
|
||||
rev: v3.0.1
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src', --py37-plus]
|
||||
@@ -67,7 +67,6 @@ repos:
|
||||
- attrs>=19.2.0
|
||||
- packaging
|
||||
- tomli
|
||||
- types-atomicwrites
|
||||
- types-pkg_resources
|
||||
- repo: local
|
||||
hooks:
|
||||
@@ -101,7 +100,7 @@ repos:
|
||||
types: [python]
|
||||
- id: py-path-deprecated
|
||||
name: py.path usage is deprecated
|
||||
exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py
|
||||
exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py|src/_pytest/legacypath.py
|
||||
language: pygrep
|
||||
entry: \bpy\.path\.local
|
||||
types: [python]
|
||||
|
||||
12
AUTHORS
12
AUTHORS
@@ -15,6 +15,7 @@ Alan Velasco
|
||||
Alexander Johnson
|
||||
Alexander King
|
||||
Alexei Kozlenok
|
||||
Alice Purcell
|
||||
Allan Feldman
|
||||
Aly Sivji
|
||||
Amir Elkess
|
||||
@@ -83,6 +84,7 @@ Damian Skrzypczak
|
||||
Daniel Grana
|
||||
Daniel Hahler
|
||||
Daniel Nuri
|
||||
Daniel Sánchez Castelló
|
||||
Daniel Wandschneider
|
||||
Daniele Procida
|
||||
Danielle Jenkins
|
||||
@@ -184,7 +186,9 @@ Katarzyna Król
|
||||
Katerina Koukiou
|
||||
Keri Volans
|
||||
Kevin Cox
|
||||
Kevin Hierro Carrasco
|
||||
Kevin J. Foley
|
||||
Kian Eliasi
|
||||
Kian-Meng Ang
|
||||
Kodi B. Arfer
|
||||
Kojo Idrissa
|
||||
@@ -245,6 +249,7 @@ Nicholas Murphy
|
||||
Niclas Olofsson
|
||||
Nicolas Delaby
|
||||
Nikolay Kondratyev
|
||||
Nipunn Koorapati
|
||||
Olga Matoula
|
||||
Oleg Pidsadnyi
|
||||
Oleg Sushchenko
|
||||
@@ -255,6 +260,8 @@ Ondřej Súkup
|
||||
Oscar Benjamin
|
||||
Parth Patel
|
||||
Patrick Hayes
|
||||
Paul Müller
|
||||
Paul Reece
|
||||
Pauli Virtanen
|
||||
Pavel Karateev
|
||||
Paweł Adamczak
|
||||
@@ -300,6 +307,7 @@ Seth Junot
|
||||
Shantanu Jain
|
||||
Shubham Adep
|
||||
Simon Gomizelj
|
||||
Simon Holesch
|
||||
Simon Kerr
|
||||
Skylar Downes
|
||||
Srinivas Reddy Thatiparthy
|
||||
@@ -317,6 +325,7 @@ Taneli Hukkinen
|
||||
Tanvi Mehta
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
Tatiana Ovary
|
||||
Ted Xiao
|
||||
Terje Runde
|
||||
Thomas Grainger
|
||||
@@ -327,12 +336,14 @@ Tom Dalton
|
||||
Tom Viner
|
||||
Tomáš Gavenčiak
|
||||
Tomer Keren
|
||||
Tony Narlock
|
||||
Tor Colvin
|
||||
Trevor Bekolay
|
||||
Tyler Goodlet
|
||||
Tzu-ping Chung
|
||||
Vasily Kuznetsov
|
||||
Victor Maryama
|
||||
Victor Rodriguez
|
||||
Victor Uriarte
|
||||
Vidar T. Fauske
|
||||
Virgil Dupras
|
||||
@@ -353,5 +364,6 @@ Yoav Caspi
|
||||
Yuval Shimon
|
||||
Zac Hatfield-Dodds
|
||||
Zachary Kneupper
|
||||
Zachary OBrien
|
||||
Zoltán Máté
|
||||
Zsolt Cserna
|
||||
|
||||
@@ -50,6 +50,8 @@ Fix bugs
|
||||
--------
|
||||
|
||||
Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
|
||||
See also the `"status: easy" issues <https://github.com/pytest-dev/pytest/labels/status%3A%20easy>`_
|
||||
that are friendly to new contributors.
|
||||
|
||||
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going
|
||||
to work on a particular issue, add a comment to that effect on the specific issue.
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
:target: https://codecov.io/gh/pytest-dev/pytest
|
||||
:alt: Code coverage Status
|
||||
|
||||
.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg
|
||||
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain
|
||||
.. image:: https://github.com/pytest-dev/pytest/workflows/test/badge.svg
|
||||
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest
|
||||
|
||||
.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg
|
||||
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
Fixed test output for some data types where ``-v`` would show less information.
|
||||
|
||||
Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff.
|
||||
@@ -1,3 +0,0 @@
|
||||
The deprecation of raising :class:`unittest.SkipTest` to skip collection of
|
||||
tests during the pytest collection phase is reverted - this is now a supported
|
||||
feature again.
|
||||
@@ -1,15 +0,0 @@
|
||||
As per our policy, the following features have been deprecated in the 6.X series and are now
|
||||
removed:
|
||||
|
||||
* ``pytest._fillfuncargs`` function.
|
||||
|
||||
* ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead.
|
||||
|
||||
* ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead.
|
||||
|
||||
* ``-k foobar:`` syntax.
|
||||
|
||||
* ``pytest.collect`` module - import from ``pytest`` directly.
|
||||
|
||||
For more information consult
|
||||
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
||||
@@ -1 +0,0 @@
|
||||
pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``.
|
||||
@@ -1 +0,0 @@
|
||||
Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
|
||||
@@ -1,10 +0,0 @@
|
||||
Symbolic link components are no longer resolved in conftest paths.
|
||||
This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice.
|
||||
For example, given
|
||||
|
||||
tests/real/conftest.py
|
||||
tests/real/test_it.py
|
||||
tests/link -> tests/real
|
||||
|
||||
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
||||
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
|
||||
@@ -1 +0,0 @@
|
||||
When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width.
|
||||
@@ -1,3 +0,0 @@
|
||||
Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
|
||||
|
||||
If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count.
|
||||
@@ -1,4 +0,0 @@
|
||||
More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
|
||||
be obtained by enabling :mod:`tracemalloc`.
|
||||
|
||||
See :ref:`resource-warnings` for more information.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
|
||||
@@ -1,3 +0,0 @@
|
||||
More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``.
|
||||
Previously only `str`, `float`, `int` and `bool` were accepted;
|
||||
now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted.
|
||||
@@ -1,3 +0,0 @@
|
||||
:func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`).
|
||||
|
||||
Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order.
|
||||
@@ -1 +0,0 @@
|
||||
:fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables.
|
||||
@@ -1 +0,0 @@
|
||||
Malformed ``pyproject.toml`` files now produce a clearer error message.
|
||||
@@ -17,7 +17,6 @@
|
||||
<li><a href="{{ pathto('changelog') }}">Changelog</a></li>
|
||||
<li><a href="{{ pathto('contributing') }}">Contributing</a></li>
|
||||
<li><a href="{{ pathto('backwards-compatibility') }}">Backwards Compatibility</a></li>
|
||||
<li><a href="{{ pathto('py27-py34-deprecation') }}">Python 2.7 and 3.4 Support</a></li>
|
||||
<li><a href="{{ pathto('sponsor') }}">Sponsor</a></li>
|
||||
<li><a href="{{ pathto('tidelift') }}">pytest for Enterprise</a></li>
|
||||
<li><a href="{{ pathto('license') }}">License</a></li>
|
||||
@@ -30,5 +29,3 @@
|
||||
{%- endif %}
|
||||
|
||||
<hr>
|
||||
<a href="{{ pathto('genindex') }}">Index</a>
|
||||
<hr>
|
||||
|
||||
@@ -6,6 +6,10 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-7.1.3
|
||||
release-7.1.2
|
||||
release-7.1.1
|
||||
release-7.1.0
|
||||
release-7.0.1
|
||||
release-7.0.0
|
||||
release-7.0.0rc1
|
||||
|
||||
48
doc/en/announce/release-7.1.0.rst
Normal file
48
doc/en/announce/release-7.1.0.rst
Normal file
@@ -0,0 +1,48 @@
|
||||
pytest-7.1.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 7.1.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:
|
||||
|
||||
* Akuli
|
||||
* Andrew Svetlov
|
||||
* Anthony Sottile
|
||||
* Brett Holman
|
||||
* Bruno Oliveira
|
||||
* Chris NeJame
|
||||
* Dan Alvizu
|
||||
* Elijah DeLee
|
||||
* Emmanuel Arias
|
||||
* Fabian Egli
|
||||
* Florian Bruhin
|
||||
* Gabor Szabo
|
||||
* Hasan Ramezani
|
||||
* Hugo van Kemenade
|
||||
* Kian Meng, Ang
|
||||
* Kojo Idrissa
|
||||
* Masaru Tsuchiyama
|
||||
* Olga Matoula
|
||||
* P. L. Lim
|
||||
* Ran Benita
|
||||
* Tobias Deiminger
|
||||
* Yuval Shimon
|
||||
* eduardo naufel schettino
|
||||
* Éric
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
18
doc/en/announce/release-7.1.1.rst
Normal file
18
doc/en/announce/release-7.1.1.rst
Normal file
@@ -0,0 +1,18 @@
|
||||
pytest-7.1.1
|
||||
=======================================
|
||||
|
||||
pytest 7.1.1 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:
|
||||
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
23
doc/en/announce/release-7.1.2.rst
Normal file
23
doc/en/announce/release-7.1.2.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
pytest-7.1.2
|
||||
=======================================
|
||||
|
||||
pytest 7.1.2 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
|
||||
* Hugo van Kemenade
|
||||
* Kian Eliasi
|
||||
* Ran Benita
|
||||
* Zac Hatfield-Dodds
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
28
doc/en/announce/release-7.1.3.rst
Normal file
28
doc/en/announce/release-7.1.3.rst
Normal file
@@ -0,0 +1,28 @@
|
||||
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
|
||||
@@ -77,3 +77,18 @@ Deprecation Roadmap
|
||||
Features currently deprecated and removed in previous releases can be found in :ref:`deprecations`.
|
||||
|
||||
We track future deprecation and removal of features using milestones and the `deprecation <https://github.com/pytest-dev/pytest/issues?q=label%3A%22type%3A+deprecation%22>`_ and `removal <https://github.com/pytest-dev/pytest/labels/type%3A%20removal>`_ labels on GitHub.
|
||||
|
||||
|
||||
Python version support
|
||||
======================
|
||||
|
||||
Released pytest versions support all Python versions that are actively maintained at the time of the release:
|
||||
|
||||
============== ===================
|
||||
pytest version min. Python version
|
||||
============== ===================
|
||||
7.1+ 3.7+
|
||||
6.2 - 7.0 3.6+
|
||||
5.0 - 6.1 3.5+
|
||||
3.3 - 4.6 2.7, 3.4+
|
||||
============== ===================
|
||||
|
||||
@@ -40,32 +40,86 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
calls, which return a ``(out, err)`` namedtuple.
|
||||
``out`` and ``err`` will be ``text`` objects.
|
||||
|
||||
capsysbinary -- .../_pytest/capture.py:895
|
||||
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:906
|
||||
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.
|
||||
|
||||
capfd -- .../_pytest/capture.py:912
|
||||
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:934
|
||||
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.
|
||||
|
||||
capfdbinary -- .../_pytest/capture.py:929
|
||||
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:962
|
||||
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.
|
||||
|
||||
doctest_namespace [session scope] -- .../_pytest/doctest.py:731
|
||||
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:735
|
||||
Fixture that returns a :py:class:`dict` that will be injected into the
|
||||
namespace of doctests.
|
||||
|
||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1365
|
||||
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:1344
|
||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||
object.
|
||||
|
||||
@@ -117,10 +171,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:295
|
||||
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302
|
||||
Return a :class:`pytest.TempdirFactory` instance for the test session.
|
||||
|
||||
tmpdir -- .../_pytest/legacypath.py:302
|
||||
tmpdir -- .../_pytest/legacypath.py:309
|
||||
Return a temporary directory path object which is unique to each test
|
||||
function invocation, created as a sub directory of the base temporary
|
||||
directory.
|
||||
@@ -132,9 +186,14 @@ 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:483
|
||||
caplog -- .../_pytest/logging.py:487
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following properties/methods::
|
||||
@@ -148,21 +207,26 @@ 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
|
||||
os.environ::
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
:data:`os.environ`:
|
||||
|
||||
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)
|
||||
* :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>`
|
||||
|
||||
All modifications will be undone after the requesting test function or
|
||||
fixture has finished. The ``raising`` parameter determines if a KeyError
|
||||
or AttributeError will be raised if the set/deletion operation has no target.
|
||||
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>`.
|
||||
|
||||
recwarn -- .../_pytest/recwarn.py:29
|
||||
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
||||
@@ -170,10 +234,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
See https://docs.python.org/library/how-to/capture-warnings.html for information
|
||||
on warning categories.
|
||||
|
||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:183
|
||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:184
|
||||
Return a :class:`pytest.TempPathFactory` instance for the test session.
|
||||
|
||||
tmp_path -- .../_pytest/tmpdir.py:198
|
||||
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.
|
||||
|
||||
@@ -28,6 +28,168 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
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)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#9726 <https://github.com/pytest-dev/pytest/issues/9726>`_: An unnecessary ``numpy`` import inside :func:`pytest.approx` was removed.
|
||||
|
||||
|
||||
- `#9820 <https://github.com/pytest-dev/pytest/issues/9820>`_: Fix comparison of ``dataclasses`` with ``InitVar``.
|
||||
|
||||
|
||||
- `#9869 <https://github.com/pytest-dev/pytest/issues/9869>`_: Increase ``stacklevel`` for the ``NODE_CTOR_FSPATH_ARG`` deprecation to point to the
|
||||
user's code, not pytest.
|
||||
|
||||
|
||||
- `#9871 <https://github.com/pytest-dev/pytest/issues/9871>`_: Fix a bizarre (and fortunately rare) bug where the `temp_path` fixture could raise
|
||||
an internal error while attempting to get the current user's username.
|
||||
|
||||
|
||||
pytest 7.1.1 (2022-03-17)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#9767 <https://github.com/pytest-dev/pytest/issues/9767>`_: Fixed a regression in pytest 7.1.0 where some conftest.py files outside of the source tree (e.g. in the `site-packages` directory) were not picked up.
|
||||
|
||||
|
||||
pytest 7.1.0 (2022-03-13)
|
||||
=========================
|
||||
|
||||
Breaking Changes
|
||||
----------------
|
||||
|
||||
- `#8838 <https://github.com/pytest-dev/pytest/issues/8838>`_: As per our policy, the following features have been deprecated in the 6.X series and are now
|
||||
removed:
|
||||
|
||||
* ``pytest._fillfuncargs`` function.
|
||||
|
||||
* ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead.
|
||||
|
||||
* ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead.
|
||||
|
||||
* ``-k foobar:`` syntax.
|
||||
|
||||
* ``pytest.collect`` module - import from ``pytest`` directly.
|
||||
|
||||
For more information consult
|
||||
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
||||
|
||||
|
||||
- `#9437 <https://github.com/pytest-dev/pytest/issues/9437>`_: Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
|
||||
|
||||
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#5192 <https://github.com/pytest-dev/pytest/issues/5192>`_: Fixed test output for some data types where ``-v`` would show less information.
|
||||
|
||||
Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff.
|
||||
|
||||
|
||||
- `#9362 <https://github.com/pytest-dev/pytest/issues/9362>`_: pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``.
|
||||
|
||||
|
||||
- `#9536 <https://github.com/pytest-dev/pytest/issues/9536>`_: When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width.
|
||||
|
||||
|
||||
- `#9644 <https://github.com/pytest-dev/pytest/issues/9644>`_: More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
|
||||
be obtained by enabling :mod:`tracemalloc`.
|
||||
|
||||
See :ref:`resource-warnings` for more information.
|
||||
|
||||
|
||||
- `#9678 <https://github.com/pytest-dev/pytest/issues/9678>`_: More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``.
|
||||
Previously only `str`, `float`, `int` and `bool` were accepted;
|
||||
now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted.
|
||||
|
||||
|
||||
- `#9692 <https://github.com/pytest-dev/pytest/issues/9692>`_: :func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`).
|
||||
|
||||
Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#8242 <https://github.com/pytest-dev/pytest/issues/8242>`_: The deprecation of raising :class:`unittest.SkipTest` to skip collection of
|
||||
tests during the pytest collection phase is reverted - this is now a supported
|
||||
feature again.
|
||||
|
||||
|
||||
- `#9493 <https://github.com/pytest-dev/pytest/issues/9493>`_: Symbolic link components are no longer resolved in conftest paths.
|
||||
This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice.
|
||||
For example, given
|
||||
|
||||
tests/real/conftest.py
|
||||
tests/real/test_it.py
|
||||
tests/link -> tests/real
|
||||
|
||||
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
||||
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
|
||||
|
||||
|
||||
- `#9626 <https://github.com/pytest-dev/pytest/issues/9626>`_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
|
||||
|
||||
If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count.
|
||||
|
||||
|
||||
- `#9645 <https://github.com/pytest-dev/pytest/issues/9645>`_: Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
|
||||
|
||||
|
||||
- `#9708 <https://github.com/pytest-dev/pytest/issues/9708>`_: :fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables.
|
||||
|
||||
|
||||
- `#9730 <https://github.com/pytest-dev/pytest/issues/9730>`_: Malformed ``pyproject.toml`` files now produce a clearer error message.
|
||||
|
||||
|
||||
pytest 7.0.1 (2022-02-11)
|
||||
=========================
|
||||
|
||||
@@ -2497,7 +2659,8 @@ Important
|
||||
|
||||
This release is a Python3.5+ only release.
|
||||
|
||||
For more details, see our :std:doc:`Python 2.7 and 3.4 support plan <py27-py34-deprecation>`.
|
||||
For more details, see our `Python 2.7 and 3.4 support plan
|
||||
<https://docs.pytest.org/en/7.0.x/py27-py34-deprecation.html>`_.
|
||||
|
||||
Removals
|
||||
--------
|
||||
@@ -2721,7 +2884,11 @@ Features
|
||||
|
||||
- :issue:`6870`: New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.
|
||||
|
||||
Remark: while this is technically a new feature and according to our :ref:`policy <what goes into 4.6.x releases>` it should not have been backported, we have opened an exception in this particular case because it fixes a serious interaction with ``pytest-xdist``, so it can also be considered a bugfix.
|
||||
Remark: while this is technically a new feature and according to our
|
||||
`policy <https://docs.pytest.org/en/7.0.x/py27-py34-deprecation.html#what-goes-into-4-6-x-releases>`_
|
||||
it should not have been backported, we have opened an exception in this
|
||||
particular case because it fixes a serious interaction with ``pytest-xdist``,
|
||||
so it can also be considered a bugfix.
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
@@ -2893,7 +3060,8 @@ Important
|
||||
|
||||
The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**.
|
||||
|
||||
For more details, see our :std:doc:`Python 2.7 and 3.4 support plan <py27-py34-deprecation>`.
|
||||
For more details, see our `Python 2.7 and 3.4 support plan
|
||||
<https://docs.pytest.org/en/7.0.x/py27-py34-deprecation.html>`_.
|
||||
|
||||
|
||||
Features
|
||||
|
||||
@@ -247,7 +247,7 @@ html_sidebars = {
|
||||
html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
html_use_index = True
|
||||
html_use_index = False
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
# html_split_index = False
|
||||
@@ -320,7 +320,9 @@ latex_domain_indices = False
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)]
|
||||
man_pages = [
|
||||
("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)
|
||||
]
|
||||
|
||||
|
||||
# -- Options for Epub output ---------------------------------------------------
|
||||
|
||||
@@ -85,7 +85,6 @@ Further topics
|
||||
|
||||
backwards-compatibility
|
||||
deprecations
|
||||
py27-py34-deprecation
|
||||
|
||||
contributing
|
||||
development_guide
|
||||
|
||||
@@ -657,20 +657,17 @@ Use :func:`pytest.raises` with the
|
||||
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
|
||||
in which some tests raise exceptions and others do not.
|
||||
|
||||
It is helpful to define a no-op context manager ``does_not_raise`` to serve
|
||||
as a complement to ``raises``. For example:
|
||||
It may be helpful to use ``nullcontext`` as a complement to ``raises``.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import contextmanager
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@contextmanager
|
||||
def does_not_raise():
|
||||
yield
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"example_input,expectation",
|
||||
[
|
||||
@@ -687,22 +684,3 @@ as a complement to ``raises``. For example:
|
||||
|
||||
In the example above, the first three test cases should run unexceptionally,
|
||||
while the fourth should raise ``ZeroDivisionError``.
|
||||
|
||||
If you're only supporting Python 3.7+, you can simply use ``nullcontext``
|
||||
to define ``does_not_raise``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
|
||||
Or, if you're supporting Python 3.3+ you can use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import ExitStack as does_not_raise
|
||||
|
||||
Or, if desired, you can ``pip install contextlib2`` and use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib2 import nullcontext as does_not_raise
|
||||
|
||||
@@ -155,7 +155,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
> assert [0, 1, 2] == [0, 1, 3]
|
||||
E assert [0, 1, 2] == [0, 1, 3]
|
||||
E At index 2 diff: 2 != 3
|
||||
E Use -v to get the full diff
|
||||
E Use -v to get more diff
|
||||
|
||||
failure_demo.py:63: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
@@ -168,7 +168,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
> assert a == b
|
||||
E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
|
||||
E At index 100 diff: 1 != 2
|
||||
E Use -v to get the full diff
|
||||
E Use -v to get more diff
|
||||
|
||||
failure_demo.py:68: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
@@ -215,7 +215,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
> assert [1, 2] == [1, 2, 3]
|
||||
E assert [1, 2] == [1, 2, 3]
|
||||
E Right contains one more item: 3
|
||||
E Use -v to get the full diff
|
||||
E Use -v to get more diff
|
||||
|
||||
failure_demo.py:77: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
@@ -173,10 +173,9 @@ This layout prevents a lot of common pitfalls and has many benefits, which are b
|
||||
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
|
||||
|
||||
.. note::
|
||||
The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't have
|
||||
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 and report if the new option works well for them.
|
||||
test modules, so users that run into this issue are strongly encouraged to try it.
|
||||
|
||||
The ``src`` directory layout is still strongly recommended however.
|
||||
|
||||
|
||||
@@ -45,10 +45,19 @@ these values:
|
||||
|
||||
* ``importlib``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`.
|
||||
|
||||
For this reason this doesn't require test module names to be unique, but also makes test
|
||||
modules non-importable by each other.
|
||||
For this reason this doesn't require test module names to be unique.
|
||||
|
||||
One drawback however is that test modules are non-importable by each other. Also, utility
|
||||
modules in the tests directories are not automatically importable because the tests directory is no longer
|
||||
added to :py:data:`sys.path`.
|
||||
|
||||
Initially we intended to make ``importlib`` the default in future releases, however it is clear now that
|
||||
it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future.
|
||||
|
||||
.. seealso::
|
||||
|
||||
The :confval:`pythonpath` configuration variable.
|
||||
|
||||
We intend to make ``importlib`` the default in future releases, depending on feedback.
|
||||
|
||||
``prepend`` and ``append`` import modes scenarios
|
||||
-------------------------------------------------
|
||||
|
||||
@@ -22,7 +22,7 @@ Install ``pytest``
|
||||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 7.0.1
|
||||
pytest 7.1.3
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ if you run this module:
|
||||
E '1'
|
||||
E Extra items in the right set:
|
||||
E '5'
|
||||
E Use -v to get the full diff
|
||||
E Use -v to get more diff
|
||||
|
||||
test_assert2.py:4: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
|
||||
@@ -126,14 +126,17 @@ pytest also introduces new options:
|
||||
in expected doctest output.
|
||||
|
||||
* ``NUMBER``: when enabled, floating-point numbers only need to match as far as
|
||||
the precision you have written in the expected doctest output. For example,
|
||||
the following output would only need to match to 2 decimal places::
|
||||
the precision you have written in the expected doctest output. The numbers are
|
||||
compared using :func:`pytest.approx` with relative tolerance equal to the
|
||||
precision. For example, the following output would only need to match to 2
|
||||
decimal places when comparing ``3.14`` to
|
||||
``pytest.approx(math.pi, rel=10**-2)``::
|
||||
|
||||
>>> math.pi
|
||||
3.14
|
||||
|
||||
If you wrote ``3.1416`` then the actual output would need to match to 4
|
||||
decimal places; and so on.
|
||||
If you wrote ``3.1416`` then the actual output would need to match to
|
||||
approximately 4 decimal places; and so on.
|
||||
|
||||
This avoids false positives caused by limited floating-point precision, like
|
||||
this::
|
||||
|
||||
@@ -736,6 +736,87 @@ 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
|
||||
@@ -1332,13 +1413,15 @@ 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 11 items
|
||||
collected 12 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]>
|
||||
@@ -1350,7 +1433,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]>
|
||||
|
||||
======================= 11 tests collected in 0.12s ========================
|
||||
======================= 12 tests collected in 0.12s ========================
|
||||
|
||||
.. _`fixture-parametrize-marks`:
|
||||
|
||||
|
||||
@@ -180,8 +180,8 @@ logging records as they are emitted directly into the console.
|
||||
|
||||
You can specify the logging level for which log records with equal or higher
|
||||
level are printed to the console by passing ``--log-cli-level``. This setting
|
||||
accepts the logging level names as seen in python's documentation or an integer
|
||||
as the logging level num.
|
||||
accepts the logging level names or numeric values as seen in
|
||||
:ref:`logging's documentation <python:levels>`.
|
||||
|
||||
Additionally, you can also specify ``--log-cli-format`` and
|
||||
``--log-cli-date-format`` which mirror and default to ``--log-format`` and
|
||||
@@ -198,11 +198,12 @@ option names are:
|
||||
If you need to record the whole test suite logging calls to a file, you can pass
|
||||
``--log-file=/path/to/log/file``. This log file is opened in write mode which
|
||||
means that it will be overwritten at each run tests session.
|
||||
Note that relative paths for the log-file location, whether passed on the CLI or declared in a
|
||||
config file, are always resolved relative to the current working directory.
|
||||
|
||||
You can also specify the logging level for the log file by passing
|
||||
``--log-file-level``. This setting accepts the logging level names as seen in
|
||||
python's documentation(ie, uppercased level names) or an integer as the logging
|
||||
level num.
|
||||
``--log-file-level``. This setting accepts the logging level names or numeric
|
||||
values as seen in :ref:`logging's documentation <python:levels>`.
|
||||
|
||||
Additionally, you can also specify ``--log-file-format`` and
|
||||
``--log-file-date-format`` which are equal to ``--log-format`` and
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
How to monkeypatch/mock modules and environments
|
||||
================================================================
|
||||
|
||||
.. currentmodule:: _pytest.monkeypatch
|
||||
.. currentmodule:: pytest
|
||||
|
||||
Sometimes tests need to invoke functionality which depends
|
||||
on global settings or which invokes code which cannot be easily
|
||||
@@ -14,17 +14,16 @@ environment variable, or to modify ``sys.path`` for importing.
|
||||
The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking
|
||||
functionality in tests:
|
||||
|
||||
.. code-block:: python
|
||||
* :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.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)
|
||||
|
||||
All modifications will be undone after the requesting
|
||||
test function or fixture has finished. The ``raising``
|
||||
@@ -55,13 +54,16 @@ during a test.
|
||||
5. Use :py:meth:`monkeypatch.syspath_prepend <MonkeyPatch.syspath_prepend>` to modify ``sys.path`` which will also
|
||||
call ``pkg_resources.fixup_namespace_packages`` and :py:func:`importlib.invalidate_caches`.
|
||||
|
||||
6. Use :py:meth:`monkeypatch.context <MonkeyPatch.context>` to apply patches only in a specific scope, which can help
|
||||
control teardown of complex fixtures or patches to the stdlib.
|
||||
|
||||
See the `monkeypatch blog post`_ for some introduction material
|
||||
and a discussion of its motivation.
|
||||
|
||||
.. _`monkeypatch blog post`: https://tetamap.wordpress.com//2009/03/03/monkeypatching-in-unit-tests-done-right/
|
||||
|
||||
Simple example: monkeypatching functions
|
||||
----------------------------------------
|
||||
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``
|
||||
@@ -436,7 +438,7 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||
_ = app.create_connection_string()
|
||||
|
||||
|
||||
.. currentmodule:: _pytest.monkeypatch
|
||||
.. currentmodule:: pytest
|
||||
|
||||
API Reference
|
||||
-------------
|
||||
|
||||
@@ -84,7 +84,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo
|
||||
> assert fruits1 == fruits2
|
||||
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
|
||||
E At index 2 diff: 'grapes' != 'orange'
|
||||
E Use -v to get the full diff
|
||||
E Use -v to get more diff
|
||||
|
||||
test_verbosity_example.py:8: AssertionError
|
||||
____________________________ test_numbers_fail _____________________________
|
||||
@@ -99,7 +99,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo
|
||||
E {'1': 1, '2': 2, '3': 3, '4': 4}
|
||||
E Right contains 4 more items:
|
||||
E {'10': 10, '20': 20, '30': 30, '40': 40}
|
||||
E Use -v to get the full diff
|
||||
E Use -v to get more diff
|
||||
|
||||
test_verbosity_example.py:14: AssertionError
|
||||
___________________________ test_long_text_fail ____________________________
|
||||
|
||||
@@ -104,8 +104,10 @@ The ``tmpdir`` and ``tmpdir_factory`` fixtures
|
||||
|
||||
The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path``
|
||||
and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects
|
||||
rather than standard :class:`pathlib.Path` objects. These days, prefer to
|
||||
use ``tmp_path`` and ``tmp_path_factory``.
|
||||
rather than standard :class:`pathlib.Path` objects.
|
||||
|
||||
.. note::
|
||||
These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``.
|
||||
|
||||
See :fixture:`tmpdir <tmpdir>` :fixture:`tmpdir_factory <tmpdir_factory>`
|
||||
API for details.
|
||||
|
||||
@@ -27,12 +27,15 @@ Almost all ``unittest`` features are supported:
|
||||
* ``setUpClass/tearDownClass``;
|
||||
* ``setUpModule/tearDownModule``;
|
||||
|
||||
.. _`pytest-subtests`: https://github.com/pytest-dev/pytest-subtests
|
||||
.. _`load_tests protocol`: https://docs.python.org/3/library/unittest.html#load-tests-protocol
|
||||
|
||||
Additionally, :ref:`subtests <python:subtests>` are supported by the
|
||||
`pytest-subtests`_ plugin.
|
||||
|
||||
Up to this point pytest does not have support for the following features:
|
||||
|
||||
* `load_tests protocol`_;
|
||||
* :ref:`subtests <python:subtests>`;
|
||||
|
||||
Benefits out of the box
|
||||
-----------------------
|
||||
|
||||
@@ -2,16 +2,11 @@
|
||||
|
||||
.. sidebar:: Next Open Trainings
|
||||
|
||||
- `PyConDE <https://2022.pycon.de/program/W93DBJ/>`__, April 11th 2022 (3h), Berlin, Germany
|
||||
- `PyConIT <https://pycon.it/en/talk/pytest-simple-rapid-and-fun-testing-with-python>`__, June 3rd 2022 (4h), Florence, Italy
|
||||
- `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>`.
|
||||
|
||||
..
|
||||
- `Europython <https://ep2022.europython.eu/>`__, July 11th to 17th (3h), Dublin, Ireland
|
||||
- `CH Open Workshoptage <https://workshoptage.ch/>`__ (German), September 6th to 8th (1 day), Bern, Switzerland
|
||||
|
||||
.. _features:
|
||||
|
||||
pytest: helps you write better programs
|
||||
@@ -27,8 +22,6 @@ scale to support complex functional testing for applications and libraries.
|
||||
|
||||
**PyPI package name**: :pypi:`pytest`
|
||||
|
||||
**Documentation as PDF**: `download latest <https://media.readthedocs.org/pdf/pytest/latest/pytest.pdf>`_
|
||||
|
||||
|
||||
A quick example
|
||||
---------------
|
||||
@@ -104,11 +97,6 @@ Bugs/Requests
|
||||
Please use the `GitHub issue tracker <https://github.com/pytest-dev/pytest/issues>`_ to submit bugs or request features.
|
||||
|
||||
|
||||
Changelog
|
||||
---------
|
||||
|
||||
Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each version.
|
||||
|
||||
Support pytest
|
||||
--------------
|
||||
|
||||
@@ -141,13 +129,3 @@ Security
|
||||
pytest has never been associated with a security vulnerability, but in any case, to report a
|
||||
security vulnerability please use the `Tidelift security contact <https://tidelift.com/security>`_.
|
||||
Tidelift will coordinate the fix and disclosure.
|
||||
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright Holger Krekel and others, 2004.
|
||||
|
||||
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
|
||||
|
||||
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/main/LICENSE
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
Python 2.7 and 3.4 support
|
||||
==========================
|
||||
|
||||
It is demanding on the maintainers of an open source project to support many Python versions, as
|
||||
there's extra cost of keeping code compatible between all versions, while holding back on
|
||||
features only made possible on newer Python versions.
|
||||
|
||||
In case of Python 2 and 3, the difference between the languages makes it even more prominent,
|
||||
because many new Python 3 features cannot be used in a Python 2/3 compatible code base.
|
||||
|
||||
Python 2.7 EOL has been reached :pep:`in 2020 <0373#maintenance-releases>`, with
|
||||
the last release made in April, 2020.
|
||||
|
||||
Python 3.4 EOL has been reached :pep:`in 2019 <0429#release-schedule>`, with the last release made in March, 2019.
|
||||
|
||||
For those reasons, in Jun 2019 it was decided that **pytest 4.6** series will be the last to support Python 2.7 and 3.4.
|
||||
|
||||
What this means for general users
|
||||
---------------------------------
|
||||
|
||||
Thanks to the `python_requires`_ setuptools option,
|
||||
Python 2.7 and Python 3.4 users using a modern pip version
|
||||
will install the last pytest 4.6.X version automatically even if 5.0 or later versions
|
||||
are available on PyPI.
|
||||
|
||||
Users should ensure they are using the latest pip and setuptools versions for this to work.
|
||||
|
||||
Maintenance of 4.6.X versions
|
||||
-----------------------------
|
||||
|
||||
Until January 2020, the pytest core team ported many bug-fixes from the main release into the
|
||||
``4.6.x`` branch, with several 4.6.X releases being made along the year.
|
||||
|
||||
From now on, the core team will **no longer actively backport patches**, but the ``4.6.x``
|
||||
branch will continue to exist so the community itself can contribute patches.
|
||||
|
||||
The core team will be happy to accept those patches, and make new 4.6.X releases **until mid-2020**
|
||||
(but consider that date as a ballpark, after that date the team might still decide to make new releases
|
||||
for critical bugs).
|
||||
|
||||
.. _`python_requires`: https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires
|
||||
|
||||
Technical aspects
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
(This section is a transcript from :issue:`5275`).
|
||||
|
||||
In this section we describe the technical aspects of the Python 2.7 and 3.4 support plan.
|
||||
|
||||
.. _what goes into 4.6.x releases:
|
||||
|
||||
What goes into 4.6.X releases
|
||||
+++++++++++++++++++++++++++++
|
||||
|
||||
New 4.6.X releases will contain bug fixes only.
|
||||
|
||||
When will 4.6.X releases happen
|
||||
+++++++++++++++++++++++++++++++
|
||||
|
||||
New 4.6.X releases will happen after we have a few bugs in place to release, or if a few weeks have
|
||||
passed (say a single bug has been fixed a month after the latest 4.6.X release).
|
||||
|
||||
No hard rules here, just ballpark.
|
||||
|
||||
Who will handle applying bug fixes
|
||||
++++++++++++++++++++++++++++++++++
|
||||
|
||||
We core maintainers expect that people still using Python 2.7/3.4 and being affected by
|
||||
bugs to step up and provide patches and/or port bug fixes from the active branches.
|
||||
|
||||
We will be happy to guide users interested in doing so, so please don't hesitate to ask.
|
||||
|
||||
**Backporting changes into 4.6**
|
||||
|
||||
Please follow these instructions:
|
||||
|
||||
#. ``git fetch --all --prune``
|
||||
|
||||
#. ``git checkout origin/4.6.x -b backport-XXXX`` # use the PR number here
|
||||
|
||||
#. Locate the merge commit on the PR, in the *merged* message, for example:
|
||||
|
||||
nicoddemus merged commit 0f8b462 into pytest-dev:features
|
||||
|
||||
#. ``git cherry-pick -m1 REVISION`` # use the revision you found above (``0f8b462``).
|
||||
|
||||
#. Open a PR targeting ``4.6.x``:
|
||||
|
||||
* Prefix the message with ``[4.6]`` so it is an obvious backport
|
||||
* Delete the PR body, it usually contains a duplicate commit message.
|
||||
|
||||
**Providing new PRs to 4.6**
|
||||
|
||||
Fresh pull requests to ``4.6.x`` will be accepted provided that
|
||||
the equivalent code in the active branches does not contain that bug (for example, a bug is specific
|
||||
to Python 2 only).
|
||||
|
||||
Bug fixes that also happen in the mainstream version should be first fixed
|
||||
there, and then backported as per instructions above.
|
||||
@@ -8,8 +8,8 @@ Reference guides
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
fixtures
|
||||
plugin_list
|
||||
customize
|
||||
reference
|
||||
fixtures
|
||||
customize
|
||||
exit-codes
|
||||
plugin_list
|
||||
|
||||
@@ -92,7 +92,7 @@ pytest.param
|
||||
pytest.raises
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`assertraises`.
|
||||
**Tutorial**: :ref:`assertraises`
|
||||
|
||||
.. autofunction:: pytest.raises(expected_exception: Exception [, *, match])
|
||||
:with: excinfo
|
||||
@@ -100,7 +100,7 @@ pytest.raises
|
||||
pytest.deprecated_call
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`ensuring_function_triggers`.
|
||||
**Tutorial**: :ref:`ensuring_function_triggers`
|
||||
|
||||
.. autofunction:: pytest.deprecated_call()
|
||||
:with:
|
||||
@@ -108,7 +108,7 @@ pytest.deprecated_call
|
||||
pytest.register_assert_rewrite
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`assertion-rewriting`.
|
||||
**Tutorial**: :ref:`assertion-rewriting`
|
||||
|
||||
.. autofunction:: pytest.register_assert_rewrite
|
||||
|
||||
@@ -123,7 +123,7 @@ pytest.warns
|
||||
pytest.freeze_includes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`freezing-pytest`.
|
||||
**Tutorial**: :ref:`freezing-pytest`
|
||||
|
||||
.. autofunction:: pytest.freeze_includes
|
||||
|
||||
@@ -143,7 +143,7 @@ fixtures or plugins.
|
||||
pytest.mark.filterwarnings
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`filterwarnings`.
|
||||
**Tutorial**: :ref:`filterwarnings`
|
||||
|
||||
Add warning filters to marked test items.
|
||||
|
||||
@@ -169,7 +169,7 @@ Add warning filters to marked test items.
|
||||
pytest.mark.parametrize
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`parametrize`.
|
||||
**Tutorial**: :ref:`parametrize`
|
||||
|
||||
This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there.
|
||||
|
||||
@@ -179,7 +179,7 @@ This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see
|
||||
pytest.mark.skip
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`skip`.
|
||||
**Tutorial**: :ref:`skip`
|
||||
|
||||
Unconditionally skip a test function.
|
||||
|
||||
@@ -193,7 +193,7 @@ Unconditionally skip a test function.
|
||||
pytest.mark.skipif
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`skipif`.
|
||||
**Tutorial**: :ref:`skipif`
|
||||
|
||||
Skip a test function if a condition is ``True``.
|
||||
|
||||
@@ -209,7 +209,7 @@ Skip a test function if a condition is ``True``.
|
||||
pytest.mark.usefixtures
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`usefixtures`.
|
||||
**Tutorial**: :ref:`usefixtures`
|
||||
|
||||
Mark a test function as using the given fixture names.
|
||||
|
||||
@@ -231,7 +231,7 @@ Mark a test function as using the given fixture names.
|
||||
pytest.mark.xfail
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`xfail`.
|
||||
**Tutorial**: :ref:`xfail`
|
||||
|
||||
Marks a test function as *expected to fail*.
|
||||
|
||||
@@ -245,7 +245,7 @@ Marks a test function as *expected to fail*.
|
||||
:keyword str reason:
|
||||
Reason why the test function is marked as xfail.
|
||||
:keyword Type[Exception] raises:
|
||||
Exception subclass expected to be raised by the test function; other exceptions will fail the test.
|
||||
Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test.
|
||||
:keyword bool run:
|
||||
If the test function should actually be executed. If ``False``, the function will always xfail and will
|
||||
not be executed (useful if a function is segfaulting).
|
||||
@@ -297,7 +297,7 @@ When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.
|
||||
Fixtures
|
||||
--------
|
||||
|
||||
**Tutorial**: :ref:`fixture`.
|
||||
**Tutorial**: :ref:`fixture`
|
||||
|
||||
Fixtures are requested by test functions or other fixtures by declaring them as argument names.
|
||||
|
||||
@@ -338,7 +338,7 @@ For more details, consult the full :ref:`fixtures docs <fixture>`.
|
||||
config.cache
|
||||
~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`cache`.
|
||||
**Tutorial**: :ref:`cache`
|
||||
|
||||
The ``config.cache`` object allows other plugins and fixtures
|
||||
to store and retrieve values across test runs. To access it from fixtures
|
||||
@@ -358,22 +358,11 @@ Under the hood, the cache plugin uses the simple
|
||||
capsys
|
||||
~~~~~~
|
||||
|
||||
:ref:`captures`.
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capsys()
|
||||
:no-auto-options:
|
||||
|
||||
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"
|
||||
|
||||
.. autoclass:: pytest.CaptureFixture()
|
||||
:members:
|
||||
|
||||
@@ -383,93 +372,48 @@ capsys
|
||||
capsysbinary
|
||||
~~~~~~~~~~~~
|
||||
|
||||
:ref:`captures`.
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capsysbinary()
|
||||
:no-auto-options:
|
||||
|
||||
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"
|
||||
|
||||
|
||||
.. fixture:: capfd
|
||||
|
||||
capfd
|
||||
~~~~~~
|
||||
|
||||
:ref:`captures`.
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capfd()
|
||||
:no-auto-options:
|
||||
|
||||
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"
|
||||
|
||||
|
||||
.. fixture:: capfdbinary
|
||||
|
||||
capfdbinary
|
||||
~~~~~~~~~~~~
|
||||
|
||||
:ref:`captures`.
|
||||
**Tutorial**: :ref:`captures`
|
||||
|
||||
.. autofunction:: _pytest.capture.capfdbinary()
|
||||
:no-auto-options:
|
||||
|
||||
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"
|
||||
|
||||
|
||||
.. fixture:: doctest_namespace
|
||||
|
||||
doctest_namespace
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`doctest`.
|
||||
**Tutorial**: :ref:`doctest`
|
||||
|
||||
.. autofunction:: _pytest.doctest.doctest_namespace()
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
.. fixture:: request
|
||||
|
||||
request
|
||||
~~~~~~~
|
||||
|
||||
:ref:`request example`.
|
||||
**Example**: :ref:`request example`
|
||||
|
||||
The ``request`` fixture is a special fixture providing information of the requesting test function.
|
||||
|
||||
@@ -490,7 +434,7 @@ pytestconfig
|
||||
record_property
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`record_property example`.
|
||||
**Tutorial**: :ref:`record_property example`
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_property()
|
||||
|
||||
@@ -500,7 +444,7 @@ record_property
|
||||
record_testsuite_property
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`record_testsuite_property example`.
|
||||
**Tutorial**: :ref:`record_testsuite_property example`
|
||||
|
||||
.. autofunction:: _pytest.junitxml.record_testsuite_property()
|
||||
|
||||
@@ -510,7 +454,7 @@ record_testsuite_property
|
||||
caplog
|
||||
~~~~~~
|
||||
|
||||
:ref:`logging`.
|
||||
**Tutorial**: :ref:`logging`
|
||||
|
||||
.. autofunction:: _pytest.logging.caplog()
|
||||
:no-auto-options:
|
||||
@@ -526,7 +470,7 @@ caplog
|
||||
monkeypatch
|
||||
~~~~~~~~~~~
|
||||
|
||||
:ref:`monkeypatching`.
|
||||
**Tutorial**: :ref:`monkeypatching`
|
||||
|
||||
.. autofunction:: _pytest.monkeypatch.monkeypatch()
|
||||
:no-auto-options:
|
||||
@@ -600,19 +544,13 @@ recwarn
|
||||
.. autoclass:: pytest.WarningsRecorder()
|
||||
:members:
|
||||
|
||||
Each recorded warning is an instance of :class:`warnings.WarningMessage`.
|
||||
|
||||
.. note::
|
||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
||||
differently; see :ref:`ensuring_function_triggers`.
|
||||
|
||||
|
||||
.. fixture:: tmp_path
|
||||
|
||||
tmp_path
|
||||
~~~~~~~~
|
||||
|
||||
:ref:`tmp_path`
|
||||
**Tutorial**: :ref:`tmp_path`
|
||||
|
||||
.. autofunction:: _pytest.tmpdir.tmp_path()
|
||||
:no-auto-options:
|
||||
@@ -623,7 +561,7 @@ tmp_path
|
||||
tmp_path_factory
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`tmp_path_factory example`
|
||||
**Tutorial**: :ref:`tmp_path_factory example`
|
||||
|
||||
.. _`tmp_path_factory factory api`:
|
||||
|
||||
@@ -638,7 +576,7 @@ tmp_path_factory
|
||||
tmpdir
|
||||
~~~~~~
|
||||
|
||||
:ref:`tmpdir and tmpdir_factory`
|
||||
**Tutorial**: :ref:`tmpdir and tmpdir_factory`
|
||||
|
||||
.. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir()
|
||||
:no-auto-options:
|
||||
@@ -649,7 +587,7 @@ tmpdir
|
||||
tmpdir_factory
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
:ref:`tmpdir and tmpdir_factory`
|
||||
**Tutorial**: :ref:`tmpdir and tmpdir_factory`
|
||||
|
||||
``tmpdir_factory`` is an instance of :class:`~pytest.TempdirFactory`:
|
||||
|
||||
@@ -662,7 +600,7 @@ tmpdir_factory
|
||||
Hooks
|
||||
-----
|
||||
|
||||
:ref:`writing-plugins`.
|
||||
**Tutorial**: :ref:`writing-plugins`
|
||||
|
||||
.. currentmodule:: _pytest.hookspec
|
||||
|
||||
@@ -1514,7 +1452,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
|
||||
|
||||
Sets a file name relative to the ``pytest.ini`` file where log messages should be written to, in addition
|
||||
Sets a file name relative to the current working directory where log messages should be written to, in addition
|
||||
to the other logging facilities that are active.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
@@ -5,3 +5,6 @@ sphinx-removed-in>=0.2.0
|
||||
sphinx>=3.1,<4
|
||||
sphinxcontrib-trio
|
||||
sphinxcontrib-svg2pdfconverter
|
||||
|
||||
# XXX: sphinx<4 is broken with latest jinja2
|
||||
jinja2<3.1
|
||||
|
||||
@@ -17,6 +17,8 @@ Books
|
||||
Talks and blog postings
|
||||
---------------------------------------------
|
||||
|
||||
- Training: `pytest - simple, rapid and fun testing with Python <https://www.youtube.com/watch?v=ofPHJrAOaTE>`_, Florian Bruhin, PyConDE 2022
|
||||
|
||||
- `pytest: Simple, rapid and fun testing with Python, <https://youtu.be/cSJ-X3TbQ1c?t=15752>`_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021
|
||||
|
||||
- Webinar: `pytest: Test Driven Development für Python (German) <https://bruhin.software/ins-pytest/>`_, Florian Bruhin, via mylearning.ch, 2020
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import sys
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -47,7 +47,6 @@ install_requires =
|
||||
pluggy>=0.12,<2.0
|
||||
py>=1.8.2
|
||||
tomli>=1.0.0
|
||||
atomicwrites>=1.0;sys_platform=="win32"
|
||||
colorama;sys_platform=="win32"
|
||||
importlib-metadata>=0.12;python_version<"3.8"
|
||||
python_requires = >=3.7
|
||||
|
||||
@@ -190,7 +190,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||
return False
|
||||
|
||||
# For matching the name it must be as if it was a filename.
|
||||
path = PurePath(os.path.sep.join(parts) + ".py")
|
||||
path = PurePath(*parts).with_suffix(".py")
|
||||
|
||||
for pat in self.fnpats:
|
||||
# if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
|
||||
@@ -281,7 +281,9 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||
else:
|
||||
from importlib.resources.readers import FileReader
|
||||
|
||||
return FileReader(types.SimpleNamespace(path=self._rewritten_names[name]))
|
||||
return FileReader( # type:ignore[no-any-return]
|
||||
types.SimpleNamespace(path=self._rewritten_names[name])
|
||||
)
|
||||
|
||||
|
||||
def _write_pyc_fp(
|
||||
@@ -302,53 +304,29 @@ def _write_pyc_fp(
|
||||
fp.write(marshal.dumps(co))
|
||||
|
||||
|
||||
if sys.platform == "win32":
|
||||
from atomicwrites import atomic_write
|
||||
|
||||
def _write_pyc(
|
||||
state: "AssertionState",
|
||||
co: types.CodeType,
|
||||
source_stat: os.stat_result,
|
||||
pyc: Path,
|
||||
) -> bool:
|
||||
try:
|
||||
with atomic_write(os.fspath(pyc), mode="wb", overwrite=True) as fp:
|
||||
_write_pyc_fp(fp, source_stat, co)
|
||||
except OSError as e:
|
||||
state.trace(f"error writing pyc file at {pyc}: {e}")
|
||||
# we ignore any failure to write the cache file
|
||||
# there are many reasons, permission-denied, pycache dir being a
|
||||
# file etc.
|
||||
return False
|
||||
return True
|
||||
|
||||
else:
|
||||
|
||||
def _write_pyc(
|
||||
state: "AssertionState",
|
||||
co: types.CodeType,
|
||||
source_stat: os.stat_result,
|
||||
pyc: Path,
|
||||
) -> bool:
|
||||
proc_pyc = f"{pyc}.{os.getpid()}"
|
||||
try:
|
||||
fp = open(proc_pyc, "wb")
|
||||
except OSError as e:
|
||||
state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}")
|
||||
return False
|
||||
|
||||
try:
|
||||
def _write_pyc(
|
||||
state: "AssertionState",
|
||||
co: types.CodeType,
|
||||
source_stat: os.stat_result,
|
||||
pyc: Path,
|
||||
) -> bool:
|
||||
proc_pyc = f"{pyc}.{os.getpid()}"
|
||||
try:
|
||||
with open(proc_pyc, "wb") as fp:
|
||||
_write_pyc_fp(fp, source_stat, co)
|
||||
os.rename(proc_pyc, pyc)
|
||||
except OSError as e:
|
||||
state.trace(f"error writing pyc file at {pyc}: {e}")
|
||||
# we ignore any failure to write the cache file
|
||||
# there are many reasons, permission-denied, pycache dir being a
|
||||
# file etc.
|
||||
return False
|
||||
finally:
|
||||
fp.close()
|
||||
return True
|
||||
except OSError as e:
|
||||
state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}")
|
||||
return False
|
||||
|
||||
try:
|
||||
os.replace(proc_pyc, pyc)
|
||||
except OSError as e:
|
||||
state.trace(f"error writing pyc file at {pyc}: {e}")
|
||||
# we ignore any failure to write the cache file
|
||||
# there are many reasons, permission-denied, pycache dir being a
|
||||
# file etc.
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]:
|
||||
|
||||
@@ -437,8 +437,10 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
|
||||
if not has_default_eq(left):
|
||||
return []
|
||||
if isdatacls(left):
|
||||
all_fields = left.__dataclass_fields__
|
||||
fields_to_check = [field for field, info in all_fields.items() if info.compare]
|
||||
import dataclasses
|
||||
|
||||
all_fields = dataclasses.fields(left)
|
||||
fields_to_check = [info.name for info in all_fields if info.compare]
|
||||
elif isattrs(left):
|
||||
all_fields = left.__attrs_attrs__
|
||||
fields_to_check = [field.name for field in all_fields if getattr(field, "eq")]
|
||||
|
||||
@@ -876,11 +876,22 @@ class CaptureFixture(Generic[AnyStr]):
|
||||
|
||||
@fixture
|
||||
def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
|
||||
"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
r"""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"
|
||||
"""
|
||||
capman = request.config.pluginmanager.getplugin("capturemanager")
|
||||
capture_fixture = CaptureFixture[str](SysCapture, request, _ispytest=True)
|
||||
@@ -893,11 +904,22 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
|
||||
|
||||
@fixture
|
||||
def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
|
||||
"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
r"""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"
|
||||
"""
|
||||
capman = request.config.pluginmanager.getplugin("capturemanager")
|
||||
capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request, _ispytest=True)
|
||||
@@ -910,11 +932,22 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None,
|
||||
|
||||
@fixture
|
||||
def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
|
||||
"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
|
||||
r"""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"
|
||||
"""
|
||||
capman = request.config.pluginmanager.getplugin("capturemanager")
|
||||
capture_fixture = CaptureFixture[str](FDCapture, request, _ispytest=True)
|
||||
@@ -927,11 +960,23 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
|
||||
|
||||
@fixture
|
||||
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
|
||||
"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
|
||||
r"""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"
|
||||
|
||||
"""
|
||||
capman = request.config.pluginmanager.getplugin("capturemanager")
|
||||
capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request, _ispytest=True)
|
||||
|
||||
@@ -538,11 +538,7 @@ class PytestPluginManager(PluginManager):
|
||||
"""
|
||||
if self._confcutdir is None:
|
||||
return True
|
||||
try:
|
||||
path.relative_to(self._confcutdir)
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
return path not in self._confcutdir.parents
|
||||
|
||||
def _try_load_conftest(
|
||||
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
||||
@@ -837,7 +833,8 @@ 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
|
||||
if module_name != "setup":
|
||||
# as well as editable installation finder modules made by setuptools
|
||||
if module_name != "setup" and not module_name.startswith("__editable__"):
|
||||
seen_some = True
|
||||
yield module_name
|
||||
elif is_package:
|
||||
|
||||
@@ -542,7 +542,11 @@ class DoctestModule(pytest.Module):
|
||||
)
|
||||
else:
|
||||
try:
|
||||
module = import_path(self.path, root=self.config.rootpath)
|
||||
module = import_path(
|
||||
self.path,
|
||||
root=self.config.rootpath,
|
||||
mode=self.config.getoption("importmode"),
|
||||
)
|
||||
except ImportError:
|
||||
if self.config.getvalue("doctest_ignore_import_errors"):
|
||||
pytest.skip("unable to import module %r" % self.path)
|
||||
@@ -730,5 +734,16 @@ def _get_report_choice(key: str) -> int:
|
||||
@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."""
|
||||
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`.
|
||||
"""
|
||||
return dict()
|
||||
|
||||
@@ -350,7 +350,7 @@ def reorder_items_atscope(
|
||||
return items_done
|
||||
|
||||
|
||||
def get_direct_param_fixture_func(request):
|
||||
def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
|
||||
return request.param
|
||||
|
||||
|
||||
@@ -412,6 +412,15 @@ class FixtureRequest:
|
||||
self._arg2fixturedefs = fixtureinfo.name2fixturedefs.copy()
|
||||
self._arg2index: Dict[str, int] = {}
|
||||
self._fixturemanager: FixtureManager = pyfuncitem.session._fixturemanager
|
||||
# Notes on the type of `param`:
|
||||
# -`request.param` is only defined in parametrized fixtures, and will raise
|
||||
# AttributeError otherwise. Python typing has no notion of "undefined", so
|
||||
# this cannot be reflected in the type.
|
||||
# - Technically `param` is only (possibly) defined on SubRequest, not
|
||||
# FixtureRequest, but the typing of that is still in flux so this cheats.
|
||||
# - In the future we might consider using a generic for the param type, but
|
||||
# for now just using Any.
|
||||
self.param: Any
|
||||
|
||||
@property
|
||||
def scope(self) -> "_ScopeName":
|
||||
@@ -491,6 +500,7 @@ class FixtureRequest:
|
||||
|
||||
@property
|
||||
def path(self) -> Path:
|
||||
"""Path where the test function was collected."""
|
||||
if self.scope not in ("function", "class", "module", "package"):
|
||||
raise AttributeError(f"path not available in {self.scope}-scoped context")
|
||||
# TODO: Remove ignore once _pyfuncitem is properly typed.
|
||||
|
||||
@@ -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", msg, str(report.longrepr))
|
||||
self._add_simple("error", bin_xml_escape(msg), str(report.longrepr))
|
||||
|
||||
def append_skipped(self, report: TestReport) -> None:
|
||||
if hasattr(report, "wasxfail"):
|
||||
|
||||
@@ -270,8 +270,15 @@ class LegacyTestdirPlugin:
|
||||
@final
|
||||
@attr.s(init=False, auto_attribs=True)
|
||||
class TempdirFactory:
|
||||
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
|
||||
for :class:``TempPathFactory``."""
|
||||
"""Backward compatibility wrapper that implements :class:`py.path.local`
|
||||
for :class:`TempPathFactory`.
|
||||
|
||||
.. note::
|
||||
These days, it is preferred to use ``tmp_path_factory``.
|
||||
|
||||
:ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
|
||||
|
||||
"""
|
||||
|
||||
_tmppath_factory: TempPathFactory
|
||||
|
||||
@@ -282,11 +289,11 @@ class TempdirFactory:
|
||||
self._tmppath_factory = tmppath_factory
|
||||
|
||||
def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
|
||||
"""Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object."""
|
||||
"""Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object."""
|
||||
return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
|
||||
|
||||
def getbasetemp(self) -> LEGACY_PATH:
|
||||
"""Backward compat wrapper for ``_tmppath_factory.getbasetemp``."""
|
||||
"""Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object."""
|
||||
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
|
||||
|
||||
|
||||
@@ -312,6 +319,11 @@ class LegacyTmpdirPlugin:
|
||||
|
||||
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
|
||||
"""
|
||||
return legacy_path(tmp_path)
|
||||
|
||||
@@ -29,21 +29,26 @@ 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
|
||||
os.environ::
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
:data:`os.environ`:
|
||||
|
||||
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)
|
||||
* :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>`
|
||||
|
||||
All modifications will be undone after the requesting test function or
|
||||
fixture has finished. The ``raising`` parameter determines if a KeyError
|
||||
or AttributeError will be raised if the set/deletion operation has no target.
|
||||
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>`.
|
||||
"""
|
||||
mpatch = MonkeyPatch()
|
||||
yield mpatch
|
||||
@@ -182,16 +187,40 @@ 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 convenience you can specify a string as ``target`` which
|
||||
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
|
||||
will be interpreted as a dotted import path, with the last part
|
||||
being the attribute name. For example,
|
||||
``monkeypatch.setattr("os.getcwd", lambda: "/")``
|
||||
would set the ``getcwd`` function of the ``os`` module.
|
||||
being the attribute name:
|
||||
|
||||
Raises AttributeError if the attribute does not exist, unless
|
||||
.. code-block:: python
|
||||
|
||||
monkeypatch.setattr("os.getcwd", lambda: "/")
|
||||
|
||||
Raises :class:`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
|
||||
@@ -353,11 +382,14 @@ class MonkeyPatch:
|
||||
There is generally no need to call `undo()`, since it is
|
||||
called automatically during tear-down.
|
||||
|
||||
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.
|
||||
.. 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.
|
||||
"""
|
||||
for obj, name, value in reversed(self._setattr):
|
||||
if value is not notset:
|
||||
|
||||
@@ -111,7 +111,7 @@ def _imply_path(
|
||||
NODE_CTOR_FSPATH_ARG.format(
|
||||
node_type_name=node_type.__name__,
|
||||
),
|
||||
stacklevel=3,
|
||||
stacklevel=6,
|
||||
)
|
||||
if path is not None:
|
||||
if fspath is not None:
|
||||
|
||||
@@ -133,9 +133,11 @@ class ApproxBase:
|
||||
# raise if there are any non-numeric elements in the sequence.
|
||||
|
||||
|
||||
def _recursive_list_map(f, x):
|
||||
if isinstance(x, list):
|
||||
return [_recursive_list_map(f, xi) for xi in x]
|
||||
def _recursive_sequence_map(f, x):
|
||||
"""Recursively map a function over a sequence of arbitary depth"""
|
||||
if isinstance(x, (list, tuple)):
|
||||
seq_type = type(x)
|
||||
return seq_type(_recursive_sequence_map(f, xi) for xi in x)
|
||||
else:
|
||||
return f(x)
|
||||
|
||||
@@ -144,7 +146,9 @@ class ApproxNumpy(ApproxBase):
|
||||
"""Perform approximate comparisons where the expected value is numpy array."""
|
||||
|
||||
def __repr__(self) -> str:
|
||||
list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist())
|
||||
list_scalars = _recursive_sequence_map(
|
||||
self._approx_scalar, self.expected.tolist()
|
||||
)
|
||||
return f"approx({list_scalars!r})"
|
||||
|
||||
def _repr_compare(self, other_side: "ndarray") -> List[str]:
|
||||
@@ -164,7 +168,7 @@ class ApproxNumpy(ApproxBase):
|
||||
return value
|
||||
|
||||
np_array_shape = self.expected.shape
|
||||
approx_side_as_list = _recursive_list_map(
|
||||
approx_side_as_seq = _recursive_sequence_map(
|
||||
self._approx_scalar, self.expected.tolist()
|
||||
)
|
||||
|
||||
@@ -179,7 +183,7 @@ class ApproxNumpy(ApproxBase):
|
||||
max_rel_diff = -math.inf
|
||||
different_ids = []
|
||||
for index in itertools.product(*(range(i) for i in np_array_shape)):
|
||||
approx_value = get_value_from_nested_list(approx_side_as_list, index)
|
||||
approx_value = get_value_from_nested_list(approx_side_as_seq, index)
|
||||
other_value = get_value_from_nested_list(other_side, index)
|
||||
if approx_value != other_value:
|
||||
abs_diff = abs(approx_value.expected - other_value)
|
||||
@@ -194,7 +198,7 @@ class ApproxNumpy(ApproxBase):
|
||||
(
|
||||
str(index),
|
||||
str(get_value_from_nested_list(other_side, index)),
|
||||
str(get_value_from_nested_list(approx_side_as_list, index)),
|
||||
str(get_value_from_nested_list(approx_side_as_seq, index)),
|
||||
)
|
||||
for index in different_ids
|
||||
]
|
||||
@@ -319,7 +323,6 @@ class ApproxSequenceLike(ApproxBase):
|
||||
|
||||
def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
if len(self.expected) != len(other_side):
|
||||
return [
|
||||
@@ -327,7 +330,7 @@ class ApproxSequenceLike(ApproxBase):
|
||||
f"Lengths: {len(self.expected)} and {len(other_side)}",
|
||||
]
|
||||
|
||||
approx_side_as_map = _recursive_list_map(self._approx_scalar, self.expected)
|
||||
approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected)
|
||||
|
||||
number_of_elements = len(approx_side_as_map)
|
||||
max_abs_diff = -math.inf
|
||||
@@ -340,7 +343,7 @@ class ApproxSequenceLike(ApproxBase):
|
||||
abs_diff = abs(approx_value.expected - other_value)
|
||||
max_abs_diff = max(max_abs_diff, abs_diff)
|
||||
if other_value == 0.0:
|
||||
max_rel_diff = np.inf
|
||||
max_rel_diff = math.inf
|
||||
else:
|
||||
max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value))
|
||||
different_ids.append(i)
|
||||
@@ -573,7 +576,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
||||
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
|
||||
True
|
||||
|
||||
The comparision will be true if both mappings have the same keys and their
|
||||
The comparison will be true if both mappings have the same keys and their
|
||||
respective values match the expected tolerances.
|
||||
|
||||
**Tolerances**
|
||||
|
||||
@@ -158,7 +158,14 @@ def warns(
|
||||
class WarningsRecorder(warnings.catch_warnings):
|
||||
"""A context manager to record raised warnings.
|
||||
|
||||
Each recorded warning is an instance of :class:`warnings.WarningMessage`.
|
||||
|
||||
Adapted from `warnings.catch_warnings`.
|
||||
|
||||
.. note::
|
||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
|
||||
differently; see :ref:`ensuring_function_triggers`.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, *, _ispytest: bool = False) -> None:
|
||||
|
||||
@@ -455,7 +455,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
||||
def serialize_repr_entry(
|
||||
entry: Union[ReprEntry, ReprEntryNative]
|
||||
) -> Dict[str, Any]:
|
||||
data = attr.asdict(entry)
|
||||
data = attr.asdict(entry) # type:ignore[arg-type]
|
||||
for key, value in data.items():
|
||||
if hasattr(value, "__dict__"):
|
||||
data[key] = attr.asdict(value)
|
||||
@@ -463,7 +463,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
||||
return entry_data
|
||||
|
||||
def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]:
|
||||
result = attr.asdict(reprtraceback)
|
||||
result = attr.asdict(reprtraceback) # type:ignore[arg-type]
|
||||
result["reprentries"] = [
|
||||
serialize_repr_entry(x) for x in reprtraceback.reprentries
|
||||
]
|
||||
@@ -473,7 +473,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]:
|
||||
reprcrash: Optional[ReprFileLocation],
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
if reprcrash is not None:
|
||||
return attr.asdict(reprcrash)
|
||||
return attr.asdict(reprcrash) # type:ignore[arg-type]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@@ -158,9 +158,10 @@ class TempPathFactory:
|
||||
def get_user() -> Optional[str]:
|
||||
"""Return the current user name, or None if getuser() does not work
|
||||
in the current environment (see #1010)."""
|
||||
import getpass
|
||||
|
||||
try:
|
||||
# In some exotic environments, getpass may not be importable.
|
||||
import getpass
|
||||
|
||||
return getpass.getuser()
|
||||
except (ImportError, KeyError):
|
||||
return None
|
||||
|
||||
@@ -316,7 +316,10 @@ class TestCaseFunction(Function):
|
||||
# Arguably we could always postpone tearDown(), but this changes the moment where the
|
||||
# TestCase instance interacts with the results object, so better to only do it
|
||||
# when absolutely needed.
|
||||
if self.config.getoption("usepdb") and not _is_skipped(self.obj):
|
||||
# We need to consider if the test itself is skipped, or the whole class.
|
||||
assert isinstance(self.parent, UnitTestCase)
|
||||
skipped = _is_skipped(self.obj) or _is_skipped(self.parent.obj)
|
||||
if self.config.getoption("usepdb") and not skipped:
|
||||
self._explicit_tearDown = self._testcase.tearDown
|
||||
setattr(self._testcase, "tearDown", lambda *args: None)
|
||||
|
||||
|
||||
12
testing/example_scripts/dataclasses/test_compare_initvar.py
Normal file
12
testing/example_scripts/dataclasses/test_compare_initvar.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import InitVar
|
||||
|
||||
|
||||
@dataclass
|
||||
class Foo:
|
||||
init_only: InitVar[int]
|
||||
real_attr: int
|
||||
|
||||
|
||||
def test_demonstrate():
|
||||
assert Foo(1, 2) == Foo(1, 3)
|
||||
@@ -2,12 +2,14 @@ import operator
|
||||
from contextlib import contextmanager
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
from math import sqrt
|
||||
from operator import eq
|
||||
from operator import ne
|
||||
from typing import Optional
|
||||
|
||||
import pytest
|
||||
from _pytest.pytester import Pytester
|
||||
from _pytest.python_api import _recursive_sequence_map
|
||||
from pytest import approx
|
||||
|
||||
inf, nan = float("inf"), float("nan")
|
||||
@@ -92,9 +94,7 @@ SOME_INT = r"[0-9]+\s*"
|
||||
|
||||
|
||||
class TestApprox:
|
||||
def test_error_messages(self, assert_approx_raises_regex):
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
def test_error_messages_native_dtypes(self, assert_approx_raises_regex):
|
||||
assert_approx_raises_regex(
|
||||
2.0,
|
||||
1.0,
|
||||
@@ -135,6 +135,34 @@ class TestApprox:
|
||||
],
|
||||
)
|
||||
|
||||
assert_approx_raises_regex(
|
||||
(1, 2.2, 4),
|
||||
(1, 3.2, 4),
|
||||
[
|
||||
r" comparison failed. Mismatched elements: 1 / 3:",
|
||||
rf" Max absolute difference: {SOME_FLOAT}",
|
||||
rf" Max relative difference: {SOME_FLOAT}",
|
||||
r" Index \| Obtained\s+\| Expected ",
|
||||
rf" 1 \| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
|
||||
],
|
||||
)
|
||||
|
||||
# Specific test for comparison with 0.0 (relative diff will be 'inf')
|
||||
assert_approx_raises_regex(
|
||||
[0.0],
|
||||
[1.0],
|
||||
[
|
||||
r" comparison failed. Mismatched elements: 1 / 1:",
|
||||
rf" Max absolute difference: {SOME_FLOAT}",
|
||||
r" Max relative difference: inf",
|
||||
r" Index \| Obtained\s+\| Expected ",
|
||||
rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
|
||||
],
|
||||
)
|
||||
|
||||
def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex):
|
||||
np = pytest.importorskip("numpy")
|
||||
|
||||
a = np.linspace(0, 100, 20)
|
||||
b = np.linspace(0, 100, 20)
|
||||
a[10] += 0.5
|
||||
@@ -175,18 +203,6 @@ class TestApprox:
|
||||
)
|
||||
|
||||
# Specific test for comparison with 0.0 (relative diff will be 'inf')
|
||||
assert_approx_raises_regex(
|
||||
[0.0],
|
||||
[1.0],
|
||||
[
|
||||
r" comparison failed. Mismatched elements: 1 / 1:",
|
||||
rf" Max absolute difference: {SOME_FLOAT}",
|
||||
r" Max relative difference: inf",
|
||||
r" Index \| Obtained\s+\| Expected ",
|
||||
rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
|
||||
],
|
||||
)
|
||||
|
||||
assert_approx_raises_regex(
|
||||
np.array([0.0]),
|
||||
np.array([1.0]),
|
||||
@@ -876,3 +892,31 @@ class TestApprox:
|
||||
"""pytest.approx() should raise an error on unordered sequences (#9692)."""
|
||||
with pytest.raises(TypeError, match="only supports ordered sequences"):
|
||||
assert {1, 2, 3} == approx({1, 2, 3})
|
||||
|
||||
|
||||
class TestRecursiveSequenceMap:
|
||||
def test_map_over_scalar(self):
|
||||
assert _recursive_sequence_map(sqrt, 16) == 4
|
||||
|
||||
def test_map_over_empty_list(self):
|
||||
assert _recursive_sequence_map(sqrt, []) == []
|
||||
|
||||
def test_map_over_list(self):
|
||||
assert _recursive_sequence_map(sqrt, [4, 16, 25, 676]) == [2, 4, 5, 26]
|
||||
|
||||
def test_map_over_tuple(self):
|
||||
assert _recursive_sequence_map(sqrt, (4, 16, 25, 676)) == (2, 4, 5, 26)
|
||||
|
||||
def test_map_over_nested_lists(self):
|
||||
assert _recursive_sequence_map(sqrt, [4, [25, 64], [[49]]]) == [
|
||||
2,
|
||||
[5, 8],
|
||||
[[7]],
|
||||
]
|
||||
|
||||
def test_map_over_mixed_sequence(self):
|
||||
assert _recursive_sequence_map(sqrt, [4, (25, 64), [(49)]]) == [
|
||||
2,
|
||||
(5, 8),
|
||||
[(7)],
|
||||
]
|
||||
|
||||
@@ -82,13 +82,9 @@ class TestRaises:
|
||||
def test_does_not_raise(self, pytester: Pytester) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
import pytest
|
||||
|
||||
@contextmanager
|
||||
def does_not_raise():
|
||||
yield
|
||||
|
||||
@pytest.mark.parametrize('example_input,expectation', [
|
||||
(3, does_not_raise()),
|
||||
(2, does_not_raise()),
|
||||
@@ -107,13 +103,9 @@ class TestRaises:
|
||||
def test_does_not_raise_does_raise(self, pytester: Pytester) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
import pytest
|
||||
|
||||
@contextmanager
|
||||
def does_not_raise():
|
||||
yield
|
||||
|
||||
@pytest.mark.parametrize('example_input,expectation', [
|
||||
(0, does_not_raise()),
|
||||
(1, pytest.raises(ZeroDivisionError)),
|
||||
|
||||
@@ -882,6 +882,13 @@ class TestAssert_reprcompare_dataclass:
|
||||
result.assert_outcomes(failed=1, passed=0)
|
||||
result.stdout.no_re_match_line(".*Differing attributes.*")
|
||||
|
||||
def test_data_classes_with_initvar(self, pytester: Pytester) -> None:
|
||||
p = pytester.copy_example("dataclasses/test_compare_initvar.py")
|
||||
# issue 9820
|
||||
result = pytester.runpytest(p, "-vv")
|
||||
result.assert_outcomes(failed=1, passed=0)
|
||||
result.stdout.no_re_match_line(".*AttributeError.*")
|
||||
|
||||
|
||||
class TestAssert_reprcompare_attrsclass:
|
||||
def test_attrs(self) -> None:
|
||||
|
||||
@@ -204,16 +204,8 @@ class TestAssertionRewrite:
|
||||
def f4() -> None:
|
||||
assert sys == 42 # type: ignore[comparison-overlap]
|
||||
|
||||
verbose = request.config.getoption("verbose")
|
||||
msg = getmsg(f4, {"sys": sys})
|
||||
if verbose > 0:
|
||||
assert msg == (
|
||||
"assert <module 'sys' (built-in)> == 42\n"
|
||||
" +<module 'sys' (built-in)>\n"
|
||||
" -42"
|
||||
)
|
||||
else:
|
||||
assert msg == "assert sys == 42"
|
||||
assert msg == "assert sys == 42"
|
||||
|
||||
def f5() -> None:
|
||||
assert cls == 42 # type: ignore[name-defined] # noqa: F821
|
||||
@@ -224,20 +216,7 @@ class TestAssertionRewrite:
|
||||
msg = getmsg(f5, {"cls": X})
|
||||
assert msg is not None
|
||||
lines = msg.splitlines()
|
||||
if verbose > 1:
|
||||
assert lines == [
|
||||
f"assert {X!r} == 42",
|
||||
f" +{X!r}",
|
||||
" -42",
|
||||
]
|
||||
elif verbose > 0:
|
||||
assert lines == [
|
||||
"assert <class 'test_...e.<locals>.X'> == 42",
|
||||
f" +{X!r}",
|
||||
" -42",
|
||||
]
|
||||
else:
|
||||
assert lines == ["assert cls == 42"]
|
||||
assert lines == ["assert cls == 42"]
|
||||
|
||||
def test_assertrepr_compare_same_width(self, request) -> None:
|
||||
"""Should use same width/truncation with same initial width."""
|
||||
@@ -279,14 +258,11 @@ class TestAssertionRewrite:
|
||||
msg = getmsg(f, {"cls": Y})
|
||||
assert msg is not None
|
||||
lines = msg.splitlines()
|
||||
if request.config.getoption("verbose") > 0:
|
||||
assert lines == ["assert 3 == 2", " +3", " -2"]
|
||||
else:
|
||||
assert lines == [
|
||||
"assert 3 == 2",
|
||||
" + where 3 = Y.foo",
|
||||
" + where Y = cls()",
|
||||
]
|
||||
assert lines == [
|
||||
"assert 3 == 2",
|
||||
" + where 3 = Y.foo",
|
||||
" + where Y = cls()",
|
||||
]
|
||||
|
||||
def test_assert_already_has_message(self) -> None:
|
||||
def f():
|
||||
@@ -663,10 +639,7 @@ class TestAssertionRewrite:
|
||||
assert len(values) == 11
|
||||
|
||||
msg = getmsg(f)
|
||||
if request.config.getoption("verbose") > 0:
|
||||
assert msg == "assert 10 == 11\n +10\n -11"
|
||||
else:
|
||||
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
|
||||
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
|
||||
|
||||
def test_custom_reprcompare(self, monkeypatch) -> None:
|
||||
def my_reprcompare1(op, left, right) -> str:
|
||||
@@ -732,10 +705,7 @@ class TestAssertionRewrite:
|
||||
msg = getmsg(f)
|
||||
assert msg is not None
|
||||
lines = util._format_lines([msg])
|
||||
if request.config.getoption("verbose") > 0:
|
||||
assert lines == ["assert 0 == 1\n +0\n -1"]
|
||||
else:
|
||||
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
||||
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
||||
|
||||
def test_custom_repr_non_ascii(self) -> None:
|
||||
def f() -> None:
|
||||
@@ -1039,7 +1009,7 @@ class TestAssertionRewriteHookDetails:
|
||||
)
|
||||
assert pytester.runpytest().ret == 0
|
||||
|
||||
def test_write_pyc(self, pytester: Pytester, tmp_path, monkeypatch) -> None:
|
||||
def test_write_pyc(self, pytester: Pytester, tmp_path) -> None:
|
||||
from _pytest.assertion.rewrite import _write_pyc
|
||||
from _pytest.assertion import AssertionState
|
||||
|
||||
@@ -1051,27 +1021,8 @@ class TestAssertionRewriteHookDetails:
|
||||
co = compile("1", "f.py", "single")
|
||||
assert _write_pyc(state, co, os.stat(source_path), pycpath)
|
||||
|
||||
if sys.platform == "win32":
|
||||
from contextlib import contextmanager
|
||||
|
||||
@contextmanager
|
||||
def atomic_write_failed(fn, mode="r", overwrite=False):
|
||||
e = OSError()
|
||||
e.errno = 10
|
||||
raise e
|
||||
yield
|
||||
|
||||
monkeypatch.setattr(
|
||||
_pytest.assertion.rewrite, "atomic_write", atomic_write_failed
|
||||
)
|
||||
else:
|
||||
|
||||
def raise_oserror(*args):
|
||||
raise OSError()
|
||||
|
||||
monkeypatch.setattr("os.rename", raise_oserror)
|
||||
|
||||
assert not _write_pyc(state, co, os.stat(source_path), pycpath)
|
||||
with mock.patch.object(os, "replace", side_effect=OSError):
|
||||
assert not _write_pyc(state, co, os.stat(source_path), pycpath)
|
||||
|
||||
def test_resources_provider_for_loader(self, pytester: Pytester) -> None:
|
||||
"""
|
||||
|
||||
@@ -651,7 +651,7 @@ class Test_getinitialnodes:
|
||||
for parent in col.listchain():
|
||||
assert parent.config is config
|
||||
|
||||
def test_pkgfile(self, pytester: Pytester) -> None:
|
||||
def test_pkgfile(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
|
||||
"""Verify nesting when a module is within a package.
|
||||
The parent chain should match: Module<x.py> -> Package<subdir> -> Session.
|
||||
Session's parent should always be None.
|
||||
@@ -660,7 +660,8 @@ class Test_getinitialnodes:
|
||||
subdir = tmp_path.joinpath("subdir")
|
||||
x = ensure_file(subdir / "x.py")
|
||||
ensure_file(subdir / "__init__.py")
|
||||
with subdir.cwd():
|
||||
with monkeypatch.context() as mp:
|
||||
mp.chdir(subdir)
|
||||
config = pytester.parseconfigure(x)
|
||||
col = pytester.getnode(config, x)
|
||||
assert col is not None
|
||||
@@ -1188,8 +1189,7 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
|
||||
"""
|
||||
% (str(subdir),)
|
||||
)
|
||||
with pytester.path.cwd():
|
||||
result = pytester.runpytest()
|
||||
result = pytester.runpytest()
|
||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
assert result.ret == 0
|
||||
|
||||
@@ -1200,8 +1200,7 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
|
||||
testpaths = .
|
||||
"""
|
||||
)
|
||||
with pytester.path.cwd():
|
||||
result = pytester.runpytest("--collect-only")
|
||||
result = pytester.runpytest("--collect-only")
|
||||
result.stdout.fnmatch_lines(["collected 1 item"])
|
||||
|
||||
|
||||
@@ -1224,7 +1223,8 @@ def test_collect_pyargs_with_testpaths(
|
||||
)
|
||||
)
|
||||
monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep)
|
||||
with root.cwd():
|
||||
with monkeypatch.context() as mp:
|
||||
mp.chdir(root)
|
||||
result = pytester.runpytest_subprocess()
|
||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
|
||||
|
||||
@@ -837,6 +837,9 @@ class TestConfigAPI:
|
||||
(["src/bar/__init__.py"], ["bar"]),
|
||||
(["src/bar/__init__.py", "setup.py"], ["bar"]),
|
||||
(["source/python/bar/__init__.py", "setup.py"], ["bar"]),
|
||||
# editable installation finder modules
|
||||
(["__editable___xyz_finder.py"], []),
|
||||
(["bar/__init__.py", "__editable___xyz_finder.py"], ["bar"]),
|
||||
],
|
||||
)
|
||||
def test_iter_rewritable_modules(self, names, expected) -> None:
|
||||
|
||||
@@ -252,6 +252,34 @@ def test_conftest_confcutdir(pytester: Pytester) -> None:
|
||||
result.stdout.no_fnmatch_line("*warning: could not load initial*")
|
||||
|
||||
|
||||
def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) -> None:
|
||||
"""When using `--pyargs` to run tests in an installed packages (located e.g.
|
||||
in a site-packages in the PYTHONPATH), conftest files in there are picked
|
||||
up.
|
||||
|
||||
Regression test for #9767.
|
||||
"""
|
||||
# pytester dir - the source tree.
|
||||
# tmp_path - the simulated site-packages dir (not in source tree).
|
||||
|
||||
pytester.syspathinsert(tmp_path)
|
||||
pytester.makepyprojecttoml("[tool.pytest.ini_options]")
|
||||
tmp_path.joinpath("foo").mkdir()
|
||||
tmp_path.joinpath("foo", "__init__.py").touch()
|
||||
tmp_path.joinpath("foo", "conftest.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
@pytest.fixture
|
||||
def fix(): return None
|
||||
"""
|
||||
)
|
||||
)
|
||||
tmp_path.joinpath("foo", "test_it.py").write_text("def test_it(fix): pass")
|
||||
result = pytester.runpytest("--pyargs", "foo")
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_conftest_symlink(pytester: Pytester) -> None:
|
||||
"""`conftest.py` discovery follows normal path resolution and does not resolve symlinks."""
|
||||
# Structure:
|
||||
@@ -525,7 +553,7 @@ class TestConftestVisibility:
|
||||
)
|
||||
)
|
||||
print("created directory structure:")
|
||||
for x in pytester.path.rglob(""):
|
||||
for x in pytester.path.glob("**/"):
|
||||
print(" " + str(x.relative_to(pytester.path)))
|
||||
|
||||
return {"runner": runner, "package": package, "swc": swc, "snc": snc}
|
||||
|
||||
@@ -353,6 +353,7 @@ class TestPDB:
|
||||
result = pytester.runpytest_subprocess("--pdb", ".")
|
||||
result.stdout.fnmatch_lines(["-> import unknown"])
|
||||
|
||||
@pytest.mark.xfail(reason="#10042")
|
||||
def test_pdb_interaction_capturing_simple(self, pytester: Pytester) -> None:
|
||||
p1 = pytester.makepyfile(
|
||||
"""
|
||||
@@ -521,6 +522,7 @@ class TestPDB:
|
||||
assert "BdbQuit" not in rest
|
||||
assert "UNEXPECTED EXCEPTION" not in rest
|
||||
|
||||
@pytest.mark.xfail(reason="#10042")
|
||||
def test_pdb_interaction_capturing_twice(self, pytester: Pytester) -> None:
|
||||
p1 = pytester.makepyfile(
|
||||
"""
|
||||
@@ -556,6 +558,7 @@ class TestPDB:
|
||||
assert "1 failed" in rest
|
||||
self.flush(child)
|
||||
|
||||
@pytest.mark.xfail(reason="#10042")
|
||||
def test_pdb_with_injected_do_debug(self, pytester: Pytester) -> None:
|
||||
"""Simulates pdbpp, which injects Pdb into do_debug, and uses
|
||||
self.__class__ in do_continue.
|
||||
@@ -1000,6 +1003,7 @@ class TestDebuggingBreakpoints:
|
||||
assert "reading from stdin while output" not in rest
|
||||
TestPDB.flush(child)
|
||||
|
||||
@pytest.mark.xfail(reason="#10042")
|
||||
def test_pdb_not_altered(self, pytester: Pytester) -> None:
|
||||
p1 = pytester.makepyfile(
|
||||
"""
|
||||
@@ -1159,6 +1163,7 @@ def test_quit_with_swallowed_SystemExit(pytester: Pytester) -> None:
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fixture", ("capfd", "capsys"))
|
||||
@pytest.mark.xfail(reason="#10042")
|
||||
def test_pdb_suspends_fixture_capturing(pytester: Pytester, fixture: str) -> None:
|
||||
"""Using "-s" with pytest should suspend/resume fixture capturing."""
|
||||
p1 = pytester.makepyfile(
|
||||
|
||||
@@ -113,6 +113,28 @@ class TestDoctests:
|
||||
reprec = pytester.inline_run(p)
|
||||
reprec.assertoutcome(failed=1)
|
||||
|
||||
def test_importmode(self, pytester: Pytester):
|
||||
p = pytester.makepyfile(
|
||||
**{
|
||||
"namespacepkg/innerpkg/__init__.py": "",
|
||||
"namespacepkg/innerpkg/a.py": """
|
||||
def some_func():
|
||||
return 42
|
||||
""",
|
||||
"namespacepkg/innerpkg/b.py": """
|
||||
from namespacepkg.innerpkg.a import some_func
|
||||
def my_func():
|
||||
'''
|
||||
>>> my_func()
|
||||
42
|
||||
'''
|
||||
return some_func()
|
||||
""",
|
||||
}
|
||||
)
|
||||
reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib")
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
def test_new_pattern(self, pytester: Pytester):
|
||||
p = pytester.maketxtfile(
|
||||
xdoc="""
|
||||
@@ -201,7 +223,11 @@ class TestDoctests:
|
||||
"Traceback (most recent call last):",
|
||||
' File "*/doctest.py", line *, in __run',
|
||||
" *",
|
||||
*((" *^^^^*",) if sys.version_info >= (3, 11) else ()),
|
||||
*(
|
||||
(" *^^^^*",)
|
||||
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
|
||||
else ()
|
||||
),
|
||||
' File "<doctest test_doctest_unexpected_exception.txt[1]>", line 1, in <module>',
|
||||
"ZeroDivisionError: division by zero",
|
||||
"*/test_doctest_unexpected_exception.txt:2: UnexpectedException",
|
||||
|
||||
@@ -1625,6 +1625,28 @@ def test_escaped_skipreason_issue3533(
|
||||
snode.assert_attr(message="1 <> 2")
|
||||
|
||||
|
||||
def test_escaped_setup_teardown_error(
|
||||
pytester: Pytester, run_and_parse: RunAndParse
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.fixture()
|
||||
def my_setup():
|
||||
raise Exception("error: \033[31mred\033[m")
|
||||
|
||||
def test_esc(my_setup):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
_, dom = run_and_parse()
|
||||
node = dom.find_first_by_tag("testcase")
|
||||
snode = node.find_first_by_tag("error")
|
||||
assert "#x1B[31mred#x1B[m" in snode["message"]
|
||||
assert "#x1B[31mred#x1B[m" in snode.text
|
||||
|
||||
|
||||
@parametrize_families
|
||||
def test_logging_passing_tests_disabled_does_not_log_test_output(
|
||||
pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str
|
||||
|
||||
@@ -47,7 +47,7 @@ def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None:
|
||||
|
||||
end_lines = (
|
||||
result.stdout.lines[-4:]
|
||||
if sys.version_info >= (3, 11)
|
||||
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
|
||||
else result.stdout.lines[-3:]
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None:
|
||||
'INTERNALERROR> raise SystemExit("boom")',
|
||||
*(
|
||||
("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",)
|
||||
if sys.version_info >= (3, 11)
|
||||
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
|
||||
else ()
|
||||
),
|
||||
"INTERNALERROR> SystemExit: boom",
|
||||
@@ -68,7 +68,7 @@ def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None:
|
||||
'INTERNALERROR> raise ValueError("boom")',
|
||||
*(
|
||||
("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",)
|
||||
if sys.version_info >= (3, 11)
|
||||
if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11)
|
||||
else ()
|
||||
),
|
||||
"INTERNALERROR> ValueError: boom",
|
||||
|
||||
@@ -1241,12 +1241,15 @@ def test_pdb_teardown_called(pytester: Pytester, monkeypatch: MonkeyPatch) -> No
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"])
|
||||
def test_pdb_teardown_skipped(
|
||||
def test_pdb_teardown_skipped_for_functions(
|
||||
pytester: Pytester, monkeypatch: MonkeyPatch, mark: str
|
||||
) -> None:
|
||||
"""With --pdb, setUp and tearDown should not be called for skipped tests."""
|
||||
"""
|
||||
With --pdb, setUp and tearDown should not be called for tests skipped
|
||||
via a decorator (#7215).
|
||||
"""
|
||||
tracked: List[str] = []
|
||||
monkeypatch.setattr(pytest, "test_pdb_teardown_skipped", tracked, raising=False)
|
||||
monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False)
|
||||
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
@@ -1256,10 +1259,10 @@ def test_pdb_teardown_skipped(
|
||||
class MyTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pytest.test_pdb_teardown_skipped.append("setUp:" + self.id())
|
||||
pytest.track_pdb_teardown_skipped.append("setUp:" + self.id())
|
||||
|
||||
def tearDown(self):
|
||||
pytest.test_pdb_teardown_skipped.append("tearDown:" + self.id())
|
||||
pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id())
|
||||
|
||||
{mark}("skipped for reasons")
|
||||
def test_1(self):
|
||||
@@ -1274,6 +1277,43 @@ def test_pdb_teardown_skipped(
|
||||
assert tracked == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mark", ["@unittest.skip", "@pytest.mark.skip"])
|
||||
def test_pdb_teardown_skipped_for_classes(
|
||||
pytester: Pytester, monkeypatch: MonkeyPatch, mark: str
|
||||
) -> None:
|
||||
"""
|
||||
With --pdb, setUp and tearDown should not be called for tests skipped
|
||||
via a decorator on the class (#10060).
|
||||
"""
|
||||
tracked: List[str] = []
|
||||
monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False)
|
||||
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import unittest
|
||||
import pytest
|
||||
|
||||
{mark}("skipped for reasons")
|
||||
class MyTestCase(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
pytest.track_pdb_teardown_skipped.append("setUp:" + self.id())
|
||||
|
||||
def tearDown(self):
|
||||
pytest.track_pdb_teardown_skipped.append("tearDown:" + self.id())
|
||||
|
||||
def test_1(self):
|
||||
pass
|
||||
|
||||
""".format(
|
||||
mark=mark
|
||||
)
|
||||
)
|
||||
result = pytester.runpytest_inprocess("--pdb")
|
||||
result.stdout.fnmatch_lines("* 1 skipped in *")
|
||||
assert tracked == []
|
||||
|
||||
|
||||
def test_async_support(pytester: Pytester) -> None:
|
||||
pytest.importorskip("unittest.async_case")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user