Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| efbd83fe04 | |||
| 23232f3aa0 |
@@ -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
|
||||
@@ -12,7 +12,6 @@ permissions: {}
|
||||
|
||||
jobs:
|
||||
createPullRequest:
|
||||
if: github.repository_owner == 'pytest-dev'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
+6
-16
@@ -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]
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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 +0,0 @@
|
||||
The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.
|
||||
@@ -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 +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``.
|
||||
@@ -0,0 +1 @@
|
||||
Pytest will now avoid specialized assert formatting when it is detected that the default __eq__ is overridden
|
||||
@@ -0,0 +1 @@
|
||||
Fixed error message prints function decorators when using assert in Python 3.8 and above.
|
||||
@@ -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``).
|
||||
@@ -0,0 +1 @@
|
||||
Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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,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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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
@@ -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),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ Further topics
|
||||
|
||||
backwards-compatibility
|
||||
deprecations
|
||||
py27-py34-deprecation
|
||||
|
||||
contributing
|
||||
development_guide
|
||||
|
||||
+15
-40
@@ -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 |
@@ -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:
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 _________________
|
||||
|
||||
@@ -94,7 +94,7 @@ Mark Lapierre discusses the `Pros and Cons of Quarantined Tests <https://dev.to/
|
||||
CI tools that rerun on failure
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/previous-versions/azure/devops/2017/dec-11-vsts?view=tfs-2017#identify-flaky-tests>`_ and rerun failed tests.
|
||||
Azure Pipelines (the Azure cloud CI/CD tool, formerly Visual Studio Team Services or VSTS) has a feature to `identify flaky tests <https://docs.microsoft.com/en-us/azure/devops/release-notes/2017/dec-11-vsts#identify-flaky-tests>`_ and rerun failed tests.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,26 +12,41 @@ For development, we recommend you use :mod:`venv` for virtual environments and
|
||||
as well as the ``pytest`` package itself.
|
||||
This ensures your code and dependencies are isolated from your system Python installation.
|
||||
|
||||
Create a ``pyproject.toml`` file in the root of your repository as described in
|
||||
:doc:`packaging:tutorials/packaging-projects`.
|
||||
The first few lines should look like this:
|
||||
Next, place a ``pyproject.toml`` file in the root of your package:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "PACKAGENAME"
|
||||
and a ``setup.cfg`` file containing your package's metadata with the following minimum content:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[metadata]
|
||||
name = PACKAGENAME
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
|
||||
where ``PACKAGENAME`` is the name of your package.
|
||||
|
||||
.. note::
|
||||
|
||||
If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
|
||||
You can then install your package in "editable" mode by running from the same directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install -e .
|
||||
pip install -e .
|
||||
|
||||
which lets you change your source code (both tests and application) and rerun tests at will.
|
||||
|
||||
@@ -74,11 +89,11 @@ to keep tests separate from actual application code (often a good idea):
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
src/
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
setup.cfg
|
||||
mypkg/
|
||||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
tests/
|
||||
test_app.py
|
||||
test_view.py
|
||||
@@ -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`:
|
||||
|
||||
@@ -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
|
||||
-------------------------------------------------
|
||||
|
||||
@@ -22,7 +22,7 @@ Install ``pytest``
|
||||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 7.1.3
|
||||
pytest 7.0.0rc1
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
||||
@@ -201,7 +201,7 @@ if you run this module:
|
||||
E '1'
|
||||
E Extra items in the right set:
|
||||
E '5'
|
||||
E Use -v to get more diff
|
||||
E Use -v to get the full diff
|
||||
|
||||
test_assert2.py:4: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
-------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 ____________________________
|
||||
|
||||
@@ -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`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ Reference guides
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
reference
|
||||
fixtures
|
||||
customize
|
||||
exit-codes
|
||||
plugin_list
|
||||
customize
|
||||
reference
|
||||
exit-codes
|
||||
|
||||
+328
-544
File diff suppressed because it is too large
Load Diff
+123
-63
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,5 +1,4 @@
|
||||
import sys
|
||||
|
||||
from distutils.core import setup
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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]:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 *",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user