Compare commits
65 Commits
Author | SHA1 | Date |
---|---|---|
|
8c89a7b1e9 | |
|
f530a765a6 | |
|
894338e94d | |
|
3265caaaf5 | |
|
00c270c964 | |
|
c27350e60c | |
|
168019aff8 | |
|
dfd44b3bed | |
|
85c34fd20d | |
|
ec25744cb3 | |
|
3ce6030f0c | |
|
4191e02598 | |
|
eb50c6ce99 | |
|
9693556f27 | |
|
e8e7d44a4c | |
|
2fd4549db5 | |
|
cee8d6f274 | |
|
79108bf9a3 | |
|
779a87aada | |
|
60216810d9 | |
|
37e410fce8 | |
|
0aeb843e25 | |
|
1e83bd8386 | |
|
02e9e8403e | |
|
7e1549dfb6 | |
|
d61f83c030 | |
|
432a60bd52 | |
|
4b83a05939 | |
|
4e14609b99 | |
|
76bef68f3e | |
|
af22d34158 | |
|
d1b9660402 | |
|
9c103aef2f | |
|
624ae81eb1 | |
|
5bf361f24e | |
|
baa938eea5 | |
|
94c05bc2a4 | |
|
1ae778f13e | |
|
cb07711846 | |
|
944070259e | |
|
e8055c1609 | |
|
f22fbbf9f1 | |
|
211d08e9bc | |
|
a6f85a0e3e | |
|
08d0dd06ac | |
|
405fd15128 | |
|
c16315f5c3 | |
|
f1989747b7 | |
|
7d35baaa3c | |
|
c3b0080c87 | |
|
3c2f90b9b9 | |
|
f5d2edc1fc | |
|
47d6adf890 | |
|
dbd4c5fb2f | |
|
b2271afa65 | |
|
6a5076db9f | |
|
8606feb3a4 | |
|
6a4a0f43b5 | |
|
eff2e2decd | |
|
23bbd5a628 | |
|
651c7bf932 | |
|
1f08cd7225 | |
|
5c6a9a6504 | |
|
ac4e3cced9 | |
|
3af3f569d5 |
|
@ -28,25 +28,29 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Build and Check Package
|
||||||
|
uses: hynek/build-and-inspect-python-package@v1.5
|
||||||
|
|
||||||
|
- name: Download Package
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: Packages
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Publish package to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@release/v1
|
||||||
|
with:
|
||||||
|
password: ${{ secrets.pypi_token }}
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.7"
|
python-version: "3.7"
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install tox
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
pip install --upgrade build tox
|
pip install --upgrade 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
|
- name: Publish GitHub release notes
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -18,6 +18,11 @@ on:
|
||||||
env:
|
env:
|
||||||
PYTEST_ADDOPTS: "--color=yes"
|
PYTEST_ADDOPTS: "--color=yes"
|
||||||
|
|
||||||
|
# Cancel running jobs for the same workflow and branch.
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
# Set permissions at the job level.
|
# Set permissions at the job level.
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
|
@ -189,3 +194,10 @@ jobs:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
files: ./coverage.xml
|
files: ./coverage.xml
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|
||||||
|
check-package:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build and Check Package
|
||||||
|
uses: hynek/build-and-inspect-python-package@v1.5
|
||||||
|
|
|
@ -2,17 +2,17 @@ default_language_version:
|
||||||
python: "3.10"
|
python: "3.10"
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.10.0
|
rev: 23.1.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
- repo: https://github.com/asottile/blacken-docs
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
rev: v1.12.1
|
rev: 1.13.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies: [black==20.8b1]
|
additional_dependencies: [black==23.1.0]
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.3.0
|
rev: v4.4.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
@ -23,7 +23,7 @@ repos:
|
||||||
exclude: _pytest/(debugging|hookspec).py
|
exclude: _pytest/(debugging|hookspec).py
|
||||||
language_version: python3
|
language_version: python3
|
||||||
- repo: https://github.com/PyCQA/autoflake
|
- repo: https://github.com/PyCQA/autoflake
|
||||||
rev: v1.7.6
|
rev: v2.0.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: autoflake
|
- id: autoflake
|
||||||
name: autoflake
|
name: autoflake
|
||||||
|
@ -31,7 +31,7 @@ repos:
|
||||||
language: python
|
language: python
|
||||||
files: \.py$
|
files: \.py$
|
||||||
- repo: https://github.com/PyCQA/flake8
|
- repo: https://github.com/PyCQA/flake8
|
||||||
rev: 5.0.4
|
rev: 6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
language_version: python3
|
language_version: python3
|
||||||
|
@ -39,26 +39,26 @@ repos:
|
||||||
- flake8-typing-imports==1.12.0
|
- flake8-typing-imports==1.12.0
|
||||||
- flake8-docstrings==1.5.0
|
- flake8-docstrings==1.5.0
|
||||||
- repo: https://github.com/asottile/reorder_python_imports
|
- repo: https://github.com/asottile/reorder_python_imports
|
||||||
rev: v3.8.5
|
rev: v3.9.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: reorder-python-imports
|
- id: reorder-python-imports
|
||||||
args: ['--application-directories=.:src', --py37-plus]
|
args: ['--application-directories=.:src', --py37-plus]
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v3.1.0
|
rev: v3.3.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: [--py37-plus]
|
args: [--py37-plus]
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v2.1.0
|
rev: v2.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
args: ["--max-py-version=3.10", "--include-version-classifiers"]
|
args: ["--max-py-version=3.11", "--include-version-classifiers"]
|
||||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||||
rev: v1.9.0
|
rev: v1.10.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-use-type-annotations
|
- id: python-use-type-annotations
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.982
|
rev: v1.1.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/)
|
files: ^(src/|testing/)
|
||||||
|
|
|
@ -2,9 +2,12 @@ version: 2
|
||||||
|
|
||||||
python:
|
python:
|
||||||
install:
|
install:
|
||||||
- requirements: doc/en/requirements.txt
|
# Install pytest first, then doc/en/requirements.txt.
|
||||||
- method: pip
|
# This order is important to honor any pins in doc/en/requirements.txt
|
||||||
path: .
|
# when the pinned library is also a dependency of pytest.
|
||||||
|
- method: pip
|
||||||
|
path: .
|
||||||
|
- requirements: doc/en/requirements.txt
|
||||||
|
|
||||||
build:
|
build:
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
|
|
10
AUTHORS
10
AUTHORS
|
@ -12,6 +12,7 @@ Adam Uhlir
|
||||||
Ahn Ki-Wook
|
Ahn Ki-Wook
|
||||||
Akiomi Kamakura
|
Akiomi Kamakura
|
||||||
Alan Velasco
|
Alan Velasco
|
||||||
|
Alessio Izzo
|
||||||
Alexander Johnson
|
Alexander Johnson
|
||||||
Alexander King
|
Alexander King
|
||||||
Alexei Kozlenok
|
Alexei Kozlenok
|
||||||
|
@ -88,6 +89,7 @@ Daniel Grana
|
||||||
Daniel Hahler
|
Daniel Hahler
|
||||||
Daniel Nuri
|
Daniel Nuri
|
||||||
Daniel Sánchez Castelló
|
Daniel Sánchez Castelló
|
||||||
|
Daniel Valenzuela Zenteno
|
||||||
Daniel Wandschneider
|
Daniel Wandschneider
|
||||||
Daniele Procida
|
Daniele Procida
|
||||||
Danielle Jenkins
|
Danielle Jenkins
|
||||||
|
@ -124,6 +126,7 @@ Erik M. Bray
|
||||||
Evan Kepner
|
Evan Kepner
|
||||||
Fabien Zarifian
|
Fabien Zarifian
|
||||||
Fabio Zadrozny
|
Fabio Zadrozny
|
||||||
|
Felix Hofstätter
|
||||||
Felix Nieuwenhuizen
|
Felix Nieuwenhuizen
|
||||||
Feng Ma
|
Feng Ma
|
||||||
Florian Bruhin
|
Florian Bruhin
|
||||||
|
@ -161,6 +164,7 @@ Jaap Broekhuizen
|
||||||
Jakob van Santen
|
Jakob van Santen
|
||||||
Jakub Mitoraj
|
Jakub Mitoraj
|
||||||
James Bourbeau
|
James Bourbeau
|
||||||
|
James Frost
|
||||||
Jan Balster
|
Jan Balster
|
||||||
Janne Vanhala
|
Janne Vanhala
|
||||||
Jason R. Coombs
|
Jason R. Coombs
|
||||||
|
@ -285,6 +289,7 @@ Prashant Sharma
|
||||||
Pulkit Goyal
|
Pulkit Goyal
|
||||||
Punyashloka Biswal
|
Punyashloka Biswal
|
||||||
Quentin Pradet
|
Quentin Pradet
|
||||||
|
q0w
|
||||||
Ralf Schmitt
|
Ralf Schmitt
|
||||||
Ram Rachum
|
Ram Rachum
|
||||||
Ralph Giles
|
Ralph Giles
|
||||||
|
@ -310,6 +315,7 @@ Samuel Searles-Bryant
|
||||||
Samuele Pedroni
|
Samuele Pedroni
|
||||||
Sanket Duthade
|
Sanket Duthade
|
||||||
Sankt Petersbug
|
Sankt Petersbug
|
||||||
|
Saravanan Padmanaban
|
||||||
Segev Finer
|
Segev Finer
|
||||||
Serhii Mozghovyi
|
Serhii Mozghovyi
|
||||||
Seth Junot
|
Seth Junot
|
||||||
|
@ -323,6 +329,7 @@ Srinivas Reddy Thatiparthy
|
||||||
Stefan Farmbauer
|
Stefan Farmbauer
|
||||||
Stefan Scherfke
|
Stefan Scherfke
|
||||||
Stefan Zimmermann
|
Stefan Zimmermann
|
||||||
|
Stefanie Molin
|
||||||
Stefano Taschini
|
Stefano Taschini
|
||||||
Steffen Allner
|
Steffen Allner
|
||||||
Stephan Obermann
|
Stephan Obermann
|
||||||
|
@ -341,6 +348,7 @@ Thomas Grainger
|
||||||
Thomas Hisch
|
Thomas Hisch
|
||||||
Tim Hoffmann
|
Tim Hoffmann
|
||||||
Tim Strazny
|
Tim Strazny
|
||||||
|
TJ Bruno
|
||||||
Tobias Diez
|
Tobias Diez
|
||||||
Tom Dalton
|
Tom Dalton
|
||||||
Tom Viner
|
Tom Viner
|
||||||
|
@ -372,6 +380,8 @@ Xixi Zhao
|
||||||
Xuan Luong
|
Xuan Luong
|
||||||
Xuecong Liao
|
Xuecong Liao
|
||||||
Yoav Caspi
|
Yoav Caspi
|
||||||
|
Yuliang Shao
|
||||||
|
Yusuke Kadowaki
|
||||||
Yuval Shimon
|
Yuval Shimon
|
||||||
Zac Hatfield-Dodds
|
Zac Hatfield-Dodds
|
||||||
Zachary Kneupper
|
Zachary Kneupper
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8.
|
|
|
@ -1 +0,0 @@
|
||||||
:data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled.
|
|
|
@ -1 +0,0 @@
|
||||||
:class:`~pytest.PytestReturnNotNoneWarning` is now a subclass of :class:`~pytest.PytestRemovedIn8Warning`: the plan is to make returning non-``None`` from tests an error in the future.
|
|
|
@ -1,5 +0,0 @@
|
||||||
``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names,
|
|
||||||
instead of just ``list[str]`` and ``tuple[str, ...]``.
|
|
||||||
|
|
||||||
(Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a
|
|
||||||
comma-delimited name list, as before).
|
|
|
@ -1,3 +0,0 @@
|
||||||
Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for
|
|
||||||
type check and runtime purposes. Made `_pytest.doctest` use internal APIs
|
|
||||||
to avoid circular imports.
|
|
|
@ -1 +0,0 @@
|
||||||
Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``.
|
|
|
@ -1 +0,0 @@
|
||||||
The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``.
|
|
|
@ -1 +0,0 @@
|
||||||
Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file.
|
|
|
@ -1 +0,0 @@
|
||||||
pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency.
|
|
|
@ -0,0 +1 @@
|
||||||
|
The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator.
|
|
@ -0,0 +1 @@
|
||||||
|
Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully.
|
|
@ -0,0 +1 @@
|
||||||
|
The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.
|
|
@ -0,0 +1 @@
|
||||||
|
Correctly handle ``__tracebackhide__`` for chained exceptions.
|
|
@ -1 +0,0 @@
|
||||||
Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expresed instead.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Deprecate configuring hook specs/impls using attributes/marks.
|
|
||||||
|
|
||||||
Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`.
|
|
||||||
For more details, see the :ref:`docs <legacy-path-hooks-deprecated>`.
|
|
|
@ -1 +0,0 @@
|
||||||
A warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`.
|
|
|
@ -1,5 +0,0 @@
|
||||||
Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply.
|
|
||||||
|
|
||||||
When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse.
|
|
||||||
|
|
||||||
When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`pytest.Node.iter_markers` instead.
|
|
|
@ -1,2 +0,0 @@
|
||||||
Introduce multiline display for warning matching via :py:func:`pytest.warns` and
|
|
||||||
enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
|
|
|
@ -1,2 +0,0 @@
|
||||||
Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing
|
|
||||||
error. We now raise immediately with a more helpful message.
|
|
|
@ -1 +0,0 @@
|
||||||
Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.
|
|
|
@ -1 +0,0 @@
|
||||||
The documentation is now built using Sphinx 5.x (up from 3.x previously).
|
|
|
@ -1 +0,0 @@
|
||||||
Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`.
|
|
|
@ -1,3 +0,0 @@
|
||||||
On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML.
|
|
||||||
|
|
||||||
:mod:`tomli`` is no longer a dependency on Python 3.11.
|
|
|
@ -1 +0,0 @@
|
||||||
Display assertion message without escaped newline characters with ``-vv``.
|
|
|
@ -1 +0,0 @@
|
||||||
Improved error message that is shown when no collector is found for a given file.
|
|
|
@ -1 +0,0 @@
|
||||||
Some coloring has been added to the short test summary.
|
|
|
@ -1 +0,0 @@
|
||||||
Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``.
|
|
|
@ -1 +0,0 @@
|
||||||
Normalize the help description of all command-line options.
|
|
|
@ -1,10 +0,0 @@
|
||||||
The functionality for running tests written for ``nose`` has been officially deprecated.
|
|
||||||
|
|
||||||
This includes:
|
|
||||||
|
|
||||||
* Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support.
|
|
||||||
* Setup/teardown using the `@with_setup <with-setup-nose>`_ decorator.
|
|
||||||
|
|
||||||
For more details, consult the :ref:`deprecation docs <nose-deprecation>`.
|
|
||||||
|
|
||||||
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
|
|
@ -1 +0,0 @@
|
||||||
Added shell-style wildcard support to ``testpaths``.
|
|
|
@ -1 +0,0 @@
|
||||||
Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix default encoding warning (``EncodingWarning``) in ``cacheprovider``
|
|
|
@ -1 +0,0 @@
|
||||||
Display full crash messages in ``short test summary info``, when runng in a CI environment.
|
|
|
@ -1,4 +0,0 @@
|
||||||
Improve the error message when we attempt to access a fixture that has been
|
|
||||||
torn down.
|
|
||||||
Add an additional sentence to the docstring explaining when it's not a good
|
|
||||||
idea to call getfixturevalue.
|
|
|
@ -1 +0,0 @@
|
||||||
Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``.
|
|
|
@ -6,6 +6,9 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-7.2.2
|
||||||
|
release-7.2.1
|
||||||
|
release-7.2.0
|
||||||
release-7.1.3
|
release-7.1.3
|
||||||
release-7.1.2
|
release-7.1.2
|
||||||
release-7.1.1
|
release-7.1.1
|
||||||
|
|
|
@ -0,0 +1,93 @@
|
||||||
|
pytest-7.2.0
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
The pytest team is proud to announce the 7.2.0 release!
|
||||||
|
|
||||||
|
This release contains new features, improvements, and bug fixes,
|
||||||
|
the full list of changes is available in the changelog:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/stable/changelog.html
|
||||||
|
|
||||||
|
For complete documentation, please visit:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/stable/
|
||||||
|
|
||||||
|
As usual, you can upgrade from PyPI via:
|
||||||
|
|
||||||
|
pip install -U pytest
|
||||||
|
|
||||||
|
Thanks to all of the contributors to this release:
|
||||||
|
|
||||||
|
* Aaron Berdy
|
||||||
|
* Adam Turner
|
||||||
|
* Albert Villanova del Moral
|
||||||
|
* Alice Purcell
|
||||||
|
* Anthony Sottile
|
||||||
|
* Anton Yakutovich
|
||||||
|
* Babak Keyvani
|
||||||
|
* Brandon Chinn
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Chanvin Xiao
|
||||||
|
* Cheuk Ting Ho
|
||||||
|
* Chris Wheeler
|
||||||
|
* EmptyRabbit
|
||||||
|
* Ezio Melotti
|
||||||
|
* Florian Best
|
||||||
|
* Florian Bruhin
|
||||||
|
* Fredrik Berndtsson
|
||||||
|
* Gabriel Landau
|
||||||
|
* Gergely Kalmár
|
||||||
|
* Hugo van Kemenade
|
||||||
|
* James Gerity
|
||||||
|
* John Litborn
|
||||||
|
* Jon Parise
|
||||||
|
* Kevin C
|
||||||
|
* Kian Eliasi
|
||||||
|
* MatthewFlamm
|
||||||
|
* Miro Hrončok
|
||||||
|
* Nate Meyvis
|
||||||
|
* Neil Girdhar
|
||||||
|
* Nhieuvu1802
|
||||||
|
* Nipunn Koorapati
|
||||||
|
* Ofek Lev
|
||||||
|
* Paul Müller
|
||||||
|
* Paul Reece
|
||||||
|
* Pax
|
||||||
|
* Pete Baughman
|
||||||
|
* Peyman Salehi
|
||||||
|
* Philipp A
|
||||||
|
* Ran Benita
|
||||||
|
* Robert O'Shea
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* Rowin
|
||||||
|
* Ruth Comer
|
||||||
|
* Samuel Colvin
|
||||||
|
* Samuel Gaist
|
||||||
|
* Sandro Tosi
|
||||||
|
* Shantanu
|
||||||
|
* Simon K
|
||||||
|
* Stephen Rosen
|
||||||
|
* Sviatoslav Sydorenko
|
||||||
|
* Tatiana Ovary
|
||||||
|
* Thierry Moisan
|
||||||
|
* Thomas Grainger
|
||||||
|
* Tim Hoffmann
|
||||||
|
* Tobias Diez
|
||||||
|
* Tony Narlock
|
||||||
|
* Vivaan Verma
|
||||||
|
* Wolfremium
|
||||||
|
* Zac Hatfield-Dodds
|
||||||
|
* Zach OBrien
|
||||||
|
* aizpurua23a
|
||||||
|
* gresm
|
||||||
|
* holesch
|
||||||
|
* itxasos23
|
||||||
|
* johnkangw
|
||||||
|
* skhomuti
|
||||||
|
* sommersoft
|
||||||
|
* wodny
|
||||||
|
* zx.qiu
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -0,0 +1,25 @@
|
||||||
|
pytest-7.2.1
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 7.2.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
|
||||||
|
* Daniel Valenzuela
|
||||||
|
* Kadino
|
||||||
|
* Prerak Patel
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* Santiago Castro
|
||||||
|
* s-padmanaban
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -0,0 +1,25 @@
|
||||||
|
pytest-7.2.2
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 7.2.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:
|
||||||
|
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Garvit Shubham
|
||||||
|
* Mahesh Vashishtha
|
||||||
|
* Ramsey
|
||||||
|
* Ronny Pfannschmidt
|
||||||
|
* Teejay
|
||||||
|
* q0w
|
||||||
|
* vin01
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -22,7 +22,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
cachedir: .pytest_cache
|
cachedir: .pytest_cache
|
||||||
rootdir: /home/sweet/project
|
rootdir: /home/sweet/project
|
||||||
collected 0 items
|
collected 0 items
|
||||||
cache -- .../_pytest/cacheprovider.py:510
|
cache -- .../_pytest/cacheprovider.py:509
|
||||||
Return a cache object that can persist state between testing sessions.
|
Return a cache object that can persist state between testing sessions.
|
||||||
|
|
||||||
cache.get(key, default)
|
cache.get(key, default)
|
||||||
|
@ -33,7 +33,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
|
|
||||||
Values can be any object handled by the json stdlib module.
|
Values can be any object handled by the json stdlib module.
|
||||||
|
|
||||||
capsys -- .../_pytest/capture.py:878
|
capsys -- .../_pytest/capture.py:905
|
||||||
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
|
The captured output is made available via ``capsys.readouterr()`` method
|
||||||
|
@ -51,7 +51,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
captured = capsys.readouterr()
|
captured = capsys.readouterr()
|
||||||
assert captured.out == "hello\n"
|
assert captured.out == "hello\n"
|
||||||
|
|
||||||
capsysbinary -- .../_pytest/capture.py:906
|
capsysbinary -- .../_pytest/capture.py:933
|
||||||
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()``
|
The captured output is made available via ``capsysbinary.readouterr()``
|
||||||
|
@ -69,7 +69,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
captured = capsysbinary.readouterr()
|
captured = capsysbinary.readouterr()
|
||||||
assert captured.out == b"hello\n"
|
assert captured.out == b"hello\n"
|
||||||
|
|
||||||
capfd -- .../_pytest/capture.py:934
|
capfd -- .../_pytest/capture.py:961
|
||||||
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
|
The captured output is made available via ``capfd.readouterr()`` method
|
||||||
|
@ -87,7 +87,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
captured = capfd.readouterr()
|
captured = capfd.readouterr()
|
||||||
assert captured.out == "hello\n"
|
assert captured.out == "hello\n"
|
||||||
|
|
||||||
capfdbinary -- .../_pytest/capture.py:962
|
capfdbinary -- .../_pytest/capture.py:989
|
||||||
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
|
The captured output is made available via ``capfd.readouterr()`` method
|
||||||
|
@ -105,7 +105,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
captured = capfdbinary.readouterr()
|
captured = capfdbinary.readouterr()
|
||||||
assert captured.out == b"hello\n"
|
assert captured.out == b"hello\n"
|
||||||
|
|
||||||
doctest_namespace [session scope] -- .../_pytest/doctest.py:735
|
doctest_namespace [session scope] -- .../_pytest/doctest.py:738
|
||||||
Fixture that returns a :py:class:`dict` that will be injected into the
|
Fixture that returns a :py:class:`dict` that will be injected into the
|
||||||
namespace of doctests.
|
namespace of doctests.
|
||||||
|
|
||||||
|
@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
|
|
||||||
For more details: :ref:`doctest_namespace`.
|
For more details: :ref:`doctest_namespace`.
|
||||||
|
|
||||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1344
|
pytestconfig [session scope] -- .../_pytest/fixtures.py:1356
|
||||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||||
object.
|
object.
|
||||||
|
|
||||||
|
@ -163,7 +163,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
record_testsuite_property("ARCH", "PPC")
|
record_testsuite_property("ARCH", "PPC")
|
||||||
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
record_testsuite_property("STORAGE_TYPE", "CEPH")
|
||||||
|
|
||||||
``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped.
|
:param name:
|
||||||
|
The property name.
|
||||||
|
:param value:
|
||||||
|
The property value. Will be converted to a string.
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
@ -193,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
|
|
||||||
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
||||||
|
|
||||||
caplog -- .../_pytest/logging.py:487
|
caplog -- .../_pytest/logging.py:491
|
||||||
Access and control log capturing.
|
Access and control log capturing.
|
||||||
|
|
||||||
Captured logs are available through the following properties/methods::
|
Captured logs are available through the following properties/methods::
|
||||||
|
@ -228,16 +231,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
To undo modifications done by the fixture in a contained scope,
|
To undo modifications done by the fixture in a contained scope,
|
||||||
use :meth:`context() <pytest.MonkeyPatch.context>`.
|
use :meth:`context() <pytest.MonkeyPatch.context>`.
|
||||||
|
|
||||||
recwarn -- .../_pytest/recwarn.py:29
|
recwarn -- .../_pytest/recwarn.py:30
|
||||||
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
||||||
|
|
||||||
See https://docs.python.org/library/how-to/capture-warnings.html for information
|
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
|
||||||
on warning categories.
|
on warning categories.
|
||||||
|
|
||||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:184
|
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:188
|
||||||
Return a :class:`pytest.TempPathFactory` instance for the test session.
|
Return a :class:`pytest.TempPathFactory` instance for the test session.
|
||||||
|
|
||||||
tmp_path -- .../_pytest/tmpdir.py:199
|
tmp_path -- .../_pytest/tmpdir.py:203
|
||||||
Return a temporary directory path object which is unique to each test
|
Return a temporary directory path object which is unique to each test
|
||||||
function invocation, created as a sub directory of the base temporary
|
function invocation, created as a sub directory of the base temporary
|
||||||
directory.
|
directory.
|
||||||
|
|
|
@ -28,6 +28,206 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 7.2.2 (2023-03-03)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#10533 <https://github.com/pytest-dev/pytest/issues/10533>`_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10592 <https://github.com/pytest-dev/pytest/issues/10592>`_: Fixed crash if `--cache-show` and `--help` are passed at the same time.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10597 <https://github.com/pytest-dev/pytest/issues/10597>`_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10626 <https://github.com/pytest-dev/pytest/issues/10626>`_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10660 <https://github.com/pytest-dev/pytest/issues/10660>`_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow
|
||||||
|
:code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#10690 <https://github.com/pytest-dev/pytest/issues/10690>`_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10721 <https://github.com/pytest-dev/pytest/issues/10721>`_: Fixed entry-points declaration in the documentation example using Hatch.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10753 <https://github.com/pytest-dev/pytest/issues/10753>`_: Changed wording of the module level skip to be very explicit
|
||||||
|
about not collecting tests and not executing the rest of the module.
|
||||||
|
|
||||||
|
|
||||||
|
pytest 7.2.1 (2023-01-13)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#10452 <https://github.com/pytest-dev/pytest/issues/10452>`_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10457 <https://github.com/pytest-dev/pytest/issues/10457>`_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10506 <https://github.com/pytest-dev/pytest/issues/10506>`_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir <rootdir>` on Windows.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10607 <https://github.com/pytest-dev/pytest/issues/10607>`_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10641 <https://github.com/pytest-dev/pytest/issues/10641>`_: Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache.
|
||||||
|
|
||||||
|
|
||||||
|
pytest 7.2.0 (2022-10-23)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Deprecations
|
||||||
|
------------
|
||||||
|
|
||||||
|
- `#10012 <https://github.com/pytest-dev/pytest/issues/10012>`_: Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10396 <https://github.com/pytest-dev/pytest/issues/10396>`_: pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4562 <https://github.com/pytest-dev/pytest/issues/4562>`_: Deprecate configuring hook specs/impls using attributes/marks.
|
||||||
|
|
||||||
|
Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`.
|
||||||
|
For more details, see the :ref:`docs <legacy-path-hooks-deprecated>`.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9886 <https://github.com/pytest-dev/pytest/issues/9886>`_: The functionality for running tests written for ``nose`` has been officially deprecated.
|
||||||
|
|
||||||
|
This includes:
|
||||||
|
|
||||||
|
* Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support.
|
||||||
|
* Setup/teardown using the `@with_setup <with-setup-nose>`_ decorator.
|
||||||
|
|
||||||
|
For more details, consult the :ref:`deprecation docs <nose-deprecation>`.
|
||||||
|
|
||||||
|
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
||||||
|
|
||||||
|
- `#7337 <https://github.com/pytest-dev/pytest/issues/7337>`_: A deprecation warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. The plan is to make returning non-`None` from tests an error in the future.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#9897 <https://github.com/pytest-dev/pytest/issues/9897>`_: Added shell-style wildcard support to ``testpaths``.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improvements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- `#10218 <https://github.com/pytest-dev/pytest/issues/10218>`_: ``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names,
|
||||||
|
instead of just ``list[str]`` and ``tuple[str, ...]``.
|
||||||
|
|
||||||
|
(Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a
|
||||||
|
comma-delimited name list, as before).
|
||||||
|
|
||||||
|
|
||||||
|
- `#10381 <https://github.com/pytest-dev/pytest/issues/10381>`_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#3426 <https://github.com/pytest-dev/pytest/issues/3426>`_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expressed instead.
|
||||||
|
|
||||||
|
|
||||||
|
- `#8508 <https://github.com/pytest-dev/pytest/issues/8508>`_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and
|
||||||
|
enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
|
||||||
|
|
||||||
|
|
||||||
|
- `#8646 <https://github.com/pytest-dev/pytest/issues/8646>`_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing
|
||||||
|
error. We now raise immediately with a more helpful message.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9741 <https://github.com/pytest-dev/pytest/issues/9741>`_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML.
|
||||||
|
|
||||||
|
:mod:`tomli` is no longer a dependency on Python 3.11.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9742 <https://github.com/pytest-dev/pytest/issues/9742>`_: Display assertion message without escaped newline characters with ``-vv``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9823 <https://github.com/pytest-dev/pytest/issues/9823>`_: Improved error message that is shown when no collector is found for a given file.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9873 <https://github.com/pytest-dev/pytest/issues/9873>`_: Some coloring has been added to the short test summary.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9883 <https://github.com/pytest-dev/pytest/issues/9883>`_: Normalize the help description of all command-line options.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9920 <https://github.com/pytest-dev/pytest/issues/9920>`_: Display full crash messages in ``short test summary info``, when running in a CI environment.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9987 <https://github.com/pytest-dev/pytest/issues/9987>`_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#10150 <https://github.com/pytest-dev/pytest/issues/10150>`_: :data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled.
|
||||||
|
|
||||||
|
|
||||||
|
- `#10382 <https://github.com/pytest-dev/pytest/issues/10382>`_: Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file.
|
||||||
|
|
||||||
|
|
||||||
|
- `#7792 <https://github.com/pytest-dev/pytest/issues/7792>`_: Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply.
|
||||||
|
|
||||||
|
When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse.
|
||||||
|
|
||||||
|
When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`pytest.Node.iter_markers` instead.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9159 <https://github.com/pytest-dev/pytest/issues/9159>`_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9877 <https://github.com/pytest-dev/pytest/issues/9877>`_: Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#10344 <https://github.com/pytest-dev/pytest/issues/10344>`_: Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9248 <https://github.com/pytest-dev/pytest/issues/9248>`_: The documentation is now built using Sphinx 5.x (up from 3.x previously).
|
||||||
|
|
||||||
|
|
||||||
|
- `#9291 <https://github.com/pytest-dev/pytest/issues/9291>`_: Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Trivial/Internal Changes
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
- `#10313 <https://github.com/pytest-dev/pytest/issues/10313>`_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for
|
||||||
|
type check and runtime purposes. Made `_pytest.doctest` use internal APIs
|
||||||
|
to avoid circular imports.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9906 <https://github.com/pytest-dev/pytest/issues/9906>`_: Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9910 <https://github.com/pytest-dev/pytest/issues/9910>`_: Fix default encoding warning (``EncodingWarning``) in ``cacheprovider``
|
||||||
|
|
||||||
|
|
||||||
|
- `#9984 <https://github.com/pytest-dev/pytest/issues/9984>`_: Improve the error message when we attempt to access a fixture that has been
|
||||||
|
torn down.
|
||||||
|
Add an additional sentence to the docstring explaining when it's not a good
|
||||||
|
idea to call ``getfixturevalue``.
|
||||||
|
|
||||||
|
|
||||||
pytest 7.1.3 (2022-08-31)
|
pytest 7.1.3 (2022-08-31)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -273,6 +273,9 @@ html_show_sourcelink = False
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = "pytestdoc"
|
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 --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1052,7 +1052,7 @@ that are then turned into proper test methods. Example:
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
def check(x, y):
|
def check(x, y):
|
||||||
assert x ** x == y
|
assert x**x == y
|
||||||
|
|
||||||
|
|
||||||
def test_squared():
|
def test_squared():
|
||||||
|
@ -1067,7 +1067,7 @@ This form of test function doesn't support fixtures properly, and users should s
|
||||||
|
|
||||||
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
|
@pytest.mark.parametrize("x, y", [(2, 4), (3, 9)])
|
||||||
def test_squared(x, y):
|
def test_squared(x, y):
|
||||||
assert x ** x == y
|
assert x**x == y
|
||||||
|
|
||||||
.. _internal classes accessed through node deprecated:
|
.. _internal classes accessed through node deprecated:
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ def b(a, order):
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def c(a, b, order):
|
def c(b, order):
|
||||||
order.append("c")
|
order.append("c")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -246,9 +246,9 @@ You can ask which markers exist for your test suite - the list includes our just
|
||||||
|
|
||||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
|
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
|
||||||
|
|
||||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.
|
||||||
|
|
||||||
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.
|
||||||
|
|
||||||
|
|
||||||
For an example on how to add and work with markers from a plugin, see
|
For an example on how to add and work with markers from a plugin, see
|
||||||
|
@ -438,9 +438,9 @@ The ``--markers`` option always gives you a list of available markers:
|
||||||
|
|
||||||
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
|
@pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures
|
||||||
|
|
||||||
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible.
|
@pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.
|
||||||
|
|
||||||
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible.
|
@pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead.
|
||||||
|
|
||||||
|
|
||||||
.. _`passing callables to custom markers`:
|
.. _`passing callables to custom markers`:
|
||||||
|
@ -611,7 +611,7 @@ then you will see two tests skipped and two executed tests as expected:
|
||||||
test_plat.py s.s. [100%]
|
test_plat.py s.s. [100%]
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [2] conftest.py:12: cannot run on platform linux
|
SKIPPED [2] conftest.py:13: cannot run on platform linux
|
||||||
======================= 2 passed, 2 skipped in 0.12s =======================
|
======================= 2 passed, 2 skipped in 0.12s =======================
|
||||||
|
|
||||||
Note that if you specify a platform via the marker-command line option like this:
|
Note that if you specify a platform via the marker-command line option like this:
|
||||||
|
|
|
@ -504,9 +504,9 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
sssssssssssssssssssssssssss [100%]
|
sssssssssssssssssssssssssss [100%]
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [9] multipython.py:29: 'python3.5' not found
|
SKIPPED [9] multipython.py:69: 'python3.5' not found
|
||||||
SKIPPED [9] multipython.py:29: 'python3.6' not found
|
SKIPPED [9] multipython.py:69: 'python3.6' not found
|
||||||
SKIPPED [9] multipython.py:29: 'python3.7' not found
|
SKIPPED [9] multipython.py:69: 'python3.7' not found
|
||||||
27 skipped in 0.12s
|
27 skipped in 0.12s
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
|
@ -574,7 +574,7 @@ If you run this with reporting for skips enabled:
|
||||||
test_module.py .s [100%]
|
test_module.py .s [100%]
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2'
|
SKIPPED [1] test_module.py:3: could not import 'opt2': No module named 'opt2'
|
||||||
======================= 1 passed, 1 skipped in 0.12s =======================
|
======================= 1 passed, 1 skipped in 0.12s =======================
|
||||||
|
|
||||||
You'll see that we don't have an ``opt2`` module and thus the second test run
|
You'll see that we don't have an ``opt2`` module and thus the second test run
|
||||||
|
|
|
@ -661,8 +661,7 @@ If we run this:
|
||||||
|
|
||||||
test_step.py:11: AssertionError
|
test_step.py:11: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
XFAIL test_step.py::TestUserHandling::test_deletion
|
XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification)
|
||||||
reason: previous test failed (test_modification)
|
|
||||||
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
|
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
|
||||||
|
|
||||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||||
|
|
|
@ -24,8 +24,9 @@ The first few lines should look like this:
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "PACKAGENAME"
|
name = "PACKAGENAME"
|
||||||
|
version = "PACKAGEVERSION"
|
||||||
|
|
||||||
where ``PACKAGENAME`` is the name of your package.
|
where ``PACKAGENAME`` and ``PACKAGEVERSION`` are the name and version of your package respectively.
|
||||||
|
|
||||||
You can then install your package in "editable" mode by running from the same directory:
|
You can then install your package in "editable" mode by running from the same directory:
|
||||||
|
|
||||||
|
@ -270,8 +271,8 @@ tox
|
||||||
|
|
||||||
Once you are done with your work and want to make sure that your actual
|
Once you are done with your work and want to make sure that your actual
|
||||||
package passes all tests you may want to look into :doc:`tox <tox:index>`, the
|
package passes all tests you may want to look into :doc:`tox <tox:index>`, the
|
||||||
virtualenv test automation tool and its :doc:`pytest support <tox:example/pytest>`.
|
virtualenv test automation tool.
|
||||||
tox helps you to setup virtualenv environments with pre-defined
|
``tox`` helps you to setup virtualenv environments with pre-defined
|
||||||
dependencies and then executing a pre-configured test command with
|
dependencies and then executing a pre-configured test command with
|
||||||
options. It will run tests against the installed package and not
|
options. It will run tests against the installed package and not
|
||||||
against your source code checkout, helping to detect packaging
|
against your source code checkout, helping to detect packaging
|
||||||
|
|
|
@ -16,7 +16,7 @@ import process can be controlled through the ``--import-mode`` command-line flag
|
||||||
these values:
|
these values:
|
||||||
|
|
||||||
* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
|
* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
|
||||||
of :py:data:`sys.path` if not already there, and then imported with the :func:`__import__ <__import__>` builtin.
|
of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module <importlib.import_module>` function.
|
||||||
|
|
||||||
This requires test module names to be unique when the test directory tree is not arranged in
|
This requires test module names to be unique when the test directory tree is not arranged in
|
||||||
packages, because the modules will put in :py:data:`sys.modules` after importing.
|
packages, because the modules will put in :py:data:`sys.modules` after importing.
|
||||||
|
@ -24,7 +24,7 @@ these values:
|
||||||
This is the classic mechanism, dating back from the time Python 2 was still supported.
|
This is the classic mechanism, dating back from the time Python 2 was still supported.
|
||||||
|
|
||||||
* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
|
* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
|
||||||
there, and imported with ``__import__``.
|
there, and imported with :func:`importlib.import_module <importlib.import_module>`.
|
||||||
|
|
||||||
This better allows to run test modules against installed versions of a package even if the
|
This better allows to run test modules against installed versions of a package even if the
|
||||||
package under test has the same import root. For example:
|
package under test has the same import root. For example:
|
||||||
|
@ -43,7 +43,7 @@ these values:
|
||||||
Same as ``prepend``, requires test module names to be unique when the test directory tree is
|
Same as ``prepend``, requires test module names to be unique when the test directory tree is
|
||||||
not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing.
|
not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing.
|
||||||
|
|
||||||
* ``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`.
|
* ``importlib``: new in pytest-6.0, this mode uses more fine control mechanisms provided by :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.
|
For this reason this doesn't require test module names to be unique.
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ Install ``pytest``
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ pytest --version
|
$ pytest --version
|
||||||
pytest 7.1.3
|
pytest 7.2.2
|
||||||
|
|
||||||
.. _`simpletest`:
|
.. _`simpletest`:
|
||||||
|
|
||||||
|
|
|
@ -233,7 +233,7 @@ If you run this command for the first time, you can see the print statement:
|
||||||
> assert mydata == 23
|
> assert mydata == 23
|
||||||
E assert 42 == 23
|
E assert 42 == 23
|
||||||
|
|
||||||
test_caching.py:20: AssertionError
|
test_caching.py:19: AssertionError
|
||||||
-------------------------- Captured stdout setup ---------------------------
|
-------------------------- Captured stdout setup ---------------------------
|
||||||
running expensive computation...
|
running expensive computation...
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
|
@ -256,7 +256,7 @@ the cache and nothing will be printed:
|
||||||
> assert mydata == 23
|
> assert mydata == 23
|
||||||
E assert 42 == 23
|
E assert 42 == 23
|
||||||
|
|
||||||
test_caching.py:20: AssertionError
|
test_caching.py:19: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
FAILED test_caching.py::test_function - assert 42 == 23
|
FAILED test_caching.py::test_function - assert 42 == 23
|
||||||
1 failed in 0.12s
|
1 failed in 0.12s
|
||||||
|
|
|
@ -109,6 +109,18 @@ When a warning matches more than one option in the list, the action for the last
|
||||||
is performed.
|
is performed.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are
|
||||||
|
similar in structure, but each configuration option interprets its filter
|
||||||
|
differently. For example, *message* in ``filterwarnings`` is a string containing a
|
||||||
|
regular expression that the start of the warning message must match,
|
||||||
|
case-insensitively, while *message* in ``-W`` is a literal string that the start of
|
||||||
|
the warning message must contain (case-insensitively), ignoring any whitespace at
|
||||||
|
the start or end of message. Consult the `warning filter`_ documentation for more
|
||||||
|
details.
|
||||||
|
|
||||||
|
|
||||||
.. _`filterwarnings`:
|
.. _`filterwarnings`:
|
||||||
|
|
||||||
``@pytest.mark.filterwarnings``
|
``@pytest.mark.filterwarnings``
|
||||||
|
@ -270,20 +282,34 @@ which works in a similar manner to :ref:`raises <assertraises>` (except that
|
||||||
warnings.warn("my warning", UserWarning)
|
warnings.warn("my warning", UserWarning)
|
||||||
|
|
||||||
The test will fail if the warning in question is not raised. Use the keyword
|
The test will fail if the warning in question is not raised. Use the keyword
|
||||||
argument ``match`` to assert that the warning matches a text or regex::
|
argument ``match`` to assert that the warning matches a text or regex.
|
||||||
|
To match a literal string that may contain regular expression metacharacters like ``(`` or ``.``, the pattern can
|
||||||
|
first be escaped with ``re.escape``.
|
||||||
|
|
||||||
>>> with warns(UserWarning, match='must be 0 or None'):
|
Some examples:
|
||||||
|
|
||||||
|
.. code-block:: pycon
|
||||||
|
|
||||||
|
|
||||||
|
>>> with warns(UserWarning, match="must be 0 or None"):
|
||||||
... warnings.warn("value must be 0 or None", UserWarning)
|
... warnings.warn("value must be 0 or None", UserWarning)
|
||||||
|
...
|
||||||
|
|
||||||
>>> with warns(UserWarning, match=r'must be \d+$'):
|
>>> with warns(UserWarning, match=r"must be \d+$"):
|
||||||
... warnings.warn("value must be 42", UserWarning)
|
... warnings.warn("value must be 42", UserWarning)
|
||||||
|
...
|
||||||
|
|
||||||
>>> with warns(UserWarning, match=r'must be \d+$'):
|
>>> with warns(UserWarning, match=r"must be \d+$"):
|
||||||
... warnings.warn("this is not here", UserWarning)
|
... warnings.warn("this is not here", UserWarning)
|
||||||
|
...
|
||||||
Traceback (most recent call last):
|
Traceback (most recent call last):
|
||||||
...
|
...
|
||||||
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
|
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
|
||||||
|
|
||||||
|
>>> with warns(UserWarning, match=re.escape("issue with foo() func")):
|
||||||
|
... warnings.warn("issue with foo() func")
|
||||||
|
...
|
||||||
|
|
||||||
You can also call :func:`pytest.warns` on a function or code string:
|
You can also call :func:`pytest.warns` on a function or code string:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
|
@ -1237,7 +1237,6 @@ If the data created by the factory requires managing, the fixture can take care
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def make_customer_record():
|
def make_customer_record():
|
||||||
|
|
||||||
created_records = []
|
created_records = []
|
||||||
|
|
||||||
def _make_customer_record(name):
|
def _make_customer_record(name):
|
||||||
|
|
|
@ -135,10 +135,10 @@ This can be done in our test file by defining a class to represent ``r``.
|
||||||
# this is the previous code block example
|
# this is the previous code block example
|
||||||
import app
|
import app
|
||||||
|
|
||||||
|
|
||||||
# custom class to be the mock return value
|
# custom class to be the mock return value
|
||||||
# will override the requests.Response returned from requests.get
|
# will override the requests.Response returned from requests.get
|
||||||
class MockResponse:
|
class MockResponse:
|
||||||
|
|
||||||
# mock json() method always returns a specific testing dictionary
|
# mock json() method always returns a specific testing dictionary
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def json():
|
def json():
|
||||||
|
@ -146,7 +146,6 @@ This can be done in our test file by defining a class to represent ``r``.
|
||||||
|
|
||||||
|
|
||||||
def test_get_json(monkeypatch):
|
def test_get_json(monkeypatch):
|
||||||
|
|
||||||
# Any arguments may be passed and mock_get() will always return our
|
# Any arguments may be passed and mock_get() will always return our
|
||||||
# mocked object, which only has the .json() method.
|
# mocked object, which only has the .json() method.
|
||||||
def mock_get(*args, **kwargs):
|
def mock_get(*args, **kwargs):
|
||||||
|
@ -181,6 +180,7 @@ This mock can be shared across tests using a ``fixture``:
|
||||||
# app.py that includes the get_json() function
|
# app.py that includes the get_json() function
|
||||||
import app
|
import app
|
||||||
|
|
||||||
|
|
||||||
# custom class to be the mock return value of requests.get()
|
# custom class to be the mock return value of requests.get()
|
||||||
class MockResponse:
|
class MockResponse:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -358,7 +358,6 @@ For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific
|
||||||
|
|
||||||
|
|
||||||
def test_connection(monkeypatch):
|
def test_connection(monkeypatch):
|
||||||
|
|
||||||
# Patch the values of DEFAULT_CONFIG to specific
|
# Patch the values of DEFAULT_CONFIG to specific
|
||||||
# testing values only for this test.
|
# testing values only for this test.
|
||||||
monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")
|
monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user")
|
||||||
|
@ -383,7 +382,6 @@ You can use the :py:meth:`monkeypatch.delitem <MonkeyPatch.delitem>` to remove v
|
||||||
|
|
||||||
|
|
||||||
def test_missing_user(monkeypatch):
|
def test_missing_user(monkeypatch):
|
||||||
|
|
||||||
# patch the DEFAULT_CONFIG t be missing the 'user' key
|
# patch the DEFAULT_CONFIG t be missing the 'user' key
|
||||||
monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
|
monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False)
|
||||||
|
|
||||||
|
@ -404,6 +402,7 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||||
# app.py with the connection string function
|
# app.py with the connection string function
|
||||||
import app
|
import app
|
||||||
|
|
||||||
|
|
||||||
# all of the mocks are moved into separated fixtures
|
# all of the mocks are moved into separated fixtures
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_test_user(monkeypatch):
|
def mock_test_user(monkeypatch):
|
||||||
|
@ -425,7 +424,6 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||||
|
|
||||||
# tests reference only the fixture mocks that are needed
|
# tests reference only the fixture mocks that are needed
|
||||||
def test_connection(mock_test_user, mock_test_database):
|
def test_connection(mock_test_user, mock_test_database):
|
||||||
|
|
||||||
expected = "User Id=test_user; Location=test_db;"
|
expected = "User Id=test_user; Location=test_db;"
|
||||||
|
|
||||||
result = app.create_connection_string()
|
result = app.create_connection_string()
|
||||||
|
@ -433,7 +431,6 @@ separate fixtures for each potential mock and reference them in the needed tests
|
||||||
|
|
||||||
|
|
||||||
def test_missing_user(mock_missing_default_user):
|
def test_missing_user(mock_missing_default_user):
|
||||||
|
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
_ = app.create_connection_string()
|
_ = app.create_connection_string()
|
||||||
|
|
||||||
|
|
|
@ -349,8 +349,7 @@ Example:
|
||||||
test_example.py:14: AssertionError
|
test_example.py:14: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [1] test_example.py:22: skipping this test
|
SKIPPED [1] test_example.py:22: skipping this test
|
||||||
XFAIL test_example.py::test_xfail
|
XFAIL test_example.py::test_xfail - reason: xfailing this test
|
||||||
reason: xfailing this test
|
|
||||||
XPASS test_example.py::test_xpass always xfail
|
XPASS test_example.py::test_xpass always xfail
|
||||||
ERROR test_example.py::test_error - assert 0
|
ERROR test_example.py::test_error - assert 0
|
||||||
FAILED test_example.py::test_fail - assert 0
|
FAILED test_example.py::test_fail - assert 0
|
||||||
|
|
|
@ -157,7 +157,7 @@ the ``self.db`` values in the traceback:
|
||||||
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
|
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_unittest_db.py:10: AssertionError
|
test_unittest_db.py:11: AssertionError
|
||||||
___________________________ MyTest.test_method2 ____________________________
|
___________________________ MyTest.test_method2 ____________________________
|
||||||
|
|
||||||
self = <test_unittest_db.MyTest testMethod=test_method2>
|
self = <test_unittest_db.MyTest testMethod=test_method2>
|
||||||
|
@ -167,7 +167,7 @@ the ``self.db`` values in the traceback:
|
||||||
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
|
E AssertionError: <conftest.db_class.<locals>.DummyDB object at 0xdeadbeef0001>
|
||||||
E assert 0
|
E assert 0
|
||||||
|
|
||||||
test_unittest_db.py:13: AssertionError
|
test_unittest_db.py:14: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
|
FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: <conft...
|
||||||
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
|
FAILED test_unittest_db.py::MyTest::test_method2 - AssertionError: <conft...
|
||||||
|
|
|
@ -249,6 +249,7 @@ and use pytest_addoption as follows:
|
||||||
|
|
||||||
# contents of hooks.py
|
# contents of hooks.py
|
||||||
|
|
||||||
|
|
||||||
# Use firstresult=True because we only want one plugin to define this
|
# Use firstresult=True because we only want one plugin to define this
|
||||||
# default value
|
# default value
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
|
|
|
@ -167,13 +167,8 @@ it in your ``pyproject.toml`` file.
|
||||||
"Framework :: Pytest",
|
"Framework :: Pytest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools]
|
[project.entry-points.pytest11]
|
||||||
packages = ["myproject"]
|
myproject = "myproject.pluginmodule"
|
||||||
|
|
||||||
[project.entry_points]
|
|
||||||
pytest11 = [
|
|
||||||
"myproject = myproject.pluginmodule",
|
|
||||||
]
|
|
||||||
|
|
||||||
If a package is installed this way, ``pytest`` will load
|
If a package is installed this way, ``pytest`` will load
|
||||||
``myproject.pluginmodule`` as a plugin which can define
|
``myproject.pluginmodule`` as a plugin which can define
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
:orphan:
|
:orphan:
|
||||||
|
|
||||||
.. sidebar:: Next Open Trainings
|
..
|
||||||
|
.. 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
|
||||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany
|
|
||||||
|
|
||||||
Also see :doc:`previous talks and blogposts <talks>`.
|
Also see :doc:`previous talks and blogposts <talks>`.
|
||||||
|
|
||||||
.. _features:
|
.. _features:
|
||||||
|
|
||||||
|
|
|
@ -335,7 +335,7 @@ For example:
|
||||||
|
|
||||||
.. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py
|
.. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py
|
||||||
|
|
||||||
If we map out what depends on what, we get something that look like this:
|
If we map out what depends on what, we get something that looks like this:
|
||||||
|
|
||||||
.. image:: /example/fixtures/test_fixtures_order_dependencies.*
|
.. image:: /example/fixtures/test_fixtures_order_dependencies.*
|
||||||
:align: center
|
:align: center
|
||||||
|
|
|
@ -1047,6 +1047,14 @@ Environment Variables
|
||||||
|
|
||||||
Environment variables that can be used to change pytest's behavior.
|
Environment variables that can be used to change pytest's behavior.
|
||||||
|
|
||||||
|
.. envvar:: CI
|
||||||
|
|
||||||
|
When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to ``BUILD_NUMBER`` variable.
|
||||||
|
|
||||||
|
.. envvar:: BUILD_NUMBER
|
||||||
|
|
||||||
|
When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to CI variable.
|
||||||
|
|
||||||
.. envvar:: PYTEST_ADDOPTS
|
.. envvar:: PYTEST_ADDOPTS
|
||||||
|
|
||||||
This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given
|
This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given
|
||||||
|
@ -1759,12 +1767,12 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
$ pytest --help
|
$ pytest --help
|
||||||
usage: pytest [options] [file_or_dir] [file_or_dir] [...]
|
usage: pytest [options] [file_or_dir] [file_or_dir] [...]
|
||||||
|
|
||||||
Positional arguments:
|
positional arguments:
|
||||||
file_or_dir
|
file_or_dir
|
||||||
|
|
||||||
General:
|
general:
|
||||||
-k EXPRESSION Only run tests which match the given substring
|
-k EXPRESSION Only run tests which match the given substring
|
||||||
expression. An expression is a python evaluatable
|
expression. An expression is a Python evaluatable
|
||||||
expression where all names are substring-matched
|
expression where all names are substring-matched
|
||||||
against test names and their parent classes.
|
against test names and their parent classes.
|
||||||
Example: -k 'test_method or test_other' matches all
|
Example: -k 'test_method or test_other' matches all
|
||||||
|
@ -1778,9 +1786,9 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
'extra_keyword_matches' set, as well as functions
|
'extra_keyword_matches' set, as well as functions
|
||||||
which have names assigned directly to them. The
|
which have names assigned directly to them. The
|
||||||
matching is case-insensitive.
|
matching is case-insensitive.
|
||||||
-m MARKEXPR Only run tests matching given mark expression.
|
-m MARKEXPR Only run tests matching given mark expression. For
|
||||||
For example: -m 'mark1 and not mark2'.
|
example: -m 'mark1 and not mark2'.
|
||||||
--markers Show markers (builtin, plugin and per-project ones)
|
--markers show markers (builtin, plugin and per-project ones).
|
||||||
-x, --exitfirst Exit instantly on first error or failed test
|
-x, --exitfirst Exit instantly on first error or failed test
|
||||||
--fixtures, --funcargs
|
--fixtures, --funcargs
|
||||||
Show available fixtures, sorted by plugin appearance
|
Show available fixtures, sorted by plugin appearance
|
||||||
|
@ -1790,18 +1798,18 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
KeyboardInterrupt
|
KeyboardInterrupt
|
||||||
--pdbcls=modulename:classname
|
--pdbcls=modulename:classname
|
||||||
Specify a custom interactive Python debugger for use
|
Specify a custom interactive Python debugger for use
|
||||||
with --pdb. For example:
|
with --pdb.For example:
|
||||||
--pdbcls=IPython.terminal.debugger:TerminalPdb
|
--pdbcls=IPython.terminal.debugger:TerminalPdb
|
||||||
--trace Immediately break when running each test
|
--trace Immediately break when running each test
|
||||||
--capture=method Per-test capturing method: one of fd|sys|no|tee-sys.
|
--capture=method Per-test capturing method: one of fd|sys|no|tee-sys
|
||||||
-s Shortcut for --capture=no.
|
-s Shortcut for --capture=no
|
||||||
--runxfail Report the results of xfail tests as if they were
|
--runxfail Report the results of xfail tests as if they were
|
||||||
not marked
|
not marked
|
||||||
--lf, --last-failed Rerun only the tests that failed at the last run (or
|
--lf, --last-failed Rerun only the tests that failed at the last run (or
|
||||||
all if none failed)
|
all if none failed)
|
||||||
--ff, --failed-first Run all tests, but run the last failures first
|
--ff, --failed-first Run all tests, but run the last failures first. This
|
||||||
This may re-order tests and thus lead to repeated
|
may re-order tests and thus lead to repeated fixture
|
||||||
fixture setup/teardown
|
setup/teardown.
|
||||||
--nf, --new-first Run tests from new files first, then the rest of the
|
--nf, --new-first Run tests from new files first, then the rest of the
|
||||||
tests sorted by file mtime
|
tests sorted by file mtime
|
||||||
--cache-show=[CACHESHOW]
|
--cache-show=[CACHESHOW]
|
||||||
|
@ -1815,11 +1823,10 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
test next time
|
test next time
|
||||||
--sw-skip, --stepwise-skip
|
--sw-skip, --stepwise-skip
|
||||||
Ignore the first failing test but stop on the next
|
Ignore the first failing test but stop on the next
|
||||||
failing test.
|
failing test. Implicitly enables --stepwise.
|
||||||
implicitly enables --stepwise.
|
|
||||||
|
|
||||||
Reporting:
|
Reporting:
|
||||||
--durations=N show N slowest setup/test durations (N=0 for all)
|
--durations=N Show N slowest setup/test durations (N=0 for all)
|
||||||
--durations-min=N Minimal duration in seconds for inclusion in slowest
|
--durations-min=N Minimal duration in seconds for inclusion in slowest
|
||||||
list. Default: 0.005.
|
list. Default: 0.005.
|
||||||
-v, --verbose Increase verbosity
|
-v, --verbose Increase verbosity
|
||||||
|
@ -1836,8 +1843,10 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
--disable-warnings, --disable-pytest-warnings
|
--disable-warnings, --disable-pytest-warnings
|
||||||
Disable warnings summary
|
Disable warnings summary
|
||||||
-l, --showlocals Show locals in tracebacks (disabled by default)
|
-l, --showlocals Show locals in tracebacks (disabled by default)
|
||||||
|
--no-showlocals Hide locals in tracebacks (negate --showlocals
|
||||||
|
passed through addopts)
|
||||||
--tb=style Traceback print mode
|
--tb=style Traceback print mode
|
||||||
(auto/long/short/line/native/no).
|
(auto/long/short/line/native/no)
|
||||||
--show-capture={no,stdout,stderr,log,all}
|
--show-capture={no,stdout,stderr,log,all}
|
||||||
Controls how captured stdout/stderr/log is shown on
|
Controls how captured stdout/stderr/log is shown on
|
||||||
failed tests. Default: all.
|
failed tests. Default: all.
|
||||||
|
@ -1863,15 +1872,14 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
-c file Load configuration from `file` instead of trying to
|
-c file Load configuration from `file` instead of trying to
|
||||||
locate one of the implicit configuration files
|
locate one of the implicit configuration files
|
||||||
--continue-on-collection-errors
|
--continue-on-collection-errors
|
||||||
Force test execution even if collection errors
|
Force test execution even if collection errors occur
|
||||||
occur
|
|
||||||
--rootdir=ROOTDIR Define root directory for tests. Can be relative
|
--rootdir=ROOTDIR Define root directory for tests. Can be relative
|
||||||
path: 'root_dir', './root_dir',
|
path: 'root_dir', './root_dir',
|
||||||
'root_dir/another_dir/'; absolute path:
|
'root_dir/another_dir/'; absolute path:
|
||||||
'/home/user/root_dir'; path with variables:
|
'/home/user/root_dir'; path with variables:
|
||||||
'$HOME/root_dir'.
|
'$HOME/root_dir'.
|
||||||
|
|
||||||
Collection:
|
collection:
|
||||||
--collect-only, --co Only collect tests, don't execute them
|
--collect-only, --co Only collect tests, don't execute them
|
||||||
--pyargs Try to interpret all arguments as Python packages
|
--pyargs Try to interpret all arguments as Python packages
|
||||||
--ignore=path Ignore path during collection (multi-allowed)
|
--ignore=path Ignore path during collection (multi-allowed)
|
||||||
|
@ -1899,27 +1907,24 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
For a given doctest, continue to run after the first
|
For a given doctest, continue to run after the first
|
||||||
failure
|
failure
|
||||||
|
|
||||||
Test session debugging and configuration:
|
test session debugging and configuration:
|
||||||
--basetemp=dir Base temporary directory for this test run. (Warning:
|
--basetemp=dir Base temporary directory for this test run.
|
||||||
this directory is removed if it exists.)
|
(Warning: this directory is removed if it exists.)
|
||||||
-V, --version Display pytest version and information about
|
-V, --version Display pytest version and information about
|
||||||
plugins. When given twice, also display information
|
plugins. When given twice, also display information
|
||||||
about plugins.
|
about plugins.
|
||||||
-h, --help Show help message and configuration info
|
-h, --help Show help message and configuration info
|
||||||
-p name Early-load given plugin module name or entry point
|
-p name Early-load given plugin module name or entry point
|
||||||
(multi-allowed)
|
(multi-allowed). To avoid loading of plugins, use
|
||||||
To avoid loading of plugins, use the `no:` prefix,
|
the `no:` prefix, e.g. `no:doctest`.
|
||||||
e.g. `no:doctest`
|
|
||||||
--trace-config Trace considerations of conftest.py files
|
--trace-config Trace considerations of conftest.py files
|
||||||
--debug=[DEBUG_FILE_NAME]
|
--debug=[DEBUG_FILE_NAME]
|
||||||
Store internal tracing debug information in this log
|
Store internal tracing debug information in this log
|
||||||
file.
|
file. This file is opened with 'w' and truncated as
|
||||||
This file is opened with 'w' and truncated as a
|
a result, care advised. Default: pytestdebug.log.
|
||||||
result, care advised.
|
|
||||||
Default: pytestdebug.log.
|
|
||||||
-o OVERRIDE_INI, --override-ini=OVERRIDE_INI
|
-o OVERRIDE_INI, --override-ini=OVERRIDE_INI
|
||||||
Override ini option with "option=value" style, e.g.
|
Override ini option with "option=value" style, e.g.
|
||||||
`-o xfail_strict=True -o cache_dir=cache`
|
`-o xfail_strict=True -o cache_dir=cache`.
|
||||||
--assert=MODE Control assertion debugging tools.
|
--assert=MODE Control assertion debugging tools.
|
||||||
'plain' performs no assertion debugging.
|
'plain' performs no assertion debugging.
|
||||||
'rewrite' (the default) rewrites assert statements
|
'rewrite' (the default) rewrites assert statements
|
||||||
|
@ -1930,11 +1935,11 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
--setup-plan Show what fixtures and tests would be executed but
|
--setup-plan Show what fixtures and tests would be executed but
|
||||||
don't execute anything
|
don't execute anything
|
||||||
|
|
||||||
Logging:
|
logging:
|
||||||
--log-level=LEVEL Level of messages to catch/display.
|
--log-level=LEVEL Level of messages to catch/display. Not set by
|
||||||
Not set by default, so it depends on the root/parent
|
default, so it depends on the root/parent log
|
||||||
log handler's effective level, where it is "WARNING"
|
handler's effective level, where it is "WARNING" by
|
||||||
by default.
|
default.
|
||||||
--log-format=LOG_FORMAT
|
--log-format=LOG_FORMAT
|
||||||
Log format used by the logging module
|
Log format used by the logging module
|
||||||
--log-date-format=LOG_DATE_FORMAT
|
--log-date-format=LOG_DATE_FORMAT
|
||||||
|
@ -1956,14 +1961,14 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||||
Auto-indent multiline messages passed to the logging
|
Auto-indent multiline messages passed to the logging
|
||||||
module. Accepts true|on, false|off or an integer.
|
module. Accepts true|on, false|off or an integer.
|
||||||
|
|
||||||
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
|
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
|
||||||
|
|
||||||
markers (linelist): Markers for test functions
|
markers (linelist): Markers for test functions
|
||||||
empty_parameter_set_mark (string):
|
empty_parameter_set_mark (string):
|
||||||
Default marker for empty parametersets
|
Default marker for empty parametersets
|
||||||
norecursedirs (args): Directory patterns to avoid for recursion
|
norecursedirs (args): Directory patterns to avoid for recursion
|
||||||
testpaths (args): Directories to search for tests when no files or
|
testpaths (args): Directories to search for tests when no files or
|
||||||
directories are given in the command line
|
directories are given on the command line
|
||||||
filterwarnings (linelist):
|
filterwarnings (linelist):
|
||||||
Each line specifies a pattern for
|
Each line specifies a pattern for
|
||||||
warnings.filterwarnings. Processed after
|
warnings.filterwarnings. Processed after
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
pallets-sphinx-themes
|
pallets-sphinx-themes
|
||||||
pluggy>=1.0
|
pluggy>=1.0
|
||||||
pygments-pytest>=2.2.0
|
pygments-pytest>=2.3.0
|
||||||
sphinx-removed-in>=0.2.0
|
sphinx-removed-in>=0.2.0
|
||||||
sphinx>=5,<6
|
sphinx>=5,<6
|
||||||
sphinxcontrib-trio
|
sphinxcontrib-trio
|
||||||
sphinxcontrib-svg2pdfconverter
|
sphinxcontrib-svg2pdfconverter
|
||||||
|
# Pin packaging because it no longer handles 'latest' version, which
|
||||||
|
# is the version that is assigned to the docs.
|
||||||
|
# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045.
|
||||||
|
packaging <22
|
||||||
|
|
|
@ -114,3 +114,8 @@ template = "changelog/_template.rst"
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
target-version = ['py37']
|
target-version = ['py37']
|
||||||
|
|
||||||
|
# check-wheel-contents is executed by the build-and-inspect-python-package action.
|
||||||
|
[tool.check-wheel-contents]
|
||||||
|
# W009: Wheel contains multiple toplevel library entries
|
||||||
|
ignore = "W009"
|
||||||
|
|
|
@ -21,6 +21,7 @@ classifiers =
|
||||||
Programming Language :: Python :: 3.8
|
Programming Language :: Python :: 3.8
|
||||||
Programming Language :: Python :: 3.9
|
Programming Language :: Python :: 3.9
|
||||||
Programming Language :: Python :: 3.10
|
Programming Language :: Python :: 3.10
|
||||||
|
Programming Language :: Python :: 3.11
|
||||||
Topic :: Software Development :: Libraries
|
Topic :: Software Development :: Libraries
|
||||||
Topic :: Software Development :: Testing
|
Topic :: Software Development :: Testing
|
||||||
Topic :: Utilities
|
Topic :: Utilities
|
||||||
|
|
|
@ -411,13 +411,13 @@ class Traceback(List[TracebackEntry]):
|
||||||
"""
|
"""
|
||||||
return Traceback(filter(fn, self), self._excinfo)
|
return Traceback(filter(fn, self), self._excinfo)
|
||||||
|
|
||||||
def getcrashentry(self) -> TracebackEntry:
|
def getcrashentry(self) -> Optional[TracebackEntry]:
|
||||||
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
|
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
|
||||||
for i in range(-1, -len(self) - 1, -1):
|
for i in range(-1, -len(self) - 1, -1):
|
||||||
entry = self[i]
|
entry = self[i]
|
||||||
if not entry.ishidden():
|
if not entry.ishidden():
|
||||||
return entry
|
return entry
|
||||||
return self[-1]
|
return None
|
||||||
|
|
||||||
def recursionindex(self) -> Optional[int]:
|
def recursionindex(self) -> Optional[int]:
|
||||||
"""Return the index of the frame/TracebackEntry where recursion originates if
|
"""Return the index of the frame/TracebackEntry where recursion originates if
|
||||||
|
@ -602,11 +602,13 @@ class ExceptionInfo(Generic[E]):
|
||||||
"""
|
"""
|
||||||
return isinstance(self.value, exc)
|
return isinstance(self.value, exc)
|
||||||
|
|
||||||
def _getreprcrash(self) -> "ReprFileLocation":
|
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
||||||
exconly = self.exconly(tryshort=True)
|
exconly = self.exconly(tryshort=True)
|
||||||
entry = self.traceback.getcrashentry()
|
entry = self.traceback.getcrashentry()
|
||||||
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
if entry:
|
||||||
return ReprFileLocation(path, lineno + 1, exconly)
|
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
||||||
|
return ReprFileLocation(path, lineno + 1, exconly)
|
||||||
|
return None
|
||||||
|
|
||||||
def getrepr(
|
def getrepr(
|
||||||
self,
|
self,
|
||||||
|
@ -942,9 +944,14 @@ class FormattedExcinfo:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
reprtraceback = self.repr_traceback(excinfo_)
|
reprtraceback = self.repr_traceback(excinfo_)
|
||||||
reprcrash: Optional[ReprFileLocation] = (
|
|
||||||
excinfo_._getreprcrash() if self.style != "value" else None
|
# will be None if all traceback entries are hidden
|
||||||
)
|
reprcrash: Optional[ReprFileLocation] = excinfo_._getreprcrash()
|
||||||
|
if reprcrash:
|
||||||
|
if self.style == "value":
|
||||||
|
repr_chain += [(reprtraceback, None, descr)]
|
||||||
|
else:
|
||||||
|
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||||
else:
|
else:
|
||||||
# Fallback to native repr if the exception doesn't have a traceback:
|
# Fallback to native repr if the exception doesn't have a traceback:
|
||||||
# ExceptionInfo objects require a full traceback to work.
|
# ExceptionInfo objects require a full traceback to work.
|
||||||
|
@ -952,8 +959,8 @@ class FormattedExcinfo:
|
||||||
traceback.format_exception(type(e), e, None)
|
traceback.format_exception(type(e), e, None)
|
||||||
)
|
)
|
||||||
reprcrash = None
|
reprcrash = None
|
||||||
|
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||||
|
|
||||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
|
||||||
if e.__cause__ is not None and self.chain:
|
if e.__cause__ is not None and self.chain:
|
||||||
e = e.__cause__
|
e = e.__cause__
|
||||||
excinfo_ = (
|
excinfo_ = (
|
||||||
|
@ -1037,7 +1044,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
@attr.s(eq=False, auto_attribs=True)
|
@attr.s(eq=False, auto_attribs=True)
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
reprtraceback: "ReprTraceback"
|
reprtraceback: "ReprTraceback"
|
||||||
reprcrash: "ReprFileLocation"
|
reprcrash: Optional["ReprFileLocation"]
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
self.reprtraceback.toterminal(tw)
|
self.reprtraceback.toterminal(tw)
|
||||||
|
|
|
@ -24,6 +24,7 @@ from stat import S_ISLNK
|
||||||
from stat import S_ISREG
|
from stat import S_ISREG
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
from typing import cast
|
||||||
from typing import overload
|
from typing import overload
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -146,7 +147,7 @@ class Visitor:
|
||||||
self.fil = fil
|
self.fil = fil
|
||||||
self.ignore = ignore
|
self.ignore = ignore
|
||||||
self.breadthfirst = bf
|
self.breadthfirst = bf
|
||||||
self.optsort = sort and sorted or (lambda x: x)
|
self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x)
|
||||||
|
|
||||||
def gen(self, path):
|
def gen(self, path):
|
||||||
try:
|
try:
|
||||||
|
@ -224,7 +225,7 @@ class Stat:
|
||||||
raise NotImplementedError("XXX win32")
|
raise NotImplementedError("XXX win32")
|
||||||
import pwd
|
import pwd
|
||||||
|
|
||||||
entry = error.checked_call(pwd.getpwuid, self.uid)
|
entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined]
|
||||||
return entry[0]
|
return entry[0]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -234,7 +235,7 @@ class Stat:
|
||||||
raise NotImplementedError("XXX win32")
|
raise NotImplementedError("XXX win32")
|
||||||
import grp
|
import grp
|
||||||
|
|
||||||
entry = error.checked_call(grp.getgrgid, self.gid)
|
entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined]
|
||||||
return entry[0]
|
return entry[0]
|
||||||
|
|
||||||
def isdir(self):
|
def isdir(self):
|
||||||
|
@ -252,7 +253,7 @@ def getuserid(user):
|
||||||
import pwd
|
import pwd
|
||||||
|
|
||||||
if not isinstance(user, int):
|
if not isinstance(user, int):
|
||||||
user = pwd.getpwnam(user)[2]
|
user = pwd.getpwnam(user)[2] # type:ignore[attr-defined]
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ def getgroupid(group):
|
||||||
import grp
|
import grp
|
||||||
|
|
||||||
if not isinstance(group, int):
|
if not isinstance(group, int):
|
||||||
group = grp.getgrnam(group)[2]
|
group = grp.getgrnam(group)[2] # type:ignore[attr-defined]
|
||||||
return group
|
return group
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,14 @@ from _pytest.stash import StashKey
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest.assertion import AssertionState
|
from _pytest.assertion import AssertionState
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 8):
|
||||||
|
namedExpr = ast.NamedExpr
|
||||||
|
else:
|
||||||
|
namedExpr = ast.Expr
|
||||||
|
|
||||||
|
|
||||||
assertstate_key = StashKey["AssertionState"]()
|
assertstate_key = StashKey["AssertionState"]()
|
||||||
|
|
||||||
|
|
||||||
# pytest caches rewritten pycs in pycache dirs
|
# pytest caches rewritten pycs in pycache dirs
|
||||||
PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}"
|
PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}"
|
||||||
PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
PYC_EXT = ".py" + (__debug__ and "c" or "o")
|
||||||
|
@ -274,8 +278,12 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
|
||||||
return f.read()
|
return f.read()
|
||||||
|
|
||||||
if sys.version_info >= (3, 10):
|
if sys.version_info >= (3, 10):
|
||||||
|
if sys.version_info >= (3, 12):
|
||||||
|
from importlib.resources.abc import TraversableResources
|
||||||
|
else:
|
||||||
|
from importlib.abc import TraversableResources
|
||||||
|
|
||||||
def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore
|
def get_resource_reader(self, name: str) -> TraversableResources: # type: ignore
|
||||||
if sys.version_info < (3, 11):
|
if sys.version_info < (3, 11):
|
||||||
from importlib.readers import FileReader
|
from importlib.readers import FileReader
|
||||||
else:
|
else:
|
||||||
|
@ -631,8 +639,12 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
.push_format_context() and .pop_format_context() which allows
|
.push_format_context() and .pop_format_context() which allows
|
||||||
to build another %-formatted string while already building one.
|
to build another %-formatted string while already building one.
|
||||||
|
|
||||||
This state is reset on every new assert statement visited and used
|
:variables_overwrite: A dict filled with references to variables
|
||||||
by the other visitors.
|
that change value within an assert. This happens when a variable is
|
||||||
|
reassigned with the walrus operator
|
||||||
|
|
||||||
|
This state, except the variables_overwrite, is reset on every new assert
|
||||||
|
statement visited and used by the other visitors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
@ -648,6 +660,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
else:
|
else:
|
||||||
self.enable_assertion_pass_hook = False
|
self.enable_assertion_pass_hook = False
|
||||||
self.source = source
|
self.source = source
|
||||||
|
self.variables_overwrite: Dict[str, str] = {}
|
||||||
|
|
||||||
def run(self, mod: ast.Module) -> None:
|
def run(self, mod: ast.Module) -> None:
|
||||||
"""Find all assert statements in *mod* and rewrite them."""
|
"""Find all assert statements in *mod* and rewrite them."""
|
||||||
|
@ -662,7 +675,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
if doc is not None and self.is_rewrite_disabled(doc):
|
if doc is not None and self.is_rewrite_disabled(doc):
|
||||||
return
|
return
|
||||||
pos = 0
|
pos = 0
|
||||||
lineno = 1
|
item = None
|
||||||
for item in mod.body:
|
for item in mod.body:
|
||||||
if (
|
if (
|
||||||
expect_docstring
|
expect_docstring
|
||||||
|
@ -933,6 +946,18 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
ast.copy_location(node, assert_)
|
ast.copy_location(node, assert_)
|
||||||
return self.statements
|
return self.statements
|
||||||
|
|
||||||
|
def visit_NamedExpr(self, name: namedExpr) -> Tuple[namedExpr, str]:
|
||||||
|
# This method handles the 'walrus operator' repr of the target
|
||||||
|
# name if it's a local variable or _should_repr_global_name()
|
||||||
|
# thinks it's acceptable.
|
||||||
|
locs = ast.Call(self.builtin("locals"), [], [])
|
||||||
|
target_id = name.target.id # type: ignore[attr-defined]
|
||||||
|
inlocs = ast.Compare(ast.Str(target_id), [ast.In()], [locs])
|
||||||
|
dorepr = self.helper("_should_repr_global_name", name)
|
||||||
|
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
|
||||||
|
expr = ast.IfExp(test, self.display(name), ast.Str(target_id))
|
||||||
|
return name, self.explanation_param(expr)
|
||||||
|
|
||||||
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
|
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
|
||||||
# Display the repr of the name if it's a local variable or
|
# Display the repr of the name if it's a local variable or
|
||||||
# _should_repr_global_name() thinks it's acceptable.
|
# _should_repr_global_name() thinks it's acceptable.
|
||||||
|
@ -959,6 +984,20 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
# cond is set in a prior loop iteration below
|
# cond is set in a prior loop iteration below
|
||||||
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
|
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
|
||||||
self.expl_stmts = fail_inner
|
self.expl_stmts = fail_inner
|
||||||
|
# Check if the left operand is a namedExpr and the value has already been visited
|
||||||
|
if (
|
||||||
|
isinstance(v, ast.Compare)
|
||||||
|
and isinstance(v.left, namedExpr)
|
||||||
|
and v.left.target.id
|
||||||
|
in [
|
||||||
|
ast_expr.id
|
||||||
|
for ast_expr in boolop.values[:i]
|
||||||
|
if hasattr(ast_expr, "id")
|
||||||
|
]
|
||||||
|
):
|
||||||
|
pytest_temp = self.variable()
|
||||||
|
self.variables_overwrite[v.left.target.id] = pytest_temp
|
||||||
|
v.left.target.id = pytest_temp
|
||||||
self.push_format_context()
|
self.push_format_context()
|
||||||
res, expl = self.visit(v)
|
res, expl = self.visit(v)
|
||||||
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
||||||
|
@ -1034,6 +1073,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
|
|
||||||
def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
|
def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
|
||||||
self.push_format_context()
|
self.push_format_context()
|
||||||
|
# We first check if we have overwritten a variable in the previous assert
|
||||||
|
if isinstance(comp.left, ast.Name) and comp.left.id in self.variables_overwrite:
|
||||||
|
comp.left.id = self.variables_overwrite[comp.left.id]
|
||||||
left_res, left_expl = self.visit(comp.left)
|
left_res, left_expl = self.visit(comp.left)
|
||||||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||||
left_expl = f"({left_expl})"
|
left_expl = f"({left_expl})"
|
||||||
|
@ -1045,6 +1087,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
syms = []
|
syms = []
|
||||||
results = [left_res]
|
results = [left_res]
|
||||||
for i, op, next_operand in it:
|
for i, op, next_operand in it:
|
||||||
|
if (
|
||||||
|
isinstance(next_operand, namedExpr)
|
||||||
|
and isinstance(left_res, ast.Name)
|
||||||
|
and next_operand.target.id == left_res.id
|
||||||
|
):
|
||||||
|
next_operand.target.id = self.variable()
|
||||||
|
self.variables_overwrite[left_res.id] = next_operand.target.id
|
||||||
next_res, next_expl = self.visit(next_operand)
|
next_res, next_expl = self.visit(next_operand)
|
||||||
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
|
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
|
||||||
next_expl = f"({next_expl})"
|
next_expl = f"({next_expl})"
|
||||||
|
@ -1068,6 +1117,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
||||||
res: ast.expr = ast.BoolOp(ast.And(), load_names)
|
res: ast.expr = ast.BoolOp(ast.And(), load_names)
|
||||||
else:
|
else:
|
||||||
res = load_names[0]
|
res = load_names[0]
|
||||||
|
|
||||||
return res, self.explanation_param(self.pop_format_context(expl_call))
|
return res, self.explanation_param(self.pop_format_context(expl_call))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,6 @@ from _pytest.python import Module
|
||||||
from _pytest.python import Package
|
from _pytest.python import Package
|
||||||
from _pytest.reports import TestReport
|
from _pytest.reports import TestReport
|
||||||
|
|
||||||
|
|
||||||
README_CONTENT = """\
|
README_CONTENT = """\
|
||||||
# pytest cache directory #
|
# pytest cache directory #
|
||||||
|
|
||||||
|
@ -492,7 +491,7 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
|
|
||||||
|
|
||||||
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
|
||||||
if config.option.cacheshow:
|
if config.option.cacheshow and not config.option.help:
|
||||||
from _pytest.main import wrap_session
|
from _pytest.main import wrap_session
|
||||||
|
|
||||||
return wrap_session(config, cacheshow)
|
return wrap_session(config, cacheshow)
|
||||||
|
|
|
@ -253,7 +253,6 @@ class NoCapture:
|
||||||
|
|
||||||
|
|
||||||
class SysCaptureBinary:
|
class SysCaptureBinary:
|
||||||
|
|
||||||
EMPTY_BUFFER = b""
|
EMPTY_BUFFER = b""
|
||||||
|
|
||||||
def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None:
|
def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None:
|
||||||
|
|
|
@ -62,7 +62,6 @@ from _pytest.warning_types import PytestConfigWarning
|
||||||
from _pytest.warning_types import warn_explicit_for
|
from _pytest.warning_types import warn_explicit_for
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
|
||||||
from _pytest._code.code import _TracebackStyle
|
from _pytest._code.code import _TracebackStyle
|
||||||
from _pytest.terminal import TerminalReporter
|
from _pytest.terminal import TerminalReporter
|
||||||
from .argparsing import Argument
|
from .argparsing import Argument
|
||||||
|
@ -998,6 +997,8 @@ class Config:
|
||||||
self.hook.pytest_addoption.call_historic(
|
self.hook.pytest_addoption.call_historic(
|
||||||
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
|
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
|
||||||
)
|
)
|
||||||
|
self.args_source = Config.ArgsSource.ARGS
|
||||||
|
self.args: List[str] = []
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest.cacheprovider import Cache
|
from _pytest.cacheprovider import Cache
|
||||||
|
@ -1057,7 +1058,6 @@ class Config:
|
||||||
try:
|
try:
|
||||||
self.parse(args)
|
self.parse(args)
|
||||||
except UsageError:
|
except UsageError:
|
||||||
|
|
||||||
# Handle --version and --help here in a minimal fashion.
|
# Handle --version and --help here in a minimal fashion.
|
||||||
# This gets done via helpconfig normally, but its
|
# This gets done via helpconfig normally, but its
|
||||||
# pytest_cmdline_main is not called in case of errors.
|
# pytest_cmdline_main is not called in case of errors.
|
||||||
|
@ -1337,8 +1337,8 @@ class Config:
|
||||||
|
|
||||||
def parse(self, args: List[str], addopts: bool = True) -> None:
|
def parse(self, args: List[str], addopts: bool = True) -> None:
|
||||||
# Parse given cmdline arguments into this config object.
|
# Parse given cmdline arguments into this config object.
|
||||||
assert not hasattr(
|
assert (
|
||||||
self, "args"
|
self.args == []
|
||||||
), "can only parse cmdline args at most once per Config object"
|
), "can only parse cmdline args at most once per Config object"
|
||||||
self.hook.pytest_addhooks.call_historic(
|
self.hook.pytest_addhooks.call_historic(
|
||||||
kwargs=dict(pluginmanager=self.pluginmanager)
|
kwargs=dict(pluginmanager=self.pluginmanager)
|
||||||
|
|
|
@ -43,7 +43,6 @@ class PathAwareHookProxy:
|
||||||
|
|
||||||
@_wraps(hook)
|
@_wraps(hook)
|
||||||
def fixed_hook(**kw):
|
def fixed_hook(**kw):
|
||||||
|
|
||||||
path_value: Optional[Path] = kw.pop(path_var, None)
|
path_value: Optional[Path] = kw.pop(path_var, None)
|
||||||
fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
|
fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None)
|
||||||
if fspath_value is not None:
|
if fspath_value is not None:
|
||||||
|
|
|
@ -203,8 +203,7 @@ def determine_setup(
|
||||||
else:
|
else:
|
||||||
cwd = Path.cwd()
|
cwd = Path.cwd()
|
||||||
rootdir = get_common_ancestor([cwd, ancestor])
|
rootdir = get_common_ancestor([cwd, ancestor])
|
||||||
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
|
if is_fs_root(rootdir):
|
||||||
if is_fs_root:
|
|
||||||
rootdir = ancestor
|
rootdir = ancestor
|
||||||
if rootdir_cmd_arg:
|
if rootdir_cmd_arg:
|
||||||
rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
|
rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg))
|
||||||
|
@ -216,3 +215,11 @@ def determine_setup(
|
||||||
)
|
)
|
||||||
assert rootdir is not None
|
assert rootdir is not None
|
||||||
return rootdir, inipath, inicfg or {}
|
return rootdir, inipath, inicfg or {}
|
||||||
|
|
||||||
|
|
||||||
|
def is_fs_root(p: Path) -> bool:
|
||||||
|
r"""
|
||||||
|
Return True if the given path is pointing to the root of the
|
||||||
|
file system ("/" on Unix and "C:\\" on Windows for example).
|
||||||
|
"""
|
||||||
|
return os.path.splitdrive(str(p))[1] == os.sep
|
||||||
|
|
|
@ -531,7 +531,6 @@ class DoctestModule(Module):
|
||||||
if _is_mocked(obj):
|
if _is_mocked(obj):
|
||||||
return
|
return
|
||||||
with _patch_unwrap_mock_aware():
|
with _patch_unwrap_mock_aware():
|
||||||
|
|
||||||
# Type ignored because this is a private function.
|
# Type ignored because this is a private function.
|
||||||
super()._find( # type:ignore[misc]
|
super()._find( # type:ignore[misc]
|
||||||
tests, obj, name, module, source_lines, globs, seen
|
tests, obj, name, module, source_lines, globs, seen
|
||||||
|
|
|
@ -58,6 +58,7 @@ from _pytest.mark import Mark
|
||||||
from _pytest.mark import ParameterSet
|
from _pytest.mark import ParameterSet
|
||||||
from _pytest.mark.structures import MarkDecorator
|
from _pytest.mark.structures import MarkDecorator
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
from _pytest.outcomes import skip
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
from _pytest.outcomes import TEST_OUTCOME
|
||||||
from _pytest.pathlib import absolutepath
|
from _pytest.pathlib import absolutepath
|
||||||
from _pytest.pathlib import bestrelpath
|
from _pytest.pathlib import bestrelpath
|
||||||
|
@ -1129,6 +1130,10 @@ def pytest_fixture_setup(
|
||||||
except TEST_OUTCOME:
|
except TEST_OUTCOME:
|
||||||
exc_info = sys.exc_info()
|
exc_info = sys.exc_info()
|
||||||
assert exc_info[0] is not None
|
assert exc_info[0] is not None
|
||||||
|
if isinstance(
|
||||||
|
exc_info[1], skip.Exception
|
||||||
|
) and not fixturefunc.__name__.startswith("xunit_setup"):
|
||||||
|
exc_info[1]._use_item_location = True # type: ignore[attr-defined]
|
||||||
fixturedef.cached_result = (None, my_cache_key, exc_info)
|
fixturedef.cached_result = (None, my_cache_key, exc_info)
|
||||||
raise
|
raise
|
||||||
fixturedef.cached_result = (result, my_cache_key, None)
|
fixturedef.cached_result = (result, my_cache_key, None)
|
||||||
|
|
|
@ -164,7 +164,8 @@ def showhelp(config: Config) -> None:
|
||||||
tw.write(config._parser.optparser.format_help())
|
tw.write(config._parser.optparser.format_help())
|
||||||
tw.line()
|
tw.line()
|
||||||
tw.line(
|
tw.line(
|
||||||
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
|
"[pytest] ini-options in the first "
|
||||||
|
"pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:"
|
||||||
)
|
)
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
||||||
|
|
|
@ -738,7 +738,7 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_header(
|
def pytest_report_header( # type:ignore[empty-body]
|
||||||
config: "Config", start_path: Path, startdir: "LEGACY_PATH"
|
config: "Config", start_path: Path, startdir: "LEGACY_PATH"
|
||||||
) -> Union[str, List[str]]:
|
) -> Union[str, List[str]]:
|
||||||
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
"""Return a string or list of strings to be displayed as header info for terminal reporting.
|
||||||
|
@ -767,7 +767,7 @@ def pytest_report_header(
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def pytest_report_collectionfinish(
|
def pytest_report_collectionfinish( # type:ignore[empty-body]
|
||||||
config: "Config",
|
config: "Config",
|
||||||
start_path: Path,
|
start_path: Path,
|
||||||
startdir: "LEGACY_PATH",
|
startdir: "LEGACY_PATH",
|
||||||
|
@ -800,7 +800,7 @@ def pytest_report_collectionfinish(
|
||||||
|
|
||||||
|
|
||||||
@hookspec(firstresult=True)
|
@hookspec(firstresult=True)
|
||||||
def pytest_report_teststatus(
|
def pytest_report_teststatus( # type:ignore[empty-body]
|
||||||
report: Union["CollectReport", "TestReport"], config: "Config"
|
report: Union["CollectReport", "TestReport"], config: "Config"
|
||||||
) -> Tuple[str, str, Union[str, Mapping[str, bool]]]:
|
) -> Tuple[str, str, Union[str, Mapping[str, bool]]]:
|
||||||
"""Return result-category, shortletter and verbose word for status
|
"""Return result-category, shortletter and verbose word for status
|
||||||
|
@ -880,7 +880,9 @@ def pytest_warning_recorded(
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
|
def pytest_markeval_namespace( # type:ignore[empty-body]
|
||||||
|
config: "Config",
|
||||||
|
) -> Dict[str, Any]:
|
||||||
"""Called when constructing the globals dictionary used for
|
"""Called when constructing the globals dictionary used for
|
||||||
evaluating string conditions in xfail/skipif markers.
|
evaluating string conditions in xfail/skipif markers.
|
||||||
|
|
||||||
|
|
|
@ -645,8 +645,8 @@ class LogXML:
|
||||||
|
|
||||||
def pytest_sessionfinish(self) -> None:
|
def pytest_sessionfinish(self) -> None:
|
||||||
dirname = os.path.dirname(os.path.abspath(self.logfile))
|
dirname = os.path.dirname(os.path.abspath(self.logfile))
|
||||||
if not os.path.isdir(dirname):
|
# exist_ok avoids filesystem race conditions between checking path existence and requesting creation
|
||||||
os.makedirs(dirname)
|
os.makedirs(dirname, exist_ok=True)
|
||||||
|
|
||||||
with open(self.logfile, "w", encoding="utf-8") as logfile:
|
with open(self.logfile, "w", encoding="utf-8") as logfile:
|
||||||
suite_stop_time = timing.time()
|
suite_stop_time = timing.time()
|
||||||
|
|
|
@ -157,8 +157,12 @@ def skip(
|
||||||
The message to show the user as reason for the skip.
|
The message to show the user as reason for the skip.
|
||||||
|
|
||||||
:param allow_module_level:
|
:param allow_module_level:
|
||||||
Allows this function to be called at module level, skipping the rest
|
Allows this function to be called at module level.
|
||||||
of the module. Defaults to False.
|
Raising the skip exception at module level will stop
|
||||||
|
the execution of the module and prevent the collection of all tests in the module,
|
||||||
|
even those defined before the `skip` call.
|
||||||
|
|
||||||
|
Defaults to False.
|
||||||
|
|
||||||
:param msg:
|
:param msg:
|
||||||
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
|
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
|
||||||
|
@ -219,7 +223,6 @@ def _resolve_msg_to_reason(
|
||||||
"""
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
if msg is not None:
|
if msg is not None:
|
||||||
|
|
||||||
if reason:
|
if reason:
|
||||||
from pytest import UsageError
|
from pytest import UsageError
|
||||||
|
|
||||||
|
|
|
@ -464,14 +464,14 @@ def import_path(
|
||||||
|
|
||||||
* `mode == ImportMode.prepend`: the directory containing the module (or package, taking
|
* `mode == ImportMode.prepend`: the directory containing the module (or package, taking
|
||||||
`__init__.py` files into account) will be put at the *start* of `sys.path` before
|
`__init__.py` files into account) will be put at the *start* of `sys.path` before
|
||||||
being imported with `__import__.
|
being imported with `importlib.import_module`.
|
||||||
|
|
||||||
* `mode == ImportMode.append`: same as `prepend`, but the directory will be appended
|
* `mode == ImportMode.append`: same as `prepend`, but the directory will be appended
|
||||||
to the end of `sys.path`, if not already in `sys.path`.
|
to the end of `sys.path`, if not already in `sys.path`.
|
||||||
|
|
||||||
* `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib`
|
* `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib`
|
||||||
to import the module, which avoids having to use `__import__` and muck with `sys.path`
|
to import the module, which avoids having to muck with `sys.path` at all. It effectively
|
||||||
at all. It effectively allows having same-named test modules in different places.
|
allows having same-named test modules in different places.
|
||||||
|
|
||||||
:param root:
|
:param root:
|
||||||
Used as an anchor when mode == ImportMode.importlib to obtain
|
Used as an anchor when mode == ImportMode.importlib to obtain
|
||||||
|
|
|
@ -790,7 +790,8 @@ def _call_with_optional_argument(func, arg) -> None:
|
||||||
|
|
||||||
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
|
def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
|
||||||
"""Return the attribute from the given object to be used as a setup/teardown
|
"""Return the attribute from the given object to be used as a setup/teardown
|
||||||
xunit-style function, but only if not marked as a fixture to avoid calling it twice."""
|
xunit-style function, but only if not marked as a fixture to avoid calling it twice.
|
||||||
|
"""
|
||||||
for name in names:
|
for name in names:
|
||||||
meth: Optional[object] = getattr(obj, name, None)
|
meth: Optional[object] = getattr(obj, name, None)
|
||||||
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
if meth is not None and fixtures.getfixturemarker(meth) is None:
|
||||||
|
@ -848,7 +849,7 @@ class Class(PyCollector):
|
||||||
other fixtures (#517).
|
other fixtures (#517).
|
||||||
"""
|
"""
|
||||||
setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
|
setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
|
||||||
teardown_class = getattr(self.obj, "teardown_class", None)
|
teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",))
|
||||||
if setup_class is None and teardown_class is None:
|
if setup_class is None and teardown_class is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -885,12 +886,12 @@ class Class(PyCollector):
|
||||||
emit_nose_setup_warning = True
|
emit_nose_setup_warning = True
|
||||||
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
|
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
|
||||||
teardown_name = "teardown_method"
|
teardown_name = "teardown_method"
|
||||||
teardown_method = getattr(self.obj, teardown_name, None)
|
teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
|
||||||
emit_nose_teardown_warning = False
|
emit_nose_teardown_warning = False
|
||||||
if teardown_method is None and has_nose:
|
if teardown_method is None and has_nose:
|
||||||
teardown_name = "teardown"
|
teardown_name = "teardown"
|
||||||
emit_nose_teardown_warning = True
|
emit_nose_teardown_warning = True
|
||||||
teardown_method = getattr(self.obj, teardown_name, None)
|
teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
|
||||||
if setup_method is None and teardown_method is None:
|
if setup_method is None and teardown_method is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ from types import TracebackType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import cast
|
from typing import cast
|
||||||
from typing import Generic
|
from typing import ContextManager
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -269,10 +269,16 @@ class ApproxMapping(ApproxBase):
|
||||||
max_abs_diff = max(
|
max_abs_diff = max(
|
||||||
max_abs_diff, abs(approx_value.expected - other_value)
|
max_abs_diff, abs(approx_value.expected - other_value)
|
||||||
)
|
)
|
||||||
max_rel_diff = max(
|
if approx_value.expected == 0.0:
|
||||||
max_rel_diff,
|
max_rel_diff = math.inf
|
||||||
abs((approx_value.expected - other_value) / approx_value.expected),
|
else:
|
||||||
)
|
max_rel_diff = max(
|
||||||
|
max_rel_diff,
|
||||||
|
abs(
|
||||||
|
(approx_value.expected - other_value)
|
||||||
|
/ approx_value.expected
|
||||||
|
),
|
||||||
|
)
|
||||||
different_ids.append(approx_key)
|
different_ids.append(approx_key)
|
||||||
|
|
||||||
message_data = [
|
message_data = [
|
||||||
|
@ -957,7 +963,7 @@ raises.Exception = fail.Exception # type: ignore
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class RaisesContext(Generic[E]):
|
class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
|
||||||
|
|
|
@ -337,6 +337,10 @@ class TestReport(BaseReport):
|
||||||
elif isinstance(excinfo.value, skip.Exception):
|
elif isinstance(excinfo.value, skip.Exception):
|
||||||
outcome = "skipped"
|
outcome = "skipped"
|
||||||
r = excinfo._getreprcrash()
|
r = excinfo._getreprcrash()
|
||||||
|
if r is None:
|
||||||
|
raise ValueError(
|
||||||
|
"There should always be a traceback entry for skipping a test."
|
||||||
|
)
|
||||||
if excinfo.value._use_item_location:
|
if excinfo.value._use_item_location:
|
||||||
path, line = item.reportinfo()[:2]
|
path, line = item.reportinfo()[:2]
|
||||||
assert line is not None
|
assert line is not None
|
||||||
|
@ -573,7 +577,6 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
and "reprcrash" in reportdict["longrepr"]
|
and "reprcrash" in reportdict["longrepr"]
|
||||||
and "reprtraceback" in reportdict["longrepr"]
|
and "reprtraceback" in reportdict["longrepr"]
|
||||||
):
|
):
|
||||||
|
|
||||||
reprtraceback = deserialize_repr_traceback(
|
reprtraceback = deserialize_repr_traceback(
|
||||||
reportdict["longrepr"]["reprtraceback"]
|
reportdict["longrepr"]["reprtraceback"]
|
||||||
)
|
)
|
||||||
|
|
|
@ -48,6 +48,10 @@ def pytest_configure(config: Config) -> None:
|
||||||
def pytest_sessionfinish(session: Session) -> None:
|
def pytest_sessionfinish(session: Session) -> None:
|
||||||
if not session.config.getoption("stepwise"):
|
if not session.config.getoption("stepwise"):
|
||||||
assert session.config.cache is not None
|
assert session.config.cache is not None
|
||||||
|
if hasattr(session.config, "workerinput"):
|
||||||
|
# Do not update cache if this process is a xdist worker to prevent
|
||||||
|
# race conditions (#10641).
|
||||||
|
return
|
||||||
# Clear the list of failing tests if the plugin is not active.
|
# Clear the list of failing tests if the plugin is not active.
|
||||||
session.config.cache.set(STEPWISE_CACHE_DIR, [])
|
session.config.cache.set(STEPWISE_CACHE_DIR, [])
|
||||||
|
|
||||||
|
@ -119,4 +123,8 @@ class StepwisePlugin:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def pytest_sessionfinish(self) -> None:
|
def pytest_sessionfinish(self) -> None:
|
||||||
|
if hasattr(self.config, "workerinput"):
|
||||||
|
# Do not update cache if this process is a xdist worker to prevent
|
||||||
|
# race conditions (#10641).
|
||||||
|
return
|
||||||
self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)
|
self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)
|
||||||
|
|
|
@ -803,7 +803,7 @@ class TestLocalPath(CommonFSTests):
|
||||||
# depending on how the paths are used), but > 4096 (which is the
|
# depending on how the paths are used), but > 4096 (which is the
|
||||||
# Linux' limitation) - the behaviour of paths with names > 4096 chars
|
# Linux' limitation) - the behaviour of paths with names > 4096 chars
|
||||||
# is undetermined
|
# is undetermined
|
||||||
newfilename = "/test" * 60
|
newfilename = "/test" * 60 # type:ignore[unreachable]
|
||||||
l1 = tmpdir.join(newfilename)
|
l1 = tmpdir.join(newfilename)
|
||||||
l1.ensure(file=True)
|
l1.ensure(file=True)
|
||||||
l1.write("foo")
|
l1.write("foo")
|
||||||
|
@ -1344,8 +1344,8 @@ class TestPOSIXLocalPath:
|
||||||
assert realpath.basename == "file"
|
assert realpath.basename == "file"
|
||||||
|
|
||||||
def test_owner(self, path1, tmpdir):
|
def test_owner(self, path1, tmpdir):
|
||||||
from pwd import getpwuid
|
from pwd import getpwuid # type:ignore[attr-defined]
|
||||||
from grp import getgrgid
|
from grp import getgrgid # type:ignore[attr-defined]
|
||||||
|
|
||||||
stat = path1.stat()
|
stat = path1.stat()
|
||||||
assert stat.path == path1
|
assert stat.path == path1
|
||||||
|
|
|
@ -694,7 +694,14 @@ class TestInvocationVariants:
|
||||||
|
|
||||||
# mixed module and filenames:
|
# mixed module and filenames:
|
||||||
monkeypatch.chdir("world")
|
monkeypatch.chdir("world")
|
||||||
result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
|
|
||||||
|
# pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages.
|
||||||
|
# While we could change the test to use implicit namespace packages, seems better
|
||||||
|
# to still ensure the old declaration via declare_namespace still works.
|
||||||
|
ignore_w = r"-Wignore:Deprecated call to `pkg_resources.declare_namespace"
|
||||||
|
result = pytester.runpytest(
|
||||||
|
"--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w
|
||||||
|
)
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
[
|
[
|
||||||
|
@ -872,7 +879,6 @@ class TestDurations:
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None:
|
def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None:
|
||||||
|
|
||||||
pytester.makepyfile(self.source)
|
pytester.makepyfile(self.source)
|
||||||
result = pytester.runpytest_inprocess("--durations=2")
|
result = pytester.runpytest_inprocess("--durations=2")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
|
@ -294,6 +294,7 @@ class TestTraceback_f_g_h:
|
||||||
excinfo = pytest.raises(ValueError, f)
|
excinfo = pytest.raises(ValueError, f)
|
||||||
tb = excinfo.traceback
|
tb = excinfo.traceback
|
||||||
entry = tb.getcrashentry()
|
entry = tb.getcrashentry()
|
||||||
|
assert entry is not None
|
||||||
co = _pytest._code.Code.from_function(h)
|
co = _pytest._code.Code.from_function(h)
|
||||||
assert entry.frame.code.path == co.path
|
assert entry.frame.code.path == co.path
|
||||||
assert entry.lineno == co.firstlineno + 1
|
assert entry.lineno == co.firstlineno + 1
|
||||||
|
@ -311,10 +312,7 @@ class TestTraceback_f_g_h:
|
||||||
excinfo = pytest.raises(ValueError, f)
|
excinfo = pytest.raises(ValueError, f)
|
||||||
tb = excinfo.traceback
|
tb = excinfo.traceback
|
||||||
entry = tb.getcrashentry()
|
entry = tb.getcrashentry()
|
||||||
co = _pytest._code.Code.from_function(g)
|
assert entry is None
|
||||||
assert entry.frame.code.path == co.path
|
|
||||||
assert entry.lineno == co.firstlineno + 2
|
|
||||||
assert entry.frame.code.name == "g"
|
|
||||||
|
|
||||||
|
|
||||||
def test_excinfo_exconly():
|
def test_excinfo_exconly():
|
||||||
|
|
|
@ -157,6 +157,7 @@ def color_mapping():
|
||||||
"number": "\x1b[94m",
|
"number": "\x1b[94m",
|
||||||
"str": "\x1b[33m",
|
"str": "\x1b[33m",
|
||||||
"print": "\x1b[96m",
|
"print": "\x1b[96m",
|
||||||
|
"endline": "\x1b[90m\x1b[39;49;00m",
|
||||||
}
|
}
|
||||||
RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
|
RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
|
||||||
|
|
||||||
|
@ -195,7 +196,6 @@ def mock_timing(monkeypatch: MonkeyPatch):
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class MockTiming:
|
class MockTiming:
|
||||||
|
|
||||||
_current_time = attr.ib(default=1590150050.0)
|
_current_time = attr.ib(default=1590150050.0)
|
||||||
|
|
||||||
def sleep(self, seconds):
|
def sleep(self, seconds):
|
||||||
|
|
|
@ -254,7 +254,7 @@ class TestTerminalWriterLineWidth:
|
||||||
pytest.param(
|
pytest.param(
|
||||||
True,
|
True,
|
||||||
True,
|
True,
|
||||||
"{kw}assert{hl-reset} {number}0{hl-reset}\n",
|
"{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n",
|
||||||
id="with markup and code_highlight",
|
id="with markup and code_highlight",
|
||||||
),
|
),
|
||||||
pytest.param(
|
pytest.param(
|
||||||
|
|
|
@ -630,6 +630,19 @@ class TestApprox:
|
||||||
def test_dict_vs_other(self):
|
def test_dict_vs_other(self):
|
||||||
assert 1 != approx({"a": 0})
|
assert 1 != approx({"a": 0})
|
||||||
|
|
||||||
|
def test_dict_for_div_by_zero(self, assert_approx_raises_regex):
|
||||||
|
assert_approx_raises_regex(
|
||||||
|
{"foo": 42.0},
|
||||||
|
{"foo": 0.0},
|
||||||
|
[
|
||||||
|
r" comparison failed. Mismatched elements: 1 / 1:",
|
||||||
|
rf" Max absolute difference: {SOME_FLOAT}",
|
||||||
|
r" Max relative difference: inf",
|
||||||
|
r" Index \| Obtained\s+\| Expected ",
|
||||||
|
rf" foo | {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
def test_numpy_array(self):
|
def test_numpy_array(self):
|
||||||
np = pytest.importorskip("numpy")
|
np = pytest.importorskip("numpy")
|
||||||
|
|
||||||
|
|
|
@ -3338,6 +3338,10 @@ class TestShowFixtures:
|
||||||
config = pytester.parseconfigure("--funcargs")
|
config = pytester.parseconfigure("--funcargs")
|
||||||
assert config.option.showfixtures
|
assert config.option.showfixtures
|
||||||
|
|
||||||
|
def test_show_help(self, pytester: Pytester) -> None:
|
||||||
|
result = pytester.runpytest("--fixtures", "--help")
|
||||||
|
assert not result.ret
|
||||||
|
|
||||||
def test_show_fixtures(self, pytester: Pytester) -> None:
|
def test_show_fixtures(self, pytester: Pytester) -> None:
|
||||||
result = pytester.runpytest("--fixtures")
|
result = pytester.runpytest("--fixtures")
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
|
|
|
@ -1664,15 +1664,7 @@ def test_raise_assertion_error_raising_repr(pytester: Pytester) -> None:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest()
|
||||||
if sys.version_info >= (3, 11):
|
result.stdout.fnmatch_lines(["E AssertionError: <exception str() failed>"])
|
||||||
# python 3.11 has native support for un-str-able exceptions
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
["E AssertionError: <exception str() failed>"]
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
result.stdout.fnmatch_lines(
|
|
||||||
["E AssertionError: <unprintable AssertionError object>"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_issue_1944(pytester: Pytester) -> None:
|
def test_issue_1944(pytester: Pytester) -> None:
|
||||||
|
|
|
@ -1265,6 +1265,177 @@ class TestIssue2121:
|
||||||
result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"])
|
result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
sys.version_info < (3, 8), reason="walrus operator not available in py<38"
|
||||||
|
)
|
||||||
|
class TestIssue10743:
|
||||||
|
def test_assertion_walrus_operator(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def my_func(before, after):
|
||||||
|
return before == after
|
||||||
|
|
||||||
|
def change_value(value):
|
||||||
|
return value.lower()
|
||||||
|
|
||||||
|
def test_walrus_conversion():
|
||||||
|
a = "Hello"
|
||||||
|
assert not my_func(a, a := change_value(a))
|
||||||
|
assert a == "hello"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_dont_rewrite(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
'PYTEST_DONT_REWRITE'
|
||||||
|
def my_func(before, after):
|
||||||
|
return before == after
|
||||||
|
|
||||||
|
def change_value(value):
|
||||||
|
return value.lower()
|
||||||
|
|
||||||
|
def test_walrus_conversion_dont_rewrite():
|
||||||
|
a = "Hello"
|
||||||
|
assert not my_func(a, a := change_value(a))
|
||||||
|
assert a == "hello"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_assertion_inline_walrus_operator(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def my_func(before, after):
|
||||||
|
return before == after
|
||||||
|
|
||||||
|
def test_walrus_conversion_inline():
|
||||||
|
a = "Hello"
|
||||||
|
assert not my_func(a, a := a.lower())
|
||||||
|
assert a == "hello"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_assertion_inline_walrus_operator_reverse(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def my_func(before, after):
|
||||||
|
return before == after
|
||||||
|
|
||||||
|
def test_walrus_conversion_reverse():
|
||||||
|
a = "Hello"
|
||||||
|
assert my_func(a := a.lower(), a)
|
||||||
|
assert a == 'hello'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_assertion_walrus_no_variable_name_conflict(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_conversion_no_conflict():
|
||||||
|
a = "Hello"
|
||||||
|
assert a == (b := a.lower())
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"])
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_true_assertion_and_changes_variable_value(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_conversion_succeed():
|
||||||
|
a = "Hello"
|
||||||
|
assert a != (a := a.lower())
|
||||||
|
assert a == 'hello'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_fail_assertion(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_conversion_fails():
|
||||||
|
a = "Hello"
|
||||||
|
assert a == (a := a.lower())
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"])
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_boolean_composite(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_operator_change_boolean_value():
|
||||||
|
a = True
|
||||||
|
assert a and True and ((a := False) is False) and (a is False) and ((a := None) is None)
|
||||||
|
assert a is None
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_compare_boolean_fails(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_operator_change_boolean_value():
|
||||||
|
a = True
|
||||||
|
assert not (a and ((a := False) is False))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(["*assert not (True and False is False)"])
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_boolean_none_fails(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_operator_change_boolean_value():
|
||||||
|
a = True
|
||||||
|
assert not (a and ((a := None) is None))
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(["*assert not (True and None is None)"])
|
||||||
|
|
||||||
|
def test_assertion_walrus_operator_value_changes_cleared_after_each_test(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def test_walrus_operator_change_value():
|
||||||
|
a = True
|
||||||
|
assert (a := None) is None
|
||||||
|
|
||||||
|
def test_walrus_operator_not_override_value():
|
||||||
|
a = True
|
||||||
|
assert a is True
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
|
sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
|
||||||
)
|
)
|
||||||
|
|
|
@ -494,7 +494,6 @@ class TestLastFailed:
|
||||||
def test_lastfailed_collectfailure(
|
def test_lastfailed_collectfailure(
|
||||||
self, pytester: Pytester, monkeypatch: MonkeyPatch
|
self, pytester: Pytester, monkeypatch: MonkeyPatch
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
test_maybe="""
|
test_maybe="""
|
||||||
import os
|
import os
|
||||||
|
@ -1249,3 +1248,8 @@ def test_cachedir_tag(pytester: Pytester) -> None:
|
||||||
cache.set("foo", "bar")
|
cache.set("foo", "bar")
|
||||||
cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG")
|
cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG")
|
||||||
assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT
|
assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT
|
||||||
|
|
||||||
|
|
||||||
|
def test_clioption_with_cacheshow_and_help(pytester: Pytester) -> None:
|
||||||
|
result = pytester.runpytest("--cache-show", "--help")
|
||||||
|
assert result.ret == 0
|
||||||
|
|
|
@ -1236,7 +1236,6 @@ class TestDoctestSkips:
|
||||||
|
|
||||||
|
|
||||||
class TestDoctestAutoUseFixtures:
|
class TestDoctestAutoUseFixtures:
|
||||||
|
|
||||||
SCOPES = ["module", "session", "class", "function"]
|
SCOPES = ["module", "session", "class", "function"]
|
||||||
|
|
||||||
def test_doctest_module_session_fixture(self, pytester: Pytester):
|
def test_doctest_module_session_fixture(self, pytester: Pytester):
|
||||||
|
@ -1379,7 +1378,6 @@ class TestDoctestAutoUseFixtures:
|
||||||
|
|
||||||
|
|
||||||
class TestDoctestNamespaceFixture:
|
class TestDoctestNamespaceFixture:
|
||||||
|
|
||||||
SCOPES = ["module", "session", "class", "function"]
|
SCOPES = ["module", "session", "class", "function"]
|
||||||
|
|
||||||
@pytest.mark.parametrize("scope", SCOPES)
|
@pytest.mark.parametrize("scope", SCOPES)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
@ -5,6 +6,7 @@ import pytest
|
||||||
from _pytest.config import UsageError
|
from _pytest.config import UsageError
|
||||||
from _pytest.config.findpaths import get_common_ancestor
|
from _pytest.config.findpaths import get_common_ancestor
|
||||||
from _pytest.config.findpaths import get_dirs_from_args
|
from _pytest.config.findpaths import get_dirs_from_args
|
||||||
|
from _pytest.config.findpaths import is_fs_root
|
||||||
from _pytest.config.findpaths import load_config_dict_from_file
|
from _pytest.config.findpaths import load_config_dict_from_file
|
||||||
|
|
||||||
|
|
||||||
|
@ -133,3 +135,18 @@ def test_get_dirs_from_args(tmp_path):
|
||||||
assert get_dirs_from_args(
|
assert get_dirs_from_args(
|
||||||
[str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option]
|
[str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option]
|
||||||
) == [fn.parent, d]
|
) == [fn.parent, d]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"path, expected",
|
||||||
|
[
|
||||||
|
pytest.param(
|
||||||
|
f"e:{os.sep}", True, marks=pytest.mark.skipif("sys.platform != 'win32'")
|
||||||
|
),
|
||||||
|
(f"{os.sep}", True),
|
||||||
|
(f"e:{os.sep}projects", False),
|
||||||
|
(f"{os.sep}projects", False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_is_fs_root(path: Path, expected: bool) -> None:
|
||||||
|
assert is_fs_root(Path(path)) is expected
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue