Compare commits

..

2 Commits

Author SHA1 Message Date
Andrew Svetlov efbd83fe04 Add changelog 2022-01-14 15:24:39 +02:00
Andrew Svetlov 23232f3aa0 Expose FixtureDef and SubRequest 2022-01-14 15:10:02 +02:00
128 changed files with 1653 additions and 3260 deletions
-56
View File
@@ -1,56 +0,0 @@
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: test
name: main
on:
push:
@@ -24,7 +24,7 @@ permissions: {}
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 45
timeout-minutes: 30
permissions:
contents: read
@@ -37,7 +37,6 @@ jobs:
"windows-py38",
"windows-py39",
"windows-py310",
"windows-py311",
"ubuntu-py37",
"ubuntu-py37-pluggy",
@@ -45,13 +44,10 @@ jobs:
"ubuntu-py38",
"ubuntu-py39",
"ubuntu-py310",
"ubuntu-py311",
"ubuntu-pypy3",
"macos-py37",
"macos-py38",
"macos-py39",
"macos-py310",
"docs",
"doctesting",
@@ -77,13 +73,9 @@ jobs:
os: windows-latest
tox_env: "py39-xdist"
- name: "windows-py310"
python: "3.10"
python: "3.10.1"
os: windows-latest
tox_env: "py310-xdist"
- name: "windows-py311"
python: "3.11-dev"
os: windows-latest
tox_env: "py311"
- name: "ubuntu-py37"
python: "3.7"
@@ -107,14 +99,9 @@ jobs:
os: ubuntu-latest
tox_env: "py39-xdist"
- name: "ubuntu-py310"
python: "3.10"
python: "3.10.1"
os: ubuntu-latest
tox_env: "py310-xdist"
- name: "ubuntu-py311"
python: "3.11-dev"
os: ubuntu-latest
tox_env: "py311"
use_coverage: true
- name: "ubuntu-pypy3"
python: "pypy-3.7"
os: ubuntu-latest
@@ -129,14 +116,6 @@ jobs:
os: macos-latest
tox_env: "py38-xdist"
use_coverage: true
- name: "macos-py39"
python: "3.9"
os: macos-latest
tox_env: "py39-xdist"
- name: "macos-py310"
python: "3.10"
os: macos-latest
tox_env: "py310-xdist"
- name: "plugins"
python: "3.9"
@@ -183,9 +162,51 @@ jobs:
- name: Upload coverage to Codecov
if: "matrix.use_coverage"
uses: codecov/codecov-action@v3
continue-on-error: true
uses: codecov/codecov-action@v2
with:
fail_ci_if_error: true
files: ./coverage.xml
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
View File
@@ -12,7 +12,6 @@ permissions: {}
jobs:
createPullRequest:
if: github.repository_owner == 'pytest-dev'
runs-on: ubuntu-latest
permissions:
contents: write
+6 -16
View File
@@ -1,11 +1,11 @@
repos:
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 21.12b0
hooks:
- id: black
args: [--safe, --quiet]
- repo: https://github.com/asottile/blacken-docs
rev: v1.12.1
rev: v1.12.0
hooks:
- id: blacken-docs
additional_dependencies: [black==20.8b1]
@@ -20,24 +20,16 @@ repos:
- id: debug-statements
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://github.com/myint/autoflake
rev: v1.4
hooks:
- id: autoflake
name: autoflake
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
language: python
files: \.py$
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
hooks:
- id: flake8
language_version: python3
additional_dependencies:
- flake8-typing-imports==1.12.0
- flake8-typing-imports==1.9.0
- flake8-docstrings==1.5.0
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.0.1
rev: v2.6.0
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src', --py37-plus]
@@ -67,10 +59,8 @@ repos:
- attrs>=19.2.0
- packaging
- tomli
- types-atomicwrites
- types-pkg_resources
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
# on <3.11
- exceptiongroup>=1.0.0rc8
- repo: local
hooks:
- id: rst
@@ -103,7 +93,7 @@ repos:
types: [python]
- id: py-path-deprecated
name: py.path usage is deprecated
exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py|src/_pytest/legacypath.py
exclude: docs|src/_pytest/deprecated.py|testing/deprecated_test.py
language: pygrep
entry: \bpy\.path\.local
types: [python]
+1 -15
View File
@@ -15,7 +15,6 @@ Alan Velasco
Alexander Johnson
Alexander King
Alexei Kozlenok
Alice Purcell
Allan Feldman
Aly Sivji
Amir Elkess
@@ -27,6 +26,7 @@ Andrea Cimatoribus
Andreas Motl
Andreas Zeidler
Andrew Shapton
Andrew Svetlov
Andrey Paramonov
Andrzej Klajnert
Andrzej Ostrowski
@@ -84,7 +84,6 @@ Damian Skrzypczak
Daniel Grana
Daniel Hahler
Daniel Nuri
Daniel Sánchez Castelló
Daniel Wandschneider
Daniele Procida
Danielle Jenkins
@@ -156,7 +155,6 @@ Jaap Broekhuizen
Jakob van Santen
Jakub Mitoraj
James Bourbeau
James Frost
Jan Balster
Janne Vanhala
Jason R. Coombs
@@ -166,7 +164,6 @@ Jeff Rackauckas
Jeff Widman
Jenni Rinker
John Eddie Ayson
John Litborn
John Towler
Jon Sonesen
Jonas Obrist
@@ -188,12 +185,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
Kostis Anagnostopoulos
Kristoffer Nordström
Kyle Altendorf
@@ -251,7 +245,6 @@ Nicholas Murphy
Niclas Olofsson
Nicolas Delaby
Nikolay Kondratyev
Nipunn Koorapati
Olga Matoula
Oleg Pidsadnyi
Oleg Sushchenko
@@ -262,8 +255,6 @@ Ondřej Súkup
Oscar Benjamin
Parth Patel
Patrick Hayes
Paul Müller
Paul Reece
Pauli Virtanen
Pavel Karateev
Paweł Adamczak
@@ -309,7 +300,6 @@ Seth Junot
Shantanu Jain
Shubham Adep
Simon Gomizelj
Simon Holesch
Simon Kerr
Skylar Downes
Srinivas Reddy Thatiparthy
@@ -327,7 +317,6 @@ Taneli Hukkinen
Tanvi Mehta
Tarcisio Fischer
Tareq Alayan
Tatiana Ovary
Ted Xiao
Terje Runde
Thomas Grainger
@@ -338,14 +327,12 @@ 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
@@ -366,6 +353,5 @@ Yoav Caspi
Yuval Shimon
Zac Hatfield-Dodds
Zachary Kneupper
Zachary OBrien
Zoltán Máté
Zsolt Cserna
+1 -3
View File
@@ -50,8 +50,6 @@ 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.
@@ -223,7 +221,7 @@ changes you want to review and merge. Pull requests are stored on
Once you send a pull request, we can discuss its potential modifications and
even add more commits to it later on. There's an excellent tutorial on how Pull
Requests work in the
`GitHub Help Center <https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests>`_.
`GitHub Help Center <https://help.github.com/articles/using-pull-requests/>`_.
Here is a simple overview, with pytest-specific bits:
+2 -2
View File
@@ -20,8 +20,8 @@
:target: https://codecov.io/gh/pytest-dev/pytest
:alt: Code coverage Status
.. image:: https://github.com/pytest-dev/pytest/workflows/test/badge.svg
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest
.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain
.. 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
+7 -9
View File
@@ -37,7 +37,7 @@ breaking changes or new features.
For a new minor release, first create a new maintenance branch from ``main``::
git fetch upstream
git fetch --all
git branch 7.1.x upstream/main
git push upstream 7.1.x
@@ -63,7 +63,7 @@ Major releases
1. Create a new maintenance branch from ``main``::
git fetch upstream
git fetch --all
git branch 8.0.x upstream/main
git push upstream 8.0.x
@@ -136,17 +136,17 @@ Both automatic and manual processes described above follow the same steps from t
#. After all tests pass and the PR has been approved, tag the release commit
in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI::
git fetch upstream
git fetch --all
git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH
git push upstream MAJOR.MINOR.PATCH
git push git@github.com:pytest-dev/pytest.git MAJOR.MINOR.PATCH
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
#. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch.
#. Merge the PR.
#. Cherry-pick the CHANGELOG / announce files to the ``main`` branch::
git fetch upstream
git fetch --all --prune
git checkout upstream/main -b cherry-pick-release
git cherry-pick -x -m1 upstream/MAJOR.MINOR.x
@@ -158,9 +158,7 @@ Both automatic and manual processes described above follow the same steps from t
git checkout main
git pull
git tag MAJOR.{MINOR+1}.0.dev0
git push upstream MAJOR.{MINOR+1}.0.dev0
#. For major and minor releases, change the default version in the `Read the Docs Settings <https://readthedocs.org/dashboard/pytest/advanced/>`_ to the new branch.
git push git@github.com:pytest-dev/pytest.git MAJOR.{MINOR+1}.0.dev0
#. Send an email announcement with the contents from::
-1
View File
@@ -1 +0,0 @@
The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.
+15
View File
@@ -0,0 +1,15 @@
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
View File
@@ -1 +0,0 @@
Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.
+1
View File
@@ -0,0 +1 @@
Pytest will now avoid specialized assert formatting when it is detected that the default __eq__ is overridden
+1
View File
@@ -0,0 +1 @@
Fixed error message prints function decorators when using assert in Python 3.8 and above.
+1
View File
@@ -0,0 +1 @@
Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``).
+1
View File
@@ -0,0 +1 @@
Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation.
+1
View File
@@ -0,0 +1 @@
Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
+10
View File
@@ -0,0 +1,10 @@
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).
+2
View File
@@ -0,0 +1,2 @@
Clarify where the configuration files are located. To avoid confusions documentation mentions
that configuration file is located in the root of the repository.
+9
View File
@@ -0,0 +1,9 @@
Fixture types are now exported so the may be used in pytest plugin hooks.
The newly-exported types are:
- ``pytest.FixtureDef`` for :class:`FixtureDef <pytest.FixtureDef>`
- ``pytest.SubRequest`` for ``_pytest.fixtures.SubRequest`` (derived from :class:`FixtureRequest <pytest.FixtureRequest`).
They are used by ``pytest_fixture_setup`` and ``pytest_fixture_post_finalizer`` hook definitions.
+3
View File
@@ -17,6 +17,7 @@
<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>
@@ -29,3 +30,5 @@
{%- endif %}
<hr>
<a href="{{ pathto('genindex') }}">Index</a>
<hr>
-6
View File
@@ -6,12 +6,6 @@ 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
release-6.2.5
release-6.2.4
-74
View File
@@ -1,74 +0,0 @@
pytest-7.0.0
=======================================
The pytest team is proud to announce the 7.0.0 release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Adam J. Stewart
* Alexander King
* Amin Alaee
* Andrew Neitsch
* Anthony Sottile
* Ben Davies
* Bernát Gábor
* Brian Okken
* Bruno Oliveira
* Cristian Vera
* Dan Alvizu
* David Szotten
* Eddie
* Emmanuel Arias
* Emmanuel Meric de Bellefon
* Eric Liu
* Florian Bruhin
* GergelyKalmar
* Graeme Smecher
* Harshna
* Hugo van Kemenade
* Jakub Kulík
* James Myatt
* Jeff Rasley
* Kale Kundert
* Kian Meng, Ang
* Miro Hrončok
* Naveen-Pratap
* Oleg Höfling
* Olga Matoula
* Ran Benita
* Ronny Pfannschmidt
* Simon K
* Srip
* Sören Wegener
* Taneli Hukkinen
* Terje Runde
* Thomas Grainger
* Thomas Hisch
* William Jamir Silva
* Yuval Shimon
* Zac Hatfield-Dodds
* andrewdotn
* denivyruck
* ericluoliu
* oleg.hoefling
* symonk
* ziebam
* Éloi Rivard
* Éric
Happy testing,
The pytest Development Team
-20
View File
@@ -1,20 +0,0 @@
pytest-7.0.1
=======================================
pytest 7.0.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:
* Anthony Sottile
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team
-48
View File
@@ -1,48 +0,0 @@
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
View File
@@ -1,18 +0,0 @@
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
View File
@@ -1,23 +0,0 @@
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
View File
@@ -1,28 +0,0 @@
pytest-7.1.3
=======================================
pytest 7.1.3 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Anthony Sottile
* Bruno Oliveira
* Gergely Kalmár
* Nipunn Koorapati
* Pax
* Sviatoslav Sydorenko
* Tim Hoffmann
* Tony Narlock
* Wolfremium
* Zach OBrien
* aizpurua23a
Happy testing,
The pytest Development Team
-15
View File
@@ -77,18 +77,3 @@ 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+
============== ===================
+22 -86
View File
@@ -40,86 +40,32 @@ 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.
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
capsysbinary -- .../_pytest/capture.py:895
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()``
method calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``bytes`` objects.
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_output(capsysbinary):
print("hello")
captured = capsysbinary.readouterr()
assert captured.out == b"hello\n"
capfd -- .../_pytest/capture.py:934
capfd -- .../_pytest/capture.py:912
Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_system_echo(capfd):
os.system('echo "hello"')
captured = capfd.readouterr()
assert captured.out == "hello\n"
capfdbinary -- .../_pytest/capture.py:962
capfdbinary -- .../_pytest/capture.py:929
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``byte`` objects.
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_system_echo(capfdbinary):
os.system('echo "hello"')
captured = capfdbinary.readouterr()
assert captured.out == b"hello\n"
doctest_namespace [session scope] -- .../_pytest/doctest.py:735
doctest_namespace [session scope] -- .../_pytest/doctest.py:731
Fixture that returns a :py:class:`dict` that will be injected into the
namespace of doctests.
Usually this fixture is used in conjunction with another ``autouse`` fixture:
.. code-block:: python
@pytest.fixture(autouse=True)
def add_np(doctest_namespace):
doctest_namespace["np"] = numpy
For more details: :ref:`doctest_namespace`.
pytestconfig [session scope] -- .../_pytest/fixtures.py:1344
pytestconfig [session scope] -- .../_pytest/fixtures.py:1365
Session-scoped fixture that returns the session's :class:`pytest.Config`
object.
@@ -171,10 +117,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
:issue:`7767` for details.
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:292
Return a :class:`pytest.TempdirFactory` instance for the test session.
tmpdir -- .../_pytest/legacypath.py:309
tmpdir -- .../_pytest/legacypath.py:299
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
@@ -186,14 +132,9 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
The returned object is a `legacy_path`_ object.
.. note::
These days, it is preferred to use ``tmp_path``.
:ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
caplog -- .../_pytest/logging.py:487
caplog -- .../_pytest/logging.py:483
Access and control log capturing.
Captured logs are available through the following properties/methods::
@@ -207,26 +148,21 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
monkeypatch -- .../_pytest/monkeypatch.py:29
A convenient fixture for monkey-patching.
The fixture provides these methods to modify objects, dictionaries, or
:data:`os.environ`:
The fixture provides these methods to modify objects, dictionaries or
os.environ::
* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=None)
monkeypatch.delenv(name, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone after the requesting test function or
fixture has finished. The ``raising`` parameter determines if a :class:`KeyError`
or :class:`AttributeError` will be raised if the set/deletion operation does not have the
specified target.
To undo modifications done by the fixture in a contained scope,
use :meth:`context() <pytest.MonkeyPatch.context>`.
fixture has finished. The ``raising`` parameter determines if a KeyError
or AttributeError will be raised if the set/deletion operation has no target.
recwarn -- .../_pytest/recwarn.py:29
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
@@ -234,10 +170,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:184
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:183
Return a :class:`pytest.TempPathFactory` instance for the test session.
tmp_path -- .../_pytest/tmpdir.py:199
tmp_path -- .../_pytest/tmpdir.py:198
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
+5 -244
View File
@@ -28,237 +28,6 @@ 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)
=========================
Bug Fixes
---------
- `#9608 <https://github.com/pytest-dev/pytest/issues/9608>`_: Fix invalid importing of ``importlib.readers`` in Python 3.9.
- `#9610 <https://github.com/pytest-dev/pytest/issues/9610>`_: Restore `UnitTestFunction.obj` to return unbound rather than bound method.
Fixes a crash during a failed teardown in unittest TestCases with non-default `__init__`.
Regressed in pytest 7.0.0.
- `#9636 <https://github.com/pytest-dev/pytest/issues/9636>`_: The ``pythonpath`` plugin was renamed to ``python_path``. This avoids a conflict with the ``pytest-pythonpath`` plugin.
- `#9642 <https://github.com/pytest-dev/pytest/issues/9642>`_: Fix running tests by id with ``::`` in the parametrize portion.
- `#9643 <https://github.com/pytest-dev/pytest/issues/9643>`_: Delay issuing a :class:`~pytest.PytestWarning` about diamond inheritance involving :class:`~pytest.Item` and
:class:`~pytest.Collector` so it can be filtered using :ref:`standard warning filters <warnings>`.
pytest 7.0.0 (2022-02-03)
=========================
(**Please see the full set of changes for this release also in the 7.0.0rc1 notes below**)
Deprecations
------------
- `#9488 <https://github.com/pytest-dev/pytest/issues/9488>`_: If custom subclasses of nodes like :class:`pytest.Item` override the
``__init__`` method, they should take ``**kwargs``. See
:ref:`uncooperative-constructors-deprecated` for details.
Note that a deprection warning is only emitted when there is a conflict in the
arguments pytest expected to pass. This deprecation was already part of pytest
7.0.0rc1 but wasn't documented.
Bug Fixes
---------
- `#9355 <https://github.com/pytest-dev/pytest/issues/9355>`_: Fixed error message prints function decorators when using assert in Python 3.8 and above.
- `#9396 <https://github.com/pytest-dev/pytest/issues/9396>`_: Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``).
Improved Documentation
----------------------
- `#9404 <https://github.com/pytest-dev/pytest/issues/9404>`_: Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation.
- `#9505 <https://github.com/pytest-dev/pytest/issues/9505>`_: Clarify where the configuration files are located. To avoid confusions documentation mentions
that configuration file is located in the root of the repository.
Trivial/Internal Changes
------------------------
- `#9521 <https://github.com/pytest-dev/pytest/issues/9521>`_: Add test coverage to assertion rewrite path.
pytest 7.0.0rc1 (2021-12-06)
============================
@@ -373,8 +142,6 @@ Deprecations
:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
:func:`unittest.skip` in unittest test cases is fully supported.
.. note:: This deprecation has been reverted in pytest 7.1.0.
- `#8315 <https://github.com/pytest-dev/pytest/issues/8315>`_: Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
@@ -2659,8 +2426,7 @@ Important
This release is a Python3.5+ only release.
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>`_.
For more details, see our :std:doc:`Python 2.7 and 3.4 support plan <py27-py34-deprecation>`.
Removals
--------
@@ -2884,11 +2650,7 @@ 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
`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.
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.
Trivial/Internal Changes
------------------------
@@ -3060,8 +2822,7 @@ Important
The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**.
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>`_.
For more details, see our :std:doc:`Python 2.7 and 3.4 support plan <py27-py34-deprecation>`.
Features
@@ -6229,7 +5990,7 @@ Bug Fixes
Thanks :user:`adborden` for the report and :user:`nicoddemus` for the PR.
* Clean up unittest TestCase objects after tests are complete (:issue:`1649`).
Thanks :user:`d-b-w` for the report and PR.
Thanks :user:`d_b_w` for the report and PR.
3.0.3 (2016-09-28)
@@ -6244,7 +6005,7 @@ Bug Fixes
Thanks :user:`nicoddemus` for the PR.
* Fix pkg_resources import error in Jython projects (:issue:`1853`).
Thanks :user:`raquelalegre` for the PR.
Thanks :user:`raquel-ucl` for the PR.
* Got rid of ``AttributeError: 'Module' object has no attribute '_obj'`` exception
in Python 3 (:issue:`1944`).
+7 -8
View File
@@ -247,7 +247,7 @@ html_sidebars = {
html_domain_indices = True
# If false, no index is generated.
html_use_index = False
html_use_index = True
# If true, the index is split into individual pages for each letter.
# html_split_index = False
@@ -272,9 +272,6 @@ html_show_sourcelink = False
# Output file base name for HTML help builder.
htmlhelp_basename = "pytestdoc"
# The base URL which points to the root of the HTML documentation. It is used
# to indicate the location of document using the canonical link relation (#12363).
html_baseurl = "https://docs.pytest.org/en/stable/"
# -- Options for LaTeX output --------------------------------------------------
@@ -323,9 +320,7 @@ latex_domain_indices = False
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)
]
man_pages = [("usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1)]
# -- Options for Epub output ---------------------------------------------------
@@ -387,6 +382,7 @@ texinfo_documents = [
]
# Example configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
"pluggy": ("https://pluggy.readthedocs.io/en/stable", None),
"python": ("https://docs.python.org/3", None),
@@ -394,8 +390,11 @@ intersphinx_mapping = {
"pip": ("https://pip.pypa.io/en/stable", None),
"tox": ("https://tox.wiki/en/stable", None),
"virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
"django": (
"http://docs.djangoproject.com/en/stable",
"http://docs.djangoproject.com/en/stable/_objects",
),
"setuptools": ("https://setuptools.pypa.io/en/stable", None),
"packaging": ("https://packaging.python.org/en/latest", None),
}
+1
View File
@@ -85,6 +85,7 @@ Further topics
backwards-compatibility
deprecations
py27-py34-deprecation
contributing
development_guide
+15 -40
View File
@@ -16,7 +16,7 @@ Deprecated Features
-------------------
Below is a complete list of all pytest features which are considered deprecated. Using those features will issue
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
:class:`PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
.. _instance-collector-deprecation:
@@ -56,10 +56,6 @@ Plugins which implement custom items and collectors are encouraged to replace
``fspath`` parameters (``py.path.local``) with ``path`` parameters
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
If possible, plugins with custom items should use :ref:`cooperative
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
arguments they only pass on to the superclass.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
new attribute being ``path``) is **the opposite** of the situation for
@@ -195,40 +191,6 @@ Instead, a separate collector node should be used, which collects the item. See
.. _example pr fixing inheritance: https://github.com/asmeurer/pytest-flakes/pull/40/files
.. _uncooperative-constructors-deprecated:
Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
If custom subclasses of nodes like :class:`pytest.Item` override the
``__init__`` method, they should take ``**kwargs``. Thus,
.. code-block:: python
class CustomItem(pytest.Item):
def __init__(self, name, parent, additional_arg):
super().__init__(name, parent)
self.additional_arg = additional_arg
should be turned into:
.. code-block:: python
class CustomItem(pytest.Item):
def __init__(self, *, additional_arg, **kwargs):
super().__init__(**kwargs)
self.additional_arg = additional_arg
to avoid hard-coding the arguments pytest can pass to the superclass.
See :ref:`non-python tests` for a full example.
For cases without conflicts, no deprecation warning is emitted. For cases with
conflicts (such as :class:`pytest.File` now taking ``path`` instead of
``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
deprecation warning is now raised.
Backward compatibilities in ``Parser.addoption``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -241,6 +203,19 @@ scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
Raising ``unittest.SkipTest`` during collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
Raising :class:`unittest.SkipTest` to skip collection of tests during the
pytest collection phase is deprecated. Use :func:`pytest.skip` instead.
Note: This deprecation only relates to using `unittest.SkipTest` during test
collection. You are probably not doing that. Ordinary usage of
:class:`unittest.SkipTest` / :meth:`unittest.TestCase.skipTest` /
:func:`unittest.skip` in unittest test cases is fully supported.
Using ``pytest.warns(None)``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -301,7 +276,7 @@ The ``pytest_warning_captured`` hook
This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
Use the ``pytest_warning_recorded`` hook instead, which replaces the ``item`` parameter
Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter
by a ``nodeid`` parameter.
@@ -1,56 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="112" height="682">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class {
fill: #0e84b5;
}
path, line {
stroke: black;
stroke-width: 2;
fill: none;
}
rect.autouse {
fill: #ca7f3d;
}
</style>
<line x1="56" x2="56" y1="681" y2="26" />
<ellipse class="fixture" rx="50" ry="25" cx="56" cy="26" />
<text x="56" y="26">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="96" />
<text x="56" y="96">a</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="166" />
<text x="56" y="166">b</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="236" />
<text x="56" y="236">c</text>
<rect class="autouse" width="112" height="40" x="0" y="286" />
<text x="56" y="306">autouse</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="376" />
<text x="56" y="376">d</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="446" />
<text x="56" y="446">e</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="516" />
<text x="56" y="516">f</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="586" />
<text x="56" y="586">g</text>
<rect class="test" width="110" height="50" x="1" y="631" />
<text x="56" y="656">test_order</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

+1 -1
View File
@@ -9,7 +9,7 @@ Working with non-python tests
A basic example for specifying tests in Yaml files
--------------------------------------------------------------
.. _`pytest-yamlwsgi`: https://pypi.org/project/pytest-yamlwsgi/
.. _`pytest-yamlwsgi`: http://bitbucket.org/aafshar/pytest-yamlwsgi/src/tip/pytest_yamlwsgi.py
Here is an example ``conftest.py`` (extracted from Ali Afshar's special purpose `pytest-yamlwsgi`_ plugin). This ``conftest.py`` will collect ``test*.yaml`` files and will execute the yaml-formatted content as custom tests:
+2 -2
View File
@@ -18,8 +18,8 @@ class YamlFile(pytest.File):
class YamlItem(pytest.Item):
def __init__(self, *, spec, **kwargs):
super().__init__(**kwargs)
def __init__(self, name, parent, spec):
super().__init__(name, parent)
self.spec = spec
def runtest(self):
+27 -5
View File
@@ -657,17 +657,20 @@ 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 may be helpful to use ``nullcontext`` as a complement to ``raises``.
For example:
It is helpful to define a no-op context manager ``does_not_raise`` to serve
as a complement to ``raises``. For example:
.. code-block:: python
from contextlib import nullcontext as does_not_raise
from contextlib import contextmanager
import pytest
@contextmanager
def does_not_raise():
yield
@pytest.mark.parametrize(
"example_input,expectation",
[
@@ -684,3 +687,22 @@ 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
+3 -3
View File
@@ -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 more diff
E Use -v to get the full 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 more diff
E Use -v to get the full 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 more diff
E Use -v to get the full diff
failure_demo.py:77: AssertionError
_________________ TestSpecialisedExplanations.test_in_list _________________
+1 -1
View File
@@ -94,7 +94,7 @@ Mark Lapierre discusses the `Pros and Cons of Quarantined Tests <https://dev.to/
CI tools that rerun on failure
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/previous-versions/azure/devops/2017/dec-11-vsts?view=tfs-2017#identify-flaky-tests>`_ and rerun failed tests.
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/azure/devops/release-notes/2017/dec-11-vsts#identify-flaky-tests>`_ and rerun failed tests.
+91 -80
View File
@@ -12,26 +12,41 @@ For development, we recommend you use :mod:`venv` for virtual environments and
as well as the ``pytest`` package itself.
This ensures your code and dependencies are isolated from your system Python installation.
Create a ``pyproject.toml`` file in the root of your repository as described in
:doc:`packaging:tutorials/packaging-projects`.
The first few lines should look like this:
Next, place a ``pyproject.toml`` file in the root of your package:
.. code-block:: toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "PACKAGENAME"
and a ``setup.cfg`` file containing your package's metadata with the following minimum content:
.. code-block:: ini
[metadata]
name = PACKAGENAME
[options]
packages = find:
where ``PACKAGENAME`` is the name of your package.
.. note::
If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file:
.. code-block:: python
from setuptools import setup
setup()
You can then install your package in "editable" mode by running from the same directory:
.. code-block:: bash
pip install -e .
pip install -e .
which lets you change your source code (both tests and application) and rerun tests at will.
@@ -74,11 +89,11 @@ to keep tests separate from actual application code (often a good idea):
.. code-block:: text
pyproject.toml
src/
mypkg/
__init__.py
app.py
view.py
setup.cfg
mypkg/
__init__.py
app.py
view.py
tests/
test_app.py
test_view.py
@@ -97,28 +112,73 @@ This has the following benefits:
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
``python -m pytest``.
For new projects, we recommend to use ``importlib`` :ref:`import mode <import-modes>`
(see which-import-mode_ for a detailed explanation).
To this end, add the following to your ``pyproject.toml``:
Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>`
(which is the default): your test files must have **unique names**, because
``pytest`` will import them as *top-level* modules since there are no packages
to derive a full package name from. In other words, the test files in the example above will
be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
``sys.path``.
.. code-block:: toml
If you need to have test modules with the same name, you might add ``__init__.py`` files to your
``tests`` folder and subfolders, changing them to packages:
[tool.pytest.ini_options]
addopts = [
"--import-mode=importlib",
]
.. code-block:: text
.. _src-layout:
pyproject.toml
setup.cfg
mypkg/
...
tests/
__init__.py
foo/
__init__.py
test_view.py
bar/
__init__.py
test_view.py
Generally, but especially if you use the default import mode ``prepend``,
it is **strongly** suggested to use a ``src`` layout.
Here, your application root package resides in a sub-directory of your root,
i.e. ``src/mypkg/`` instead of ``mypkg``.
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing
you to have modules with the same name. But now this introduces a subtle problem: in order to load
the test modules from the ``tests`` directory, pytest prepends the root of the repository to
``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
This layout prevents a lot of common pitfalls and has many benefits,
which are better explained in this excellent `blog post`_ by Ionel Cristian Mărieș.
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
because you want to test the *installed* version of your package, not the local code from the repository.
.. _blog post: https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>
.. _`src-layout`:
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
sub-directory of your root:
.. code-block:: text
pyproject.toml
setup.cfg
src/
mypkg/
__init__.py
app.py
view.py
tests/
__init__.py
foo/
__init__.py
test_view.py
bar/
__init__.py
test_view.py
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
.. note::
The new ``--import-mode=importlib`` (see :ref:`import-modes`) doesn't 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.
The ``src`` directory layout is still strongly recommended however.
Tests as part of application code
@@ -131,7 +191,8 @@ want to distribute them along with your application:
.. code-block:: text
pyproject.toml
[src/]mypkg/
setup.cfg
mypkg/
__init__.py
app.py
view.py
@@ -193,56 +254,6 @@ Note that this layout also works in conjunction with the ``src`` layout mentione
much less surprising.
.. _which-import-mode:
Choosing an import mode
^^^^^^^^^^^^^^^^^^^^^^^
For historical reasons, pytest defaults to the ``prepend`` :ref:`import mode <import-modes>`
instead of the ``importlib`` import mode we recommend for new projects.
The reason lies in the way the ``prepend`` mode works:
Since there are no packages to derive a full package name from,
``pytest`` will import your test files as *top-level* modules.
The test files in the first example (:ref:`src layout <src-layout>`) would be imported as
``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``.
This results in a drawback compared to the import mode ``importlib``:
your test files must have **unique names**.
If you need to have test modules with the same name,
as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders,
changing them to packages:
.. code-block:: text
pyproject.toml
mypkg/
...
tests/
__init__.py
foo/
__init__.py
test_view.py
bar/
__init__.py
test_view.py
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``,
allowing you to have modules with the same name.
But now this introduces a subtle problem:
in order to load the test modules from the ``tests`` directory,
pytest prepends the root of the repository to ``sys.path``,
which adds the side-effect that now ``mypkg`` is also importable.
This is problematic if you are using a tool like tox_ to test your package in a virtual environment,
because you want to test the *installed* version of your package,
not the local code from the repository.
The ``importlib`` import mode does not have any of the drawbacks above,
because ``sys.path`` is not changed when importing test modules.
.. _`buildout`: http://www.buildout.org/en/latest/
.. _`use tox`:
+3 -12
View File
@@ -45,19 +45,10 @@ 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.
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.
For this reason this doesn't require test module names to be unique, but also makes test
modules non-importable by each other.
We intend to make ``importlib`` the default in future releases, depending on feedback.
``prepend`` and ``append`` import modes scenarios
-------------------------------------------------
+1 -1
View File
@@ -22,7 +22,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
pytest 7.1.3
pytest 7.0.0rc1
.. _`simpletest`:
+1 -1
View File
@@ -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 more diff
E Use -v to get the full diff
test_assert2.py:4: AssertionError
========================= short test summary info ==========================
+1 -1
View File
@@ -5,7 +5,7 @@ How to set up bash completion
=============================
When using bash as your shell, ``pytest`` can use argcomplete
(https://kislyuk.github.io/argcomplete/) for auto-completion.
(https://argcomplete.readthedocs.io/) for auto-completion.
For this ``argcomplete`` needs to be installed **and** enabled.
Install argcomplete using:
+2 -19
View File
@@ -358,12 +358,12 @@ Additional use cases of warnings in tests
Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them:
- To ensure that **at least one** warning is emitted, use:
- To ensure that **any** warning is emitted, use:
.. code-block:: python
with pytest.warns():
...
pass
- To ensure that **no** warnings are emitted, use:
@@ -371,7 +371,6 @@ Here are some use cases involving warnings that often come up in tests, and sugg
with warnings.catch_warnings():
warnings.simplefilter("error")
...
- To suppress warnings, use:
@@ -379,7 +378,6 @@ Here are some use cases involving warnings that often come up in tests, and sugg
with warnings.catch_warnings():
warnings.simplefilter("ignore")
...
.. _custom_failure_messages:
@@ -441,18 +439,3 @@ Please read our :ref:`backwards-compatibility` to learn how we proceed about dep
features.
The full list of warnings is listed in :ref:`the reference documentation <warnings ref>`.
.. _`resource-warnings`:
Resource Warnings
-----------------
Additional information of the source of a :class:`ResourceWarning` can be obtained when captured by pytest if
:mod:`tracemalloc` module is enabled.
One convenient way to enable :mod:`tracemalloc` when running tests is to set the :envvar:`PYTHONTRACEMALLOC` to a large
enough number of frames (say ``20``, but that number is application dependent).
For more information, consult the `Python Development Mode <https://docs.python.org/3/library/devmode.html>`__
section in the Python documentation.
+4 -7
View File
@@ -126,17 +126,14 @@ 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. 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)``::
the precision you have written in the expected doctest output. For example,
the following output would only need to match to 2 decimal places::
>>> math.pi
3.14
If you wrote ``3.1416`` then the actual output would need to match to
approximately 4 decimal places; and so on.
If you wrote ``3.1416`` then the actual output would need to match to 4
decimal places; and so on.
This avoids false positives caused by limited floating-point precision, like
this::
+14 -87
View File
@@ -736,87 +736,6 @@ does offer some nuances for when you're in a pinch.
. [100%]
1 passed in 0.12s
Note on finalizer order
""""""""""""""""""""""""
Finalizers are executed in a first-in-last-out order.
For yield fixtures, the first teardown code to run is from the right-most fixture, i.e. the last test parameter.
.. code-block:: python
# content of test_finalizers.py
import pytest
def test_bar(fix_w_yield1, fix_w_yield2):
print("test_bar")
@pytest.fixture
def fix_w_yield1():
yield
print("after_yield_1")
@pytest.fixture
def fix_w_yield2():
yield
print("after_yield_2")
.. code-block:: pytest
$ pytest -s test_finalizers.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_finalizers.py test_bar
.after_yield_2
after_yield_1
============================ 1 passed in 0.12s =============================
For finalizers, the first fixture to run is last call to `request.addfinalizer`.
.. code-block:: python
# content of test_finalizers.py
from functools import partial
import pytest
@pytest.fixture
def fix_w_finalizers(request):
request.addfinalizer(partial(print, "finalizer_2"))
request.addfinalizer(partial(print, "finalizer_1"))
def test_bar(fix_w_finalizers):
print("test_bar")
.. code-block:: pytest
$ pytest -s test_finalizers.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 1 item
test_finalizers.py test_bar
.finalizer_1
finalizer_2
============================ 1 passed in 0.12s =============================
This is so because yield fixtures use `addfinalizer` behind the scenes: when the fixture executes, `addfinalizer` registers a function that resumes the generator, which in turn calls the teardown code.
.. _`safe teardowns`:
Safe teardowns
@@ -1413,15 +1332,13 @@ Running the above tests results in the following test IDs being used:
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project
collected 12 items
collected 11 items
<Module test_anothersmtp.py>
<Function test_showhelo[smtp.gmail.com]>
<Function test_showhelo[mail.python.org]>
<Module test_emaillib.py>
<Function test_email_received>
<Module test_finalizers.py>
<Function test_bar>
<Module test_ids.py>
<Function test_a[spam]>
<Function test_a[ham]>
@@ -1433,7 +1350,7 @@ Running the above tests results in the following test IDs being used:
<Function test_ehlo[mail.python.org]>
<Function test_noop[mail.python.org]>
======================= 12 tests collected in 0.12s ========================
======================= 11 tests collected in 0.12s ========================
.. _`fixture-parametrize-marks`:
@@ -1767,6 +1684,8 @@ Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@@ -1781,6 +1700,8 @@ Given the tests file structure is:
assert username == 'username'
subfolder/
__init__.py
conftest.py
# content of tests/subfolder/conftest.py
import pytest
@@ -1789,8 +1710,8 @@ Given the tests file structure is:
def username(username):
return 'overridden-' + username
test_something_else.py
# content of tests/subfolder/test_something_else.py
test_something.py
# content of tests/subfolder/test_something.py
def test_username(username):
assert username == 'overridden-username'
@@ -1806,6 +1727,8 @@ Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@@ -1847,6 +1770,8 @@ Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
@@ -1883,6 +1808,8 @@ Given the tests file structure is:
::
tests/
__init__.py
conftest.py
# content of tests/conftest.py
import pytest
+5 -6
View File
@@ -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 or numeric values as seen in
:ref:`logging's documentation <python:levels>`.
accepts the logging level names as seen in python's documentation or an integer
as the logging level num.
Additionally, you can also specify ``--log-cli-format`` and
``--log-cli-date-format`` which mirror and default to ``--log-format`` and
@@ -198,12 +198,11 @@ 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 or numeric
values as seen in :ref:`logging's documentation <python:levels>`.
``--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.
Additionally, you can also specify ``--log-file-format`` and
``--log-file-date-format`` which are equal to ``--log-format`` and
+14 -16
View File
@@ -3,7 +3,7 @@
How to monkeypatch/mock modules and environments
================================================================
.. currentmodule:: pytest
.. currentmodule:: _pytest.monkeypatch
Sometimes tests need to invoke functionality which depends
on global settings or which invokes code which cannot be easily
@@ -14,16 +14,17 @@ environment variable, or to modify ``sys.path`` for importing.
The ``monkeypatch`` fixture provides these helper methods for safely patching and mocking
functionality in tests:
* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
.. code-block:: python
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.setattr("somemodule.obj.name", value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=None)
monkeypatch.delenv(name, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone after the requesting
test function or fixture has finished. The ``raising``
@@ -54,16 +55,13 @@ 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/
Monkeypatching functions
------------------------
Simple example: monkeypatching functions
----------------------------------------
Consider a scenario where you are working with user directories. In the context of
testing, you do not want your test to depend on the running user. ``monkeypatch``
@@ -438,7 +436,7 @@ separate fixtures for each potential mock and reference them in the needed tests
_ = app.create_connection_string()
.. currentmodule:: pytest
.. currentmodule:: _pytest.monkeypatch
API Reference
-------------
+2 -2
View File
@@ -23,8 +23,8 @@ make use of pytest's capabilities.
Supported nose Idioms
----------------------
* ``setup()`` and ``teardown()`` at module/class/method level: any function or method called ``setup`` will be called during the setup phase for each test, same for ``teardown``.
* ``SkipTest`` exceptions and markers
* setup and teardown at module/class/method level
* SkipTest exceptions and markers
* setup/teardown decorators
* ``__test__`` attribute on modules/classes/functions
* general usage of nose utilities
+2 -2
View File
@@ -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 more diff
E Use -v to get the full 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 more diff
E Use -v to get the full diff
test_verbosity_example.py:14: AssertionError
___________________________ test_long_text_fail ____________________________
+1 -4
View File
@@ -21,7 +21,7 @@ there is no need to activate it.
Here is a little annotated list for some popular plugins:
* :pypi:`pytest-django`: write tests
for `django <https://docs.djangoproject.com/>`_ apps, using pytest integration.
for :std:doc:`django <django:index>` apps, using pytest integration.
* :pypi:`pytest-twisted`: write tests
for `twisted <https://twistedmatrix.com/>`_ apps, starting a reactor and
@@ -51,9 +51,6 @@ Here is a little annotated list for some popular plugins:
* :pypi:`pytest-flakes`:
check source code with pyflakes.
* :pypi:`allure-pytest`:
report test results via `allure-framework <https://github.com/allure-framework/>`_.
To see a complete list of all plugins with their latest testing
status against different pytest and Python versions, please visit
:ref:`plugin-list`.
+2 -19
View File
@@ -104,21 +104,8 @@ 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.
.. note::
These days, it is preferred to use ``tmp_path`` and ``tmp_path_factory``.
In order to help modernize old code bases, one can run pytest with the legacypath
plugin disabled:
.. code-block:: bash
pytest -p no:legacypath
This will trigger errors on tests using the legacy paths.
It can also be permanently set as part of the :confval:`addopts` parameter in the
config file.
rather than standard :class:`pathlib.Path` objects. These days, prefer to
use ``tmp_path`` and ``tmp_path_factory``.
See :fixture:`tmpdir <tmpdir>` :fixture:`tmpdir_factory <tmpdir_factory>`
API for details.
@@ -134,10 +121,6 @@ the system temporary directory. The base name will be ``pytest-NUM`` where
``NUM`` will be incremented with each test run. Moreover, entries older
than 3 temporary directories will be removed.
The number of entries currently cannot be changed, but using the ``--basetemp``
option will remove the directory before every run, effectively meaning the temporary directories
of only the most recent run will be kept.
You can override the default temporary directory setting like this:
.. code-block:: bash
+1 -4
View File
@@ -27,15 +27,12 @@ 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
-----------------------
+19 -3
View File
@@ -2,10 +2,9 @@
.. sidebar:: Next Open Trainings
- Professionelles Testen für Python mit pytest, part of `enterPy <https://www.enterpy.de/>`__ (German), `October 28th <https://www.enterpy.de/veranstaltung-15409-se-0-professionelles-testen-fuer-python-mit-pytest.html>`__ (sold out) and `November 4th <https://www.enterpy.de/veranstaltung-15557-se-0-professionelles-testen-fuer-python-mit-pytest-zusatztermin.html>`__, online
- `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
- `Professional Testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, February 1st to 3rd, 2022, Leipzig (Germany) and remote.
Also see :doc:`previous talks and blogposts <talks>`.
Also see `previous talks and blogposts <talks.html>`_.
.. _features:
@@ -22,6 +21,8 @@ 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
---------------
@@ -97,6 +98,11 @@ 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
--------------
@@ -129,3 +135,13 @@ 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
+99
View File
@@ -0,0 +1,99 @@
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.
-3
View File
@@ -401,9 +401,6 @@ the graph would look like this:
Because ``c`` can now be put above ``d`` in the graph, pytest can once again
linearize the graph to this:
.. image:: /example/fixtures/test_fixtures_order_autouse_flat.*
:align: center
In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as
well.
+3 -3
View File
@@ -8,8 +8,8 @@ Reference guides
.. toctree::
:maxdepth: 1
reference
fixtures
customize
exit-codes
plugin_list
customize
reference
exit-codes
File diff suppressed because it is too large Load Diff
+123 -63
View File
@@ -9,39 +9,6 @@ This page contains the full reference to pytest's API.
:depth: 3
:local:
Constants
---------
pytest.__version__
~~~~~~~~~~~~~~~~~~
The current pytest version, as a string::
>>> import pytest
>>> pytest.__version__
'7.0.0'
.. _`version-tuple`:
pytest.version_tuple
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 7.0
The current pytest version, as a tuple::
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)
For pre-releases, the last component will be a string with the prerelease version::
>>> import pytest
>>> pytest.version_tuple
(7, 0, '0rc1')
Functions
---------
@@ -92,7 +59,7 @@ pytest.param
pytest.raises
~~~~~~~~~~~~~
**Tutorial**: :ref:`assertraises`
**Tutorial**: :ref:`assertraises`.
.. autofunction:: pytest.raises(expected_exception: Exception [, *, match])
:with: excinfo
@@ -100,7 +67,7 @@ pytest.raises
pytest.deprecated_call
~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`ensuring_function_triggers`
**Tutorial**: :ref:`ensuring_function_triggers`.
.. autofunction:: pytest.deprecated_call()
:with:
@@ -108,7 +75,7 @@ pytest.deprecated_call
pytest.register_assert_rewrite
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`assertion-rewriting`
**Tutorial**: :ref:`assertion-rewriting`.
.. autofunction:: pytest.register_assert_rewrite
@@ -123,7 +90,7 @@ pytest.warns
pytest.freeze_includes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`freezing-pytest`
**Tutorial**: :ref:`freezing-pytest`.
.. autofunction:: pytest.freeze_includes
@@ -143,7 +110,7 @@ fixtures or plugins.
pytest.mark.filterwarnings
~~~~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`filterwarnings`
**Tutorial**: :ref:`filterwarnings`.
Add warning filters to marked test items.
@@ -169,7 +136,7 @@ Add warning filters to marked test items.
pytest.mark.parametrize
~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`parametrize`
:ref:`parametrize`.
This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see there.
@@ -179,7 +146,7 @@ This mark has the same signature as :py:meth:`pytest.Metafunc.parametrize`; see
pytest.mark.skip
~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`skip`
:ref:`skip`.
Unconditionally skip a test function.
@@ -193,7 +160,7 @@ Unconditionally skip a test function.
pytest.mark.skipif
~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`skipif`
:ref:`skipif`.
Skip a test function if a condition is ``True``.
@@ -209,7 +176,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 +198,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 +212,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 (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test.
Exception subclass 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).
@@ -259,6 +226,37 @@ Marks a test function as *expected to fail*.
a new release of a library fixes a known bug).
pytest.__version__
~~~~~~~~~~~~~~~~~~
The current pytest version, as a string::
>>> import pytest
>>> pytest.__version__
'7.0.0'
.. _`version-tuple`:
pytest.version_tuple
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 7.0
The current pytest version, as a tuple::
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)
For pre-releases, the last component will be a string with the prerelease version::
>>> import pytest
>>> pytest.version_tuple
(7, 0, '0rc1')
Custom marks
~~~~~~~~~~~~
@@ -297,7 +295,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 +336,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,11 +356,22 @@ Under the hood, the cache plugin uses the simple
capsys
~~~~~~
**Tutorial**: :ref:`captures`
: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:
@@ -372,48 +381,93 @@ capsys
capsysbinary
~~~~~~~~~~~~
**Tutorial**: :ref:`captures`
: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
~~~~~~
**Tutorial**: :ref:`captures`
: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
~~~~~~~~~~~~
**Tutorial**: :ref:`captures`
: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
~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`doctest`
: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
~~~~~~~
**Example**: :ref:`request example`
:ref:`request example`.
The ``request`` fixture is a special fixture providing information of the requesting test function.
@@ -434,7 +488,7 @@ pytestconfig
record_property
~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`record_property example`
**Tutorial**: :ref:`record_property example`.
.. autofunction:: _pytest.junitxml.record_property()
@@ -444,7 +498,7 @@ record_property
record_testsuite_property
~~~~~~~~~~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`record_testsuite_property example`
**Tutorial**: :ref:`record_testsuite_property example`.
.. autofunction:: _pytest.junitxml.record_testsuite_property()
@@ -454,7 +508,7 @@ record_testsuite_property
caplog
~~~~~~
**Tutorial**: :ref:`logging`
:ref:`logging`.
.. autofunction:: _pytest.logging.caplog()
:no-auto-options:
@@ -470,7 +524,7 @@ caplog
monkeypatch
~~~~~~~~~~~
**Tutorial**: :ref:`monkeypatching`
:ref:`monkeypatching`.
.. autofunction:: _pytest.monkeypatch.monkeypatch()
:no-auto-options:
@@ -544,13 +598,19 @@ 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
~~~~~~~~
**Tutorial**: :ref:`tmp_path`
:ref:`tmp_path`
.. autofunction:: _pytest.tmpdir.tmp_path()
:no-auto-options:
@@ -561,7 +621,7 @@ tmp_path
tmp_path_factory
~~~~~~~~~~~~~~~~
**Tutorial**: :ref:`tmp_path_factory example`
:ref:`tmp_path_factory example`
.. _`tmp_path_factory factory api`:
@@ -576,7 +636,7 @@ tmp_path_factory
tmpdir
~~~~~~
**Tutorial**: :ref:`tmpdir and tmpdir_factory`
:ref:`tmpdir and tmpdir_factory`
.. autofunction:: _pytest.legacypath.LegacyTmpdirPlugin.tmpdir()
:no-auto-options:
@@ -587,7 +647,7 @@ tmpdir
tmpdir_factory
~~~~~~~~~~~~~~
**Tutorial**: :ref:`tmpdir and tmpdir_factory`
:ref:`tmpdir and tmpdir_factory`
``tmpdir_factory`` is an instance of :class:`~pytest.TempdirFactory`:
@@ -600,7 +660,7 @@ tmpdir_factory
Hooks
-----
**Tutorial**: :ref:`writing-plugins`
:ref:`writing-plugins`.
.. currentmodule:: _pytest.hookspec
@@ -845,7 +905,7 @@ File
FixtureDef
~~~~~~~~~~
.. autoclass:: _pytest.fixtures.FixtureDef()
.. autoclass:: pytest.FixtureDef()
:members:
:show-inheritance:
@@ -1452,7 +1512,7 @@ passed multiple times. The expected format is ``name=value``. For example::
Sets a file name relative to the current working directory where log messages should be written to, in addition
Sets a file name relative to the ``pytest.ini`` file where log messages should be written to, in addition
to the other logging facilities that are active.
.. code-block:: ini
-3
View File
@@ -5,6 +5,3 @@ 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
-7
View File
@@ -11,16 +11,9 @@ Books
- `Python Testing with pytest, by Brian Okken (2017)
<https://pragprog.com/book/bopytest/python-testing-with-pytest>`_.
- `Python Testing with pytest, Second Edition, by Brian Okken (2022)
<https://pragprog.com/titles/bopytest2/python-testing-with-pytest-second-edition>`_.
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
- Webinar: `Simplify Your Tests with Fixtures <https://blog.jetbrains.com/pycharm/2020/08/webinar-recording-simplify-your-tests-with-fixtures-with-oliver-bestwalter/>`_, Oliver Bestwalter, via JetBrains, 2020
-1
View File
@@ -1,5 +1,4 @@
import sys
from distutils.core import setup
if __name__ == "__main__":
+5 -4
View File
@@ -88,14 +88,15 @@ def prepare_release_pr(
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
if is_major:
template_name = "release.major.rst"
elif prerelease:
if prerelease:
template_name = "release.pre.rst"
doc_version = release_branch
elif is_feature_release:
template_name = "release.minor.rst"
doc_version = "" # unused in template
else:
template_name = "release.patch.rst"
doc_version = "" # unused in template
# important to use tox here because we have changed branches, so dependencies
# might have changed as well
@@ -106,7 +107,7 @@ def prepare_release_pr(
"--",
version,
template_name,
release_branch, # doc_version
doc_version,
"--skip-check-links",
]
print("Running", " ".join(cmdline))
-24
View File
@@ -1,24 +0,0 @@
pytest-{version}
=======================================
The pytest team is proud to announce the {version} release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
{contributors}
Happy testing,
The pytest Development Team
+2 -2
View File
@@ -3,8 +3,8 @@ pytest-{version}
The pytest team is proud to announce the {version} release!
This release contains new features, improvements, and bug fixes,
the full list of changes is available in the changelog:
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
+1 -1
View File
@@ -47,8 +47,8 @@ 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"
exceptiongroup>=1.0.0rc8;python_version<"3.11"
importlib-metadata>=0.12;python_version<"3.8"
python_requires = >=3.7
package_dir =
+1 -18
View File
@@ -56,9 +56,6 @@ if TYPE_CHECKING:
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
if sys.version_info[:2] < (3, 11):
from exceptiongroup import BaseExceptionGroup
class Code:
"""Wrapper around Python code objects."""
@@ -926,21 +923,7 @@ class FormattedExcinfo:
while e is not None and id(e) not in seen:
seen.add(id(e))
if excinfo_:
# Fall back to native traceback as a temporary workaround until
# full support for exception groups added to ExceptionInfo.
# See https://github.com/pytest-dev/pytest/issues/9159
if isinstance(e, BaseExceptionGroup):
reprtraceback: Union[
ReprTracebackNative, ReprTraceback
] = ReprTracebackNative(
traceback.format_exception(
type(excinfo_.value),
excinfo_.value,
excinfo_.traceback[0]._rawentry,
)
)
else:
reprtraceback = self.repr_traceback(excinfo_)
reprtraceback = self.repr_traceback(excinfo_)
reprcrash: Optional[ReprFileLocation] = (
excinfo_._getreprcrash() if self.style != "value" else None
)
+51 -31
View File
@@ -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(*parts).with_suffix(".py")
path = PurePath(os.path.sep.join(parts) + ".py")
for pat in self.fnpats:
# if the pattern contains subdirectories ("tests/**.py" for example) we can't bail out based
@@ -273,17 +273,13 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
with open(pathname, "rb") as f:
return f.read()
if sys.version_info >= (3, 10):
if sys.version_info >= (3, 9):
def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore
if sys.version_info < (3, 11):
from importlib.readers import FileReader
else:
from importlib.resources.readers import FileReader
from types import SimpleNamespace
from importlib.readers import FileReader
return FileReader( # type:ignore[no-any-return]
types.SimpleNamespace(path=self._rewritten_names[name])
)
return FileReader(SimpleNamespace(path=self._rewritten_names[name]))
def _write_pyc_fp(
@@ -304,29 +300,53 @@ def _write_pyc_fp(
fp.write(marshal.dumps(co))
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)
except OSError as e:
state.trace(f"error writing pyc file at {proc_pyc}: errno={e.errno}")
return False
if sys.platform == "win32":
from atomicwrites import atomic_write
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 _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:
_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
def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]:
+18 -6
View File
@@ -223,6 +223,8 @@ def _compare_eq_any(left: Any, right: Any, verbose: int = 0) -> List[str]:
explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose)
elif verbose > 0:
explanation = _compare_eq_verbose(left, right)
if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose)
@@ -279,6 +281,18 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]:
return explanation
def _compare_eq_verbose(left: Any, right: Any) -> List[str]:
keepends = True
left_lines = repr(left).splitlines(keepends)
right_lines = repr(right).splitlines(keepends)
explanation: List[str] = []
explanation += ["+" + line for line in left_lines]
explanation += ["-" + line for line in right_lines]
return explanation
def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
"""Move opening/closing parenthesis/bracket to own lines."""
opening = lines[0][:1]
@@ -294,8 +308,8 @@ def _surrounding_parens_on_own_lines(lines: List[str]) -> None:
def _compare_eq_iterable(
left: Iterable[Any], right: Iterable[Any], verbose: int = 0
) -> List[str]:
if verbose <= 0 and not running_on_ci():
return ["Use -v to get more diff"]
if not verbose and not running_on_ci():
return ["Use -v to get the full diff"]
# dynamic import to speedup pytest
import difflib
@@ -437,10 +451,8 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
if not has_default_eq(left):
return []
if isdatacls(left):
import dataclasses
all_fields = dataclasses.fields(left)
fields_to_check = [info.name for info in all_fields if info.compare]
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() 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")]
+4 -49
View File
@@ -876,22 +876,11 @@ class CaptureFixture(Generic[AnyStr]):
@fixture
def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
r"""Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
"""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)
@@ -904,22 +893,11 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
@fixture
def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
r"""Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
"""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)
@@ -932,22 +910,11 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None,
@fixture
def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
r"""Enable text capturing of writes to file descriptors ``1`` and ``2``.
"""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)
@@ -960,23 +927,11 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]:
@fixture
def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, None]:
r"""Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
"""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)
+11 -18
View File
@@ -254,7 +254,7 @@ default_plugins = essential_plugins + (
"warnings",
"logging",
"reports",
"python_path",
"pythonpath",
*(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
"faulthandler",
)
@@ -330,14 +330,6 @@ def _prepareconfig(
raise
def _get_directory(path: Path) -> Path:
"""Get the directory of a path - itself if already a directory."""
if path.is_file():
return path.parent
else:
return path
@final
class PytestPluginManager(PluginManager):
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
@@ -365,11 +357,6 @@ class PytestPluginManager(PluginManager):
# If set, conftest loading is skipped.
self._noconftest = False
# _getconftestmodules()'s call to _get_directory() causes a stat
# storm when it's called potentially thousands of times in a test
# session (#9478), often with the same path, so cache it.
self._get_directory = lru_cache(256)(_get_directory)
self._duplicatepaths: Set[Path] = set()
# plugins that were explicitly skipped with pytest.skip
@@ -538,7 +525,11 @@ class PytestPluginManager(PluginManager):
"""
if self._confcutdir is None:
return True
return path not in self._confcutdir.parents
try:
path.relative_to(self._confcutdir)
except ValueError:
return False
return True
def _try_load_conftest(
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
@@ -556,7 +547,10 @@ class PytestPluginManager(PluginManager):
if self._noconftest:
return []
directory = self._get_directory(path)
if path.is_file():
directory = path.parent
else:
directory = path
# Optimization: avoid repeated searches in the same directory.
# Assumes always called with same importmode and rootpath.
@@ -833,8 +827,7 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]:
if is_simple_module:
module_name, _ = os.path.splitext(fn)
# we ignore "setup.py" at the root of the distribution
# as well as editable installation finder modules made by setuptools
if module_name != "setup" and not module_name.startswith("__editable__"):
if module_name != "setup":
seen_some = True
yield module_name
elif is_package:
+1 -1
View File
@@ -70,7 +70,7 @@ def load_config_dict_from_file(
try:
config = tomli.loads(toml_text)
except tomli.TOMLDecodeError as exc:
raise UsageError(f"{filepath}: {exc}") from exc
raise UsageError(str(exc)) from exc
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
if result is not None:
+5
View File
@@ -47,6 +47,11 @@ STRICT_OPTION = PytestRemovedIn8Warning(
# This deprecation is never really meant to be removed.
PRIVATE = PytestDeprecationWarning("A private pytest class or function was used.")
UNITTEST_SKIP_DURING_COLLECTION = PytestRemovedIn8Warning(
"Raising unittest.SkipTest to skip tests during collection is deprecated. "
"Use pytest.skip() instead."
)
ARGUMENT_PERCENT_DEFAULT = PytestRemovedIn8Warning(
'pytest now uses argparse. "%default" should be changed to "%(default)s"',
)
+3 -18
View File
@@ -542,11 +542,7 @@ class DoctestModule(pytest.Module):
)
else:
try:
module = import_path(
self.path,
root=self.config.rootpath,
mode=self.config.getoption("importmode"),
)
module = import_path(self.path, root=self.config.rootpath)
except ImportError:
if self.config.getvalue("doctest_ignore_import_errors"):
pytest.skip("unable to import module %r" % self.path)
@@ -660,7 +656,7 @@ def _init_checker_class() -> Type["doctest.OutputChecker"]:
precision = 0 if fraction is None else len(fraction)
if exponent is not None:
precision -= int(exponent)
if float(w.group()) == approx(float(g.group()), abs=10**-precision):
if float(w.group()) == approx(float(g.group()), abs=10 ** -precision):
# They're close enough. Replace the text we actually
# got with the text we want, so that it will match when we
# check the string literally.
@@ -734,16 +730,5 @@ 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.
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`.
"""
namespace of doctests."""
return dict()
+51 -67
View File
@@ -350,7 +350,7 @@ def reorder_items_atscope(
return items_done
def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
def get_direct_param_fixture_func(request):
return request.param
@@ -412,15 +412,6 @@ 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":
@@ -500,7 +491,6 @@ 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.
@@ -607,17 +597,8 @@ class FixtureRequest:
funcitem = self._pyfuncitem
scope = fixturedef._scope
try:
callspec = funcitem.callspec
except AttributeError:
callspec = None
if callspec is not None and argname in callspec.params:
param = callspec.params[argname]
param_index = callspec.indices[argname]
# If a parametrize invocation set a scope it will override
# the static scope defined with the fixture function.
with suppress(KeyError):
scope = callspec._arg2scope[argname]
else:
param = funcitem.callspec.getparam(argname)
except (AttributeError, ValueError):
param = NOTSET
param_index = 0
has_params = fixturedef.params is not None
@@ -657,6 +638,12 @@ class FixtureRequest:
)
)
fail(msg, pytrace=False)
else:
param_index = funcitem.callspec.indices[argname]
# If a parametrize invocation set a scope it will override
# the static scope defined with the fixture function.
with suppress(KeyError):
scope = funcitem.callspec._arg2scope[argname]
subrequest = SubRequest(
self, scope, param, param_index, fixturedef, _ispytest=True
@@ -940,7 +927,7 @@ def _eval_scope_callable(
@final
class FixtureDef(Generic[FixtureValue]):
"""A container for a fixture definition."""
"""A container for a factory definition."""
def __init__(
self,
@@ -952,56 +939,33 @@ class FixtureDef(Generic[FixtureValue]):
params: Optional[Sequence[object]],
unittest: bool = False,
ids: Optional[
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
Union[
Tuple[Union[None, str, float, int, bool], ...],
Callable[[Any], Optional[object]],
]
] = None,
) -> None:
self._fixturemanager = fixturemanager
# The "base" node ID for the fixture.
#
# This is a node ID prefix. A fixture is only available to a node (e.g.
# a `Function` item) if the fixture's baseid is a parent of the node's
# nodeid (see the `iterparentnodeids` function for what constitutes a
# "parent" and a "prefix" in this context).
#
# For a fixture found in a Collector's object (e.g. a `Module`s module,
# a `Class`'s class), the baseid is the Collector's nodeid.
#
# For a fixture found in a conftest plugin, the baseid is the conftest's
# directory path relative to the rootdir.
#
# For other plugins, the baseid is the empty string (always matches).
self.baseid = baseid or ""
# Whether the fixture was found from a node or a conftest in the
# collection tree. Will be false for fixtures defined in non-conftest
# plugins.
self.has_location = baseid is not None
# The fixture factory function.
self.func = func
# The name by which the fixture may be requested.
self.argname = argname
if scope is None:
scope = Scope.Function
elif callable(scope):
scope = _eval_scope_callable(scope, argname, fixturemanager.config)
if isinstance(scope, str):
scope = Scope.from_user(
scope, descr=f"Fixture '{func.__name__}'", where=baseid
)
self._scope = scope
# If the fixture is directly parametrized, the parameter values.
self.params: Optional[Sequence[object]] = params
# If the fixture is directly parametrized, a tuple of explicit IDs to
# assign to the parameter values, or a callable to generate an ID given
# a parameter value.
self.ids = ids
# The names requested by the fixtures.
self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
# Whether the fixture was collected from a unittest TestCase class.
# Note that it really only makes sense to define autouse fixtures in
# unittest TestCases.
self.argnames: Tuple[str, ...] = getfuncargnames(
func, name=argname, is_method=unittest
)
self.unittest = unittest
# If the fixture was executed, the current value of the fixture.
# Can change if the fixture is executed with different parameters.
self.ids = ids
self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None
self._finalizers: List[Callable[[], object]] = []
@@ -1028,8 +992,8 @@ class FixtureDef(Generic[FixtureValue]):
if exc:
raise exc
finally:
ihook = request.node.ihook
ihook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
hook = self._fixturemanager.session.gethookproxy(request.node.path)
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
# Even if finalization fails, we invalidate the cached fixture
# value and remove all finalizers because they may be bound methods
# which will keep instances alive.
@@ -1063,8 +1027,8 @@ class FixtureDef(Generic[FixtureValue]):
self.finish(request)
assert self.cached_result is None
ihook = request.node.ihook
result = ihook.pytest_fixture_setup(fixturedef=self, request=request)
hook = self._fixturemanager.session.gethookproxy(request.node.path)
result = hook.pytest_fixture_setup(fixturedef=self, request=request)
return result
def cache_key(self, request: SubRequest) -> object:
@@ -1129,8 +1093,18 @@ def pytest_fixture_setup(
def _ensure_immutable_ids(
ids: Optional[Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]]
) -> Optional[Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]]:
ids: Optional[
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
],
) -> Optional[
Union[
Tuple[Union[None, str, float, int, bool], ...],
Callable[[Any], Optional[object]],
]
]:
if ids is None:
return None
if callable(ids):
@@ -1174,8 +1148,9 @@ class FixtureFunctionMarker:
scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]"
params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter)
autouse: bool = False
ids: Optional[
Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]]
ids: Union[
Tuple[Union[None, str, float, int, bool], ...],
Callable[[Any], Optional[object]],
] = attr.ib(
default=None,
converter=_ensure_immutable_ids,
@@ -1216,7 +1191,10 @@ def fixture(
params: Optional[Iterable[object]] = ...,
autouse: bool = ...,
ids: Optional[
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
] = ...,
name: Optional[str] = ...,
) -> FixtureFunction:
@@ -1231,7 +1209,10 @@ def fixture(
params: Optional[Iterable[object]] = ...,
autouse: bool = ...,
ids: Optional[
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
] = ...,
name: Optional[str] = None,
) -> FixtureFunctionMarker:
@@ -1245,7 +1226,10 @@ def fixture(
params: Optional[Iterable[object]] = None,
autouse: bool = False,
ids: Optional[
Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]]
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
] = None,
name: Optional[str] = None,
) -> Union[FixtureFunctionMarker, FixtureFunction]:
@@ -1287,7 +1271,7 @@ def fixture(
the fixture.
:param ids:
Sequence of ids each corresponding to the params so that they are
List of string ids each corresponding to the params so that they are
part of the test id. If no ids are provided they will be generated
automatically from the params.
+1 -1
View File
@@ -231,7 +231,7 @@ class _NodeReporter:
msg = f'failed on teardown with "{reason}"'
else:
msg = f'failed on setup with "{reason}"'
self._add_simple("error", bin_xml_escape(msg), str(report.longrepr))
self._add_simple("error", msg, str(report.longrepr))
def append_skipped(self, report: TestReport) -> None:
if hasattr(report, "wasxfail"):
+4 -16
View File
@@ -270,15 +270,8 @@ class LegacyTestdirPlugin:
@final
@attr.s(init=False, auto_attribs=True)
class TempdirFactory:
"""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>`.
"""
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
for :class:``TempPathFactory``."""
_tmppath_factory: TempPathFactory
@@ -289,11 +282,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 :class:`py.path.local` object."""
"""Same as :meth:`TempPathFactory.mktemp`, but returns a ``_pytest.compat.LEGACY_PATH`` object."""
return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
def getbasetemp(self) -> LEGACY_PATH:
"""Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object."""
"""Backward compat wrapper for ``_tmppath_factory.getbasetemp``."""
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
@@ -319,11 +312,6 @@ 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)
+1 -4
View File
@@ -870,10 +870,7 @@ def resolve_collection_argument(
If the path doesn't exist, raise UsageError.
If the path is a directory and selection parts are present, raise UsageError.
"""
base, squacket, rest = str(arg).partition("[")
strpath, *parts = base.split("::")
if parts:
parts[-1] = f"{parts[-1]}{squacket}{rest}"
strpath, *parts = str(arg).split("::")
if as_pypath:
strpath = search_pypath(strpath)
fspath = invocation_path / strpath
+10 -5
View File
@@ -72,11 +72,16 @@ def get_empty_parameterset_mark(
return mark
class ParameterSet(NamedTuple):
values: Sequence[Union[object, NotSetType]]
marks: Collection[Union["MarkDecorator", "Mark"]]
id: Optional[str]
class ParameterSet(
NamedTuple(
"ParameterSet",
[
("values", Sequence[Union[object, NotSetType]]),
("marks", Collection[Union["MarkDecorator", "Mark"]]),
("id", Optional[str]),
],
)
):
@classmethod
def param(
cls,
+23 -55
View File
@@ -29,26 +29,21 @@ V = TypeVar("V")
def monkeypatch() -> Generator["MonkeyPatch", None, None]:
"""A convenient fixture for monkey-patching.
The fixture provides these methods to modify objects, dictionaries, or
:data:`os.environ`:
The fixture provides these methods to modify objects, dictionaries or
os.environ::
* :meth:`monkeypatch.setattr(obj, name, value, raising=True) <pytest.MonkeyPatch.setattr>`
* :meth:`monkeypatch.delattr(obj, name, raising=True) <pytest.MonkeyPatch.delattr>`
* :meth:`monkeypatch.setitem(mapping, name, value) <pytest.MonkeyPatch.setitem>`
* :meth:`monkeypatch.delitem(obj, name, raising=True) <pytest.MonkeyPatch.delitem>`
* :meth:`monkeypatch.setenv(name, value, prepend=None) <pytest.MonkeyPatch.setenv>`
* :meth:`monkeypatch.delenv(name, raising=True) <pytest.MonkeyPatch.delenv>`
* :meth:`monkeypatch.syspath_prepend(path) <pytest.MonkeyPatch.syspath_prepend>`
* :meth:`monkeypatch.chdir(path) <pytest.MonkeyPatch.chdir>`
* :meth:`monkeypatch.context() <pytest.MonkeyPatch.context>`
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
monkeypatch.delitem(obj, name, raising=True)
monkeypatch.setenv(name, value, prepend=None)
monkeypatch.delenv(name, raising=True)
monkeypatch.syspath_prepend(path)
monkeypatch.chdir(path)
All modifications will be undone after the requesting test function or
fixture has finished. The ``raising`` parameter determines if a :class:`KeyError`
or :class:`AttributeError` will be raised if the set/deletion operation does not have the
specified target.
To undo modifications done by the fixture in a contained scope,
use :meth:`context() <pytest.MonkeyPatch.context>`.
fixture has finished. The ``raising`` parameter determines if a KeyError
or AttributeError will be raised if the set/deletion operation has no target.
"""
mpatch = MonkeyPatch()
yield mpatch
@@ -187,40 +182,16 @@ class MonkeyPatch:
value: object = notset,
raising: bool = True,
) -> None:
"""
Set attribute value on target, memorizing the old value.
"""Set attribute value on target, memorizing the old value.
For example:
.. code-block:: python
import os
monkeypatch.setattr(os, "getcwd", lambda: "/")
The code above replaces the :func:`os.getcwd` function by a ``lambda`` which
always returns ``"/"``.
For convenience, you can specify a string as ``target`` which
For convenience you can specify a string as ``target`` which
will be interpreted as a dotted import path, with the last part
being the attribute name:
being the attribute name. For example,
``monkeypatch.setattr("os.getcwd", lambda: "/")``
would set the ``getcwd`` function of the ``os`` module.
.. code-block:: python
monkeypatch.setattr("os.getcwd", lambda: "/")
Raises :class:`AttributeError` if the attribute does not exist, unless
Raises AttributeError if the attribute does not exist, unless
``raising`` is set to False.
**Where to patch**
``monkeypatch.setattr`` works by (temporarily) changing the object that a name points to with another one.
There can be many names pointing to any individual object, so for patching to work you must ensure
that you patch the name used by the system under test.
See the section :ref:`Where to patch <python:where-to-patch>` in the :mod:`unittest.mock`
docs for a complete explanation, which is meant for :func:`unittest.mock.patch` but
applies to ``monkeypatch.setattr`` as well.
"""
__tracebackhide__ = True
import inspect
@@ -382,14 +353,11 @@ class MonkeyPatch:
There is generally no need to call `undo()`, since it is
called automatically during tear-down.
.. note::
The same `monkeypatch` fixture is used across a
single test function invocation. If `monkeypatch` is used both by
the test function itself and one of the test fixtures,
calling `undo()` will undo all of the changes made in
both functions.
Prefer to use :meth:`context() <pytest.MonkeyPatch.context>` instead.
Note that the same `monkeypatch` fixture is used across a
single test function invocation. If `monkeypatch` is used both by
the test function itself and one of the test fixtures,
calling `undo()` will undo all of the changes made in
both functions.
"""
for obj, name, value in reversed(self._setattr):
if value is not notset:
+16 -36
View File
@@ -111,7 +111,7 @@ def _imply_path(
NODE_CTOR_FSPATH_ARG.format(
node_type_name=node_type.__name__,
),
stacklevel=6,
stacklevel=3,
)
if path is not None:
if fspath is not None:
@@ -145,10 +145,7 @@ class NodeMeta(type):
warnings.warn(
PytestDeprecationWarning(
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
"See https://docs.pytest.org/en/stable/deprecations.html"
"#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
"for more details."
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}"
)
)
@@ -656,6 +653,20 @@ class Item(Node):
nextitem = None
def __init_subclass__(cls) -> None:
problems = ", ".join(
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
)
if problems:
warnings.warn(
f"{cls.__name__} is an Item subclass and should not be a collector, "
f"however its bases {problems} are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n"
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
PytestWarning,
)
def __init__(
self,
name,
@@ -683,37 +694,6 @@ class Item(Node):
#: for this test.
self.user_properties: List[Tuple[str, object]] = []
self._check_item_and_collector_diamond_inheritance()
def _check_item_and_collector_diamond_inheritance(self) -> None:
"""
Check if the current type inherits from both File and Collector
at the same time, emitting a warning accordingly (#8447).
"""
cls = type(self)
# We inject an attribute in the type to avoid issuing this warning
# for the same class more than once, which is not helpful.
# It is a hack, but was deemed acceptable in order to avoid
# flooding the user in the common case.
attr_name = "_pytest_diamond_inheritance_warning_shown"
if getattr(cls, attr_name, False):
return
setattr(cls, attr_name, True)
problems = ", ".join(
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
)
if problems:
warnings.warn(
f"{cls.__name__} is an Item subclass and should not be a collector, "
f"however its bases {problems} are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n"
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
PytestWarning,
)
def runtest(self) -> None:
"""Run the test case for this item.
+5 -14
View File
@@ -603,20 +603,11 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
module_parts = module_name.split(".")
while module_name:
if module_name not in modules:
try:
# If sys.meta_path is empty, calling import_module will issue
# a warning and raise ModuleNotFoundError. To avoid the
# warning, we check sys.meta_path explicitly and raise the error
# ourselves to fall back to creating a dummy module.
if not sys.meta_path:
raise ModuleNotFoundError
importlib.import_module(module_name)
except ModuleNotFoundError:
module = ModuleType(
module_name,
doc="Empty module created by pytest's importmode=importlib.",
)
modules[module_name] = module
module = ModuleType(
module_name,
doc="Empty module created by pytest's importmode=importlib.",
)
modules[module_name] = module
module_parts.pop(-1)
module_name = ".".join(module_parts)
+8 -14
View File
@@ -477,9 +477,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]:
@fixture
def pytester(
request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch
) -> "Pytester":
def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pytester":
"""
Facilities to write tests/configuration files, execute pytest in isolation, and match
against expected output, perfect for black-box testing of pytest plugins.
@@ -490,7 +488,7 @@ def pytester(
It is particularly useful for testing plugins. It is similar to the :fixture:`tmp_path`
fixture but provides methods which aid in testing pytest itself.
"""
return Pytester(request, tmp_path_factory, monkeypatch, _ispytest=True)
return Pytester(request, tmp_path_factory, _ispytest=True)
@fixture
@@ -598,15 +596,11 @@ class RunResult:
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
warnings: Optional[int] = None,
deselected: Optional[int] = None,
warnings: int = 0,
deselected: int = 0,
) -> None:
"""
Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
``warnings`` and ``deselected`` are only checked if not None.
"""
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
__tracebackhide__ = True
from _pytest.pytester_assertions import assert_outcomes
@@ -685,7 +679,6 @@ class Pytester:
self,
request: FixtureRequest,
tmp_path_factory: TempPathFactory,
monkeypatch: MonkeyPatch,
*,
_ispytest: bool = False,
) -> None:
@@ -709,7 +702,7 @@ class Pytester:
self._method = self._request.config.getoption("--runpytest")
self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True)
self._monkeypatch = mp = monkeypatch
self._monkeypatch = mp = MonkeyPatch()
mp.setenv("PYTEST_DEBUG_TEMPROOT", str(self._test_tmproot))
# Ensure no unexpected caching via tox.
mp.delenv("TOX_ENV_DIR", raising=False)
@@ -741,6 +734,7 @@ class Pytester:
self._sys_modules_snapshot.restore()
self._sys_path_snapshot.restore()
self._cwd_snapshot.restore()
self._monkeypatch.undo()
def __take_sys_modules_snapshot(self) -> SysModulesSnapshot:
# Some zope modules used by twisted-related tests keep internal state
+6 -9
View File
@@ -4,7 +4,6 @@
# hence cannot be subject to assertion rewriting, which requires a
# module to not be already imported.
from typing import Dict
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
@@ -43,8 +42,8 @@ def assert_outcomes(
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
warnings: Optional[int] = None,
deselected: Optional[int] = None,
warnings: int = 0,
deselected: int = 0,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
@@ -57,6 +56,8 @@ def assert_outcomes(
"errors": outcomes.get("errors", 0),
"xpassed": outcomes.get("xpassed", 0),
"xfailed": outcomes.get("xfailed", 0),
"warnings": outcomes.get("warnings", 0),
"deselected": outcomes.get("deselected", 0),
}
expected = {
"passed": passed,
@@ -65,11 +66,7 @@ def assert_outcomes(
"errors": errors,
"xpassed": xpassed,
"xfailed": xfailed,
"warnings": warnings,
"deselected": deselected,
}
if warnings is not None:
obtained["warnings"] = outcomes.get("warnings", 0)
expected["warnings"] = warnings
if deselected is not None:
obtained["deselected"] = outcomes.get("deselected", 0)
expected["deselected"] = deselected
assert obtained == expected
+167 -206
View File
@@ -303,9 +303,6 @@ class PyobjMixin(nodes.Node):
# used to avoid Function marker duplication
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
# This assumes that `obj` is called before there is a chance
# to add custom keys to `self.keywords`, so no fear of overriding.
self.keywords.update((mark.name, mark) for mark in self.own_markers)
return obj
@obj.setter
@@ -905,6 +902,8 @@ class InstanceDummy:
only to ignore it; this dummy class keeps them working. This will be removed
in pytest 8."""
pass
def __getattr__(name: str) -> object:
if name == "Instance":
@@ -927,159 +926,6 @@ def hasnew(obj: object) -> bool:
return False
@final
@attr.s(frozen=True, auto_attribs=True, slots=True)
class IdMaker:
"""Make IDs for a parametrization."""
# The argnames of the parametrization.
argnames: Sequence[str]
# The ParameterSets of the parametrization.
parametersets: Sequence[ParameterSet]
# Optionally, a user-provided callable to make IDs for parameters in a
# ParameterSet.
idfn: Optional[Callable[[Any], Optional[object]]]
# Optionally, explicit IDs for ParameterSets by index.
ids: Optional[Sequence[Optional[object]]]
# Optionally, the pytest config.
# Used for controlling ASCII escaping, and for calling the
# :hook:`pytest_make_parametrize_id` hook.
config: Optional[Config]
# Optionally, the ID of the node being parametrized.
# Used only for clearer error messages.
nodeid: Optional[str]
# Optionally, the ID of the function being parametrized.
# Used only for clearer error messages.
func_name: Optional[str]
def make_unique_parameterset_ids(self) -> List[str]:
"""Make a unique identifier for each ParameterSet, that may be used to
identify the parametrization in a node ID.
Format is <prm_1_token>-...-<prm_n_token>[counter], where prm_x_token is
- user-provided id, if given
- else an id derived from the value, applicable for certain types
- else <argname><parameterset index>
The counter suffix is appended only in case a string wouldn't be unique
otherwise.
"""
resolved_ids = list(self._resolve_ids())
# All IDs must be unique!
if len(resolved_ids) != len(set(resolved_ids)):
# Record the number of occurrences of each ID.
id_counts = Counter(resolved_ids)
# Map the ID to its next suffix.
id_suffixes: Dict[str, int] = defaultdict(int)
# Suffix non-unique IDs to make them unique.
for index, id in enumerate(resolved_ids):
if id_counts[id] > 1:
resolved_ids[index] = f"{id}{id_suffixes[id]}"
id_suffixes[id] += 1
return resolved_ids
def _resolve_ids(self) -> Iterable[str]:
"""Resolve IDs for all ParameterSets (may contain duplicates)."""
for idx, parameterset in enumerate(self.parametersets):
if parameterset.id is not None:
# ID provided directly - pytest.param(..., id="...")
yield parameterset.id
elif self.ids and idx < len(self.ids) and self.ids[idx] is not None:
# ID provided in the IDs list - parametrize(..., ids=[...]).
yield self._idval_from_value_required(self.ids[idx], idx)
else:
# ID not provided - generate it.
yield "-".join(
self._idval(val, argname, idx)
for val, argname in zip(parameterset.values, self.argnames)
)
def _idval(self, val: object, argname: str, idx: int) -> str:
"""Make an ID for a parameter in a ParameterSet."""
idval = self._idval_from_function(val, argname, idx)
if idval is not None:
return idval
idval = self._idval_from_hook(val, argname)
if idval is not None:
return idval
idval = self._idval_from_value(val)
if idval is not None:
return idval
return self._idval_from_argname(argname, idx)
def _idval_from_function(
self, val: object, argname: str, idx: int
) -> Optional[str]:
"""Try to make an ID for a parameter in a ParameterSet using the
user-provided id callable, if given."""
if self.idfn is None:
return None
try:
id = self.idfn(val)
except Exception as e:
prefix = f"{self.nodeid}: " if self.nodeid is not None else ""
msg = "error raised while trying to determine id of parameter '{}' at position {}"
msg = prefix + msg.format(argname, idx)
raise ValueError(msg) from e
if id is None:
return None
return self._idval_from_value(id)
def _idval_from_hook(self, val: object, argname: str) -> Optional[str]:
"""Try to make an ID for a parameter in a ParameterSet by calling the
:hook:`pytest_make_parametrize_id` hook."""
if self.config:
id: Optional[str] = self.config.hook.pytest_make_parametrize_id(
config=self.config, val=val, argname=argname
)
return id
return None
def _idval_from_value(self, val: object) -> Optional[str]:
"""Try to make an ID for a parameter in a ParameterSet from its value,
if the value type is supported."""
if isinstance(val, STRING_TYPES):
return _ascii_escaped_by_config(val, self.config)
elif val is None or isinstance(val, (float, int, bool, complex)):
return str(val)
elif isinstance(val, Pattern):
return ascii_escaped(val.pattern)
elif val is NOTSET:
# Fallback to default. Note that NOTSET is an enum.Enum.
pass
elif isinstance(val, enum.Enum):
return str(val)
elif isinstance(getattr(val, "__name__", None), str):
# Name of a class, function, module, etc.
name: str = getattr(val, "__name__")
return name
return None
def _idval_from_value_required(self, val: object, idx: int) -> str:
"""Like _idval_from_value(), but fails if the type is not supported."""
id = self._idval_from_value(val)
if id is not None:
return id
# Fail.
if self.func_name is not None:
prefix = f"In {self.func_name}: "
elif self.nodeid is not None:
prefix = f"In {self.nodeid}: "
else:
prefix = ""
msg = (
f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. "
"Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
)
fail(msg, pytrace=False)
@staticmethod
def _idval_from_argname(argname: str, idx: int) -> str:
"""Make an ID for a parameter in a ParameterSet from the argument name
and the index of the ParameterSet."""
return str(argname) + str(idx)
@final
@attr.s(frozen=True, slots=True, auto_attribs=True)
class CallSpec2:
@@ -1202,7 +1048,10 @@ class Metafunc:
argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
indirect: Union[bool, Sequence[str]] = False,
ids: Optional[
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
] = None,
scope: "Optional[_ScopeName]" = None,
*,
@@ -1269,7 +1118,7 @@ class Metafunc:
It will also override any fixture-function defined scope, allowing
to set a dynamic scope using test context or configuration.
"""
argnames, parametersets = ParameterSet._for_parametrize(
argnames, parameters = ParameterSet._for_parametrize(
argnames,
argvalues,
self.function,
@@ -1301,8 +1150,8 @@ class Metafunc:
if generated_ids is not None:
ids = generated_ids
ids = self._resolve_parameter_set_ids(
argnames, ids, parametersets, nodeid=self.definition.nodeid
ids = self._resolve_arg_ids(
argnames, ids, parameters, nodeid=self.definition.nodeid
)
# Store used (possibly generated) ids with parametrize Marks.
@@ -1314,9 +1163,7 @@ class Metafunc:
# of all calls.
newcalls = []
for callspec in self._calls or [CallSpec2()]:
for param_index, (param_id, param_set) in enumerate(
zip(ids, parametersets)
):
for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
newcallspec = callspec.setmulti(
valtypes=arg_values_types,
argnames=argnames,
@@ -1329,29 +1176,27 @@ class Metafunc:
newcalls.append(newcallspec)
self._calls = newcalls
def _resolve_parameter_set_ids(
def _resolve_arg_ids(
self,
argnames: Sequence[str],
ids: Optional[
Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
Union[
Iterable[Union[None, str, float, int, bool]],
Callable[[Any], Optional[object]],
]
],
parametersets: Sequence[ParameterSet],
parameters: Sequence[ParameterSet],
nodeid: str,
) -> List[str]:
"""Resolve the actual ids for the given parameter sets.
"""Resolve the actual ids for the given argnames, based on the ``ids`` parameter given
to ``parametrize``.
:param argnames:
Argument names passed to ``parametrize()``.
:param ids:
The `ids` parameter of the ``parametrize()`` call (see docs).
:param parametersets:
The parameter sets, each containing a set of values corresponding
to ``argnames``.
:param nodeid str:
The nodeid of the definition item that generated this
parametrization.
:returns:
List with ids for each parameter set given.
:param List[str] argnames: List of argument names passed to ``parametrize()``.
:param ids: The ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``.
:param str str: The nodeid of the item that generated this parametrized call.
:rtype: List[str]
:returns: The list of ids for each argname given.
"""
if ids is None:
idfn = None
@@ -1361,24 +1206,15 @@ class Metafunc:
ids_ = None
else:
idfn = None
ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
id_maker = IdMaker(
argnames,
parametersets,
idfn,
ids_,
self.config,
nodeid=nodeid,
func_name=self.function.__name__,
)
return id_maker.make_unique_parameterset_ids()
ids_ = self._validate_ids(ids, parameters, self.function.__name__)
return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)
def _validate_ids(
self,
ids: Iterable[Optional[object]],
parametersets: Sequence[ParameterSet],
ids: Iterable[Union[None, str, float, int, bool]],
parameters: Sequence[ParameterSet],
func_name: str,
) -> List[Optional[object]]:
) -> List[Union[None, str]]:
try:
num_ids = len(ids) # type: ignore[arg-type]
except TypeError:
@@ -1386,14 +1222,29 @@ class Metafunc:
iter(ids)
except TypeError as e:
raise TypeError("ids must be a callable or an iterable") from e
num_ids = len(parametersets)
num_ids = len(parameters)
# num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
if num_ids != len(parametersets) and num_ids != 0:
if num_ids != len(parameters) and num_ids != 0:
msg = "In {}: {} parameter sets specified, with different number of ids: {}"
fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
fail(msg.format(func_name, len(parameters), num_ids), pytrace=False)
return list(itertools.islice(ids, num_ids))
new_ids = []
for idx, id_value in enumerate(itertools.islice(ids, num_ids)):
if id_value is None or isinstance(id_value, str):
new_ids.append(id_value)
elif isinstance(id_value, (float, int, bool)):
new_ids.append(str(id_value))
else:
msg = ( # type: ignore[unreachable]
"In {}: ids must be list of string/float/int/bool, "
"found: {} (type: {!r}) at index {}"
)
fail(
msg.format(func_name, saferepr(id_value), type(id_value), idx),
pytrace=False,
)
return new_ids
def _resolve_arg_value_types(
self,
@@ -1513,6 +1364,105 @@ def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -
return val if escape_option else ascii_escaped(val) # type: ignore
def _idval(
val: object,
argname: str,
idx: int,
idfn: Optional[Callable[[Any], Optional[object]]],
nodeid: Optional[str],
config: Optional[Config],
) -> str:
if idfn:
try:
generated_id = idfn(val)
if generated_id is not None:
val = generated_id
except Exception as e:
prefix = f"{nodeid}: " if nodeid is not None else ""
msg = "error raised while trying to determine id of parameter '{}' at position {}"
msg = prefix + msg.format(argname, idx)
raise ValueError(msg) from e
elif config:
hook_id: Optional[str] = config.hook.pytest_make_parametrize_id(
config=config, val=val, argname=argname
)
if hook_id:
return hook_id
if isinstance(val, STRING_TYPES):
return _ascii_escaped_by_config(val, config)
elif val is None or isinstance(val, (float, int, bool, complex)):
return str(val)
elif isinstance(val, Pattern):
return ascii_escaped(val.pattern)
elif val is NOTSET:
# Fallback to default. Note that NOTSET is an enum.Enum.
pass
elif isinstance(val, enum.Enum):
return str(val)
elif isinstance(getattr(val, "__name__", None), str):
# Name of a class, function, module, etc.
name: str = getattr(val, "__name__")
return name
return str(argname) + str(idx)
def _idvalset(
idx: int,
parameterset: ParameterSet,
argnames: Iterable[str],
idfn: Optional[Callable[[Any], Optional[object]]],
ids: Optional[List[Union[None, str]]],
nodeid: Optional[str],
config: Optional[Config],
) -> str:
if parameterset.id is not None:
return parameterset.id
id = None if ids is None or idx >= len(ids) else ids[idx]
if id is None:
this_id = [
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
for val, argname in zip(parameterset.values, argnames)
]
return "-".join(this_id)
else:
return _ascii_escaped_by_config(id, config)
def idmaker(
argnames: Iterable[str],
parametersets: Iterable[ParameterSet],
idfn: Optional[Callable[[Any], Optional[object]]] = None,
ids: Optional[List[Union[None, str]]] = None,
config: Optional[Config] = None,
nodeid: Optional[str] = None,
) -> List[str]:
resolved_ids = [
_idvalset(
valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
)
for valindex, parameterset in enumerate(parametersets)
]
# All IDs must be unique!
unique_ids = set(resolved_ids)
if len(unique_ids) != len(resolved_ids):
# Record the number of occurrences of each test ID.
test_id_counts = Counter(resolved_ids)
# Map the test ID to its next suffix.
test_id_suffixes: Dict[str, int] = defaultdict(int)
# Suffix non-unique IDs to make them unique.
for index, test_id in enumerate(resolved_ids):
if test_id_counts[test_id] > 1:
resolved_ids[index] = f"{test_id}{test_id_suffixes[test_id]}"
test_id_suffixes[test_id] += 1
return resolved_ids
def _pretty_fixture_path(func) -> str:
cwd = Path.cwd()
loc = Path(getlocation(func, str(cwd)))
@@ -1684,7 +1634,7 @@ class Function(PyobjMixin, nodes.Item):
config: Optional[Config] = None,
callspec: Optional[CallSpec2] = None,
callobj=NOTSET,
keywords: Optional[Mapping[str, Any]] = None,
keywords=None,
session: Optional[Session] = None,
fixtureinfo: Optional[FuncFixtureInfo] = None,
originalname: Optional[str] = None,
@@ -1705,20 +1655,31 @@ class Function(PyobjMixin, nodes.Item):
# Note: when FunctionDefinition is introduced, we should change ``originalname``
# to a readonly property that returns FunctionDefinition.name.
self.keywords.update(self.obj.__dict__)
self.own_markers.extend(get_unpacked_marks(self.obj))
if callspec:
self.callspec = callspec
self.own_markers.extend(callspec.marks)
# this is total hostile and a mess
# keywords are broken by design by now
# this will be redeemed later
for mark in callspec.marks:
# feel free to cry, this was broken for years before
# and keywords can't fix it per design
self.keywords[mark.name] = mark
self.own_markers.extend(normalize_mark_list(callspec.marks))
if keywords:
self.keywords.update(keywords)
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
# Note: the order of the updates is important here; indicates what
# takes priority (ctor argument over function attributes over markers).
# Take own_markers only; NodeKeywords handles parent traversal on its own.
self.keywords.update((mark.name, mark) for mark in self.own_markers)
self.keywords.update(self.obj.__dict__)
if keywords:
self.keywords.update(keywords)
self.keywords.update(
{
mark.name: True
for mark in self.iter_markers()
if mark.name not in self.keywords
}
)
if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
+23 -41
View File
@@ -1,6 +1,5 @@
import math
import pprint
from collections.abc import Collection
from collections.abc import Sized
from decimal import Decimal
from numbers import Complex
@@ -9,6 +8,7 @@ from typing import Any
from typing import Callable
from typing import cast
from typing import Generic
from typing import Iterable
from typing import List
from typing import Mapping
from typing import Optional
@@ -131,13 +131,12 @@ class ApproxBase:
# a numeric type. For this reason, the default is to do nothing. The
# classes that deal with sequences should reimplement this method to
# raise if there are any non-numeric elements in the sequence.
pass
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)
def _recursive_list_map(f, x):
if isinstance(x, list):
return [_recursive_list_map(f, xi) for xi in x]
else:
return f(x)
@@ -146,9 +145,7 @@ class ApproxNumpy(ApproxBase):
"""Perform approximate comparisons where the expected value is numpy array."""
def __repr__(self) -> str:
list_scalars = _recursive_sequence_map(
self._approx_scalar, self.expected.tolist()
)
list_scalars = _recursive_list_map(self._approx_scalar, self.expected.tolist())
return f"approx({list_scalars!r})"
def _repr_compare(self, other_side: "ndarray") -> List[str]:
@@ -168,7 +165,7 @@ class ApproxNumpy(ApproxBase):
return value
np_array_shape = self.expected.shape
approx_side_as_seq = _recursive_sequence_map(
approx_side_as_list = _recursive_list_map(
self._approx_scalar, self.expected.tolist()
)
@@ -183,7 +180,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_seq, index)
approx_value = get_value_from_nested_list(approx_side_as_list, index)
other_value = get_value_from_nested_list(other_side, index)
if approx_value != other_value:
abs_diff = abs(approx_value.expected - other_value)
@@ -198,7 +195,7 @@ class ApproxNumpy(ApproxBase):
(
str(index),
str(get_value_from_nested_list(other_side, index)),
str(get_value_from_nested_list(approx_side_as_seq, index)),
str(get_value_from_nested_list(approx_side_as_list, index)),
)
for index in different_ids
]
@@ -310,12 +307,12 @@ class ApproxMapping(ApproxBase):
raise TypeError(msg.format(key, value, pprint.pformat(self.expected)))
class ApproxSequenceLike(ApproxBase):
class ApproxSequencelike(ApproxBase):
"""Perform approximate comparisons where the expected value is a sequence of numbers."""
def __repr__(self) -> str:
seq_type = type(self.expected)
if seq_type not in (tuple, list):
if seq_type not in (tuple, list, set):
seq_type = list
return "approx({!r})".format(
seq_type(self._approx_scalar(x) for x in self.expected)
@@ -323,6 +320,7 @@ 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 [
@@ -330,7 +328,7 @@ class ApproxSequenceLike(ApproxBase):
f"Lengths: {len(self.expected)} and {len(other_side)}",
]
approx_side_as_map = _recursive_sequence_map(self._approx_scalar, self.expected)
approx_side_as_map = _recursive_list_map(self._approx_scalar, self.expected)
number_of_elements = len(approx_side_as_map)
max_abs_diff = -math.inf
@@ -343,7 +341,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 = math.inf
max_rel_diff = np.inf
else:
max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value))
different_ids.append(i)
@@ -518,7 +516,7 @@ class ApproxDecimal(ApproxScalar):
def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
"""Assert that two numbers (or two ordered sequences of numbers) are equal to each other
"""Assert that two numbers (or two sets of numbers) are equal to each other
within some tolerance.
Due to the :std:doc:`tutorial/floatingpoint`, numbers that we
@@ -550,11 +548,16 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
>>> 0.1 + 0.2 == approx(0.3)
True
The same syntax also works for ordered sequences of numbers::
The same syntax also works for sequences of numbers::
>>> (0.1 + 0.2, 0.2 + 0.4) == approx((0.3, 0.6))
True
Dictionary *values*::
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
``numpy`` arrays::
>>> import numpy as np # doctest: +SKIP
@@ -567,20 +570,6 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
>>> np.array([0.1, 0.2]) + np.array([0.2, 0.1]) == approx(0.3) # doctest: +SKIP
True
Only ordered sequences are supported, because ``approx`` needs
to infer the relative position of the sequences without ambiguity. This means
``sets`` and other unordered sequences are not supported.
Finally, dictionary *values* can also be compared::
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
True
The comparison will be true if both mappings have the same keys and their
respective values match the expected tolerances.
**Tolerances**
By default, ``approx`` considers numbers within a relative tolerance of
``1e-6`` (i.e. one part in a million) of its expected value to be equal.
This treatment would lead to surprising results if the expected value was
@@ -720,19 +709,12 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
expected = _as_numpy_array(expected)
cls = ApproxNumpy
elif (
hasattr(expected, "__getitem__")
isinstance(expected, Iterable)
and isinstance(expected, Sized)
# Type ignored because the error is wrong -- not unreachable.
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
):
cls = ApproxSequenceLike
elif (
isinstance(expected, Collection)
# Type ignored because the error is wrong -- not unreachable.
and not isinstance(expected, STRING_TYPES) # type: ignore[unreachable]
):
msg = f"pytest.approx() only supports ordered sequences, but got: {repr(expected)}"
raise TypeError(msg)
cls = ApproxSequencelike
else:
cls = ApproxScalar
+1 -8
View File
@@ -29,7 +29,7 @@ T = TypeVar("T")
def recwarn() -> Generator["WarningsRecorder", None, None]:
"""Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
See https://docs.python.org/library/how-to/capture-warnings.html for information
on warning categories.
"""
wrec = WarningsRecorder(_ispytest=True)
@@ -158,14 +158,7 @@ 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:
+4 -5
View File
@@ -7,7 +7,6 @@ from typing import Dict
from typing import Iterable
from typing import Iterator
from typing import List
from typing import Mapping
from typing import Optional
from typing import Tuple
from typing import Type
@@ -255,7 +254,7 @@ class TestReport(BaseReport):
self,
nodeid: str,
location: Tuple[str, Optional[int], str],
keywords: Mapping[str, Any],
keywords,
outcome: "Literal['passed', 'failed', 'skipped']",
longrepr: Union[
None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr
@@ -455,7 +454,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) # type:ignore[arg-type]
data = attr.asdict(entry)
for key, value in data.items():
if hasattr(value, "__dict__"):
data[key] = attr.asdict(value)
@@ -463,7 +462,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) # type:ignore[arg-type]
result = attr.asdict(reprtraceback)
result["reprentries"] = [
serialize_repr_entry(x) for x in reprtraceback.reprentries
]
@@ -473,7 +472,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) # type:ignore[arg-type]
return attr.asdict(reprcrash)
else:
return None
+7
View File
@@ -2,6 +2,7 @@
import bdb
import os
import sys
import warnings
from typing import Callable
from typing import cast
from typing import Dict
@@ -27,6 +28,7 @@ from _pytest._code.code import TerminalRepr
from _pytest.compat import final
from _pytest.config.argparsing import Parser
from _pytest.deprecated import check_ispytest
from _pytest.deprecated import UNITTEST_SKIP_DURING_COLLECTION
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
@@ -377,6 +379,11 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport:
# Type ignored because unittest is loaded dynamically.
skip_exceptions.append(unittest.SkipTest) # type: ignore
if isinstance(call.excinfo.value, tuple(skip_exceptions)):
if unittest is not None and isinstance(
call.excinfo.value, unittest.SkipTest # type: ignore[attr-defined]
):
warnings.warn(UNITTEST_SKIP_DURING_COLLECTION, stacklevel=2)
outcome = "skipped"
r_ = collector._repr_failure_py(call.excinfo, "line")
assert isinstance(r_, ExceptionChainRepr), repr(r_)
+10 -16
View File
@@ -542,21 +542,15 @@ class TerminalReporter:
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
if rep.skipped or hasattr(report, "wasxfail"):
available_width = (
(self._tw.fullwidth - self._tw.width_of_current_line)
- len(" [100%]")
- 1
)
reason = _get_raw_skip_reason(rep)
if self.config.option.verbose < 2:
available_width = (
(self._tw.fullwidth - self._tw.width_of_current_line)
- len(" [100%]")
- 1
)
formatted_reason = _format_trimmed(
" ({})", reason, available_width
)
else:
formatted_reason = f" ({reason})"
if reason and formatted_reason is not None:
self._tw.write(formatted_reason)
reason_ = _format_trimmed(" ({})", reason, available_width)
if reason and reason_ is not None:
self._tw.write(reason_)
if self._show_progress_info:
self._write_progress_information_filling_space()
else:
@@ -663,7 +657,7 @@ class TerminalReporter:
errors = len(self.stats.get("error", []))
skipped = len(self.stats.get("skipped", []))
deselected = len(self.stats.get("deselected", []))
selected = self._numcollected - deselected
selected = self._numcollected - errors - skipped - deselected
line = "collected " if final else "collecting "
line += (
str(self._numcollected) + " item" + ("" if self._numcollected == 1 else "s")
@@ -674,7 +668,7 @@ class TerminalReporter:
line += " / %d deselected" % deselected
if skipped:
line += " / %d skipped" % skipped
if self._numcollected > selected:
if self._numcollected > selected > 0:
line += " / %d selected" % selected
if self.isatty:
self.rewrite(line, bold=True, erase=True)
+2 -3
View File
@@ -158,10 +158,9 @@ 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)."""
try:
# In some exotic environments, getpass may not be importable.
import getpass
import getpass
try:
return getpass.getuser()
except (ImportError, KeyError):
return None
+1 -13
View File
@@ -185,15 +185,6 @@ class TestCaseFunction(Function):
_excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None
_testcase: Optional["unittest.TestCase"] = None
def _getobj(self):
assert self.parent is not None
# Unlike a regular Function in a Class, where `item.obj` returns
# a *bound* method (attached to an instance), TestCaseFunction's
# `obj` returns an *unbound* method (not attached to an instance).
# This inconsistency is probably not desirable, but needs some
# consideration before changing.
return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined]
def setup(self) -> None:
# A bound method to be called during teardown() if set (see 'runtest()').
self._explicit_tearDown: Optional[Callable[[], None]] = None
@@ -316,10 +307,7 @@ 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.
# 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:
if self.config.getoption("usepdb") and not _is_skipped(self.obj):
self._explicit_tearDown = self._testcase.tearDown
setattr(self._testcase, "tearDown", lambda *args: None)
-17
View File
@@ -81,23 +81,6 @@ def warning_record_to_str(warning_message: warnings.WarningMessage) -> str:
warning_message.lineno,
warning_message.line,
)
if warning_message.source is not None:
try:
import tracemalloc
except ImportError:
pass
else:
tb = tracemalloc.get_object_traceback(warning_message.source)
if tb is not None:
formatted_tb = "\n".join(tb.format())
# Use a leading new line to better separate the (large) output
# from the traceback to the previous warning text.
msg += f"\nObject allocated at:\n{formatted_tb}"
else:
# No need for a leading new line.
url = "https://docs.pytest.org/en/stable/how-to/capture-warnings.html#resource-warnings"
msg += "Enable tracemalloc to get traceback where the object was allocated.\n"
msg += f"See {url} for more info."
return msg
+4
View File
@@ -19,8 +19,10 @@ from _pytest.config.argparsing import OptionGroup
from _pytest.config.argparsing import Parser
from _pytest.debugging import pytestPDB as __pytestPDB
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureDef
from _pytest.fixtures import FixtureLookupError
from _pytest.fixtures import FixtureRequest
from _pytest.fixtures import SubRequest
from _pytest.fixtures import yield_fixture
from _pytest.freeze_support import freeze_includes
from _pytest.legacypath import TempdirFactory
@@ -97,8 +99,10 @@ __all__ = [
"fail",
"File",
"fixture",
"FixtureDef",
"FixtureLookupError",
"FixtureRequest",
"SubRequest",
"freeze_includes",
"Function",
"hookimpl",
+2
View File
@@ -1238,6 +1238,8 @@ def test_pdb_can_be_rewritten(pytester: Pytester) -> None:
" def check():",
"> assert 1 == 2",
"E assert 1 == 2",
"E +1",
"E -2",
"",
"pdb.py:2: AssertionError",
"*= 1 failed in *",
-87
View File
@@ -1468,90 +1468,3 @@ def test_no_recursion_index_on_recursion_error():
with pytest.raises(RuntimeError) as excinfo:
RecursionDepthError().trigger
assert "maximum recursion" in str(excinfo.getrepr())
def _exceptiongroup_common(
pytester: Pytester,
outer_chain: str,
inner_chain: str,
native: bool,
) -> None:
pre_raise = "exceptiongroup." if not native else ""
pre_catch = pre_raise if sys.version_info < (3, 11) else ""
filestr = f"""
{"import exceptiongroup" if not native else ""}
import pytest
def f(): raise ValueError("From f()")
def g(): raise BaseException("From g()")
def inner(inner_chain):
excs = []
for callback in [f, g]:
try:
callback()
except BaseException as err:
excs.append(err)
if excs:
if inner_chain == "none":
raise {pre_raise}BaseExceptionGroup("Oops", excs)
try:
raise SyntaxError()
except SyntaxError as e:
if inner_chain == "from":
raise {pre_raise}BaseExceptionGroup("Oops", excs) from e
else:
raise {pre_raise}BaseExceptionGroup("Oops", excs)
def outer(outer_chain, inner_chain):
try:
inner(inner_chain)
except {pre_catch}BaseExceptionGroup as e:
if outer_chain == "none":
raise
if outer_chain == "from":
raise IndexError() from e
else:
raise IndexError()
def test():
outer("{outer_chain}", "{inner_chain}")
"""
pytester.makepyfile(test_excgroup=filestr)
result = pytester.runpytest()
match_lines = []
if inner_chain in ("another", "from"):
match_lines.append(r"SyntaxError: <no detail available>")
match_lines += [
r" + Exception Group Traceback (most recent call last):",
rf" \| {pre_catch}BaseExceptionGroup: Oops \(2 sub-exceptions\)",
r" \| ValueError: From f\(\)",
r" \| BaseException: From g\(\)",
r"=* short test summary info =*",
]
if outer_chain in ("another", "from"):
match_lines.append(r"FAILED test_excgroup.py::test - IndexError")
else:
match_lines.append(
rf"FAILED test_excgroup.py::test - {pre_catch}BaseExceptionGroup: Oops \(2.*"
)
result.stdout.re_match_lines(match_lines)
@pytest.mark.skipif(
sys.version_info < (3, 11), reason="Native ExceptionGroup not implemented"
)
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
def test_native_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=True)
@pytest.mark.parametrize("outer_chain", ["none", "from", "another"])
@pytest.mark.parametrize("inner_chain", ["none", "from", "another"])
def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
# with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
pytest.importorskip("exceptiongroup")
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)
+3
View File
@@ -1,13 +1,16 @@
# flake8: noqa
# disable flake check on this file because some constructs are strange
# or redundant on purpose and can't be disable on a line-by-line basis
import ast
import inspect
import linecache
import sys
import textwrap
from pathlib import Path
from types import CodeType
from typing import Any
from typing import Dict
from typing import Optional
import pytest
from _pytest._code import Code
+17
View File
@@ -86,6 +86,23 @@ def test_private_is_deprecated() -> None:
PrivateInit(10, _ispytest=True)
def test_raising_unittest_skiptest_during_collection_is_deprecated(
pytester: Pytester,
) -> None:
pytester.makepyfile(
"""
import unittest
raise unittest.SkipTest()
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"*PytestRemovedIn8Warning: Raising unittest.SkipTest*",
]
)
@pytest.mark.parametrize("hooktype", ["hook", "ihook"])
def test_hookproxy_warnings_for_pathlib(tmp_path, hooktype, request):
path = legacy_path(tmp_path)

Some files were not shown because too many files have changed in this diff Show More