Merge remote-tracking branch 'upstream/main' into tomllib
This commit is contained in:
commit
a3745b2ed8
|
@ -0,0 +1,56 @@
|
||||||
|
name: deploy
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
# These tags are protected, see:
|
||||||
|
# https://github.com/pytest-dev/pytest/settings/tag_protection
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+"
|
||||||
|
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||||
|
|
||||||
|
|
||||||
|
# Set permissions at the job level.
|
||||||
|
permissions: {}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
deploy:
|
||||||
|
if: github.repository == 'pytest-dev/pytest'
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v2
|
||||||
|
with:
|
||||||
|
python-version: "3.7"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade build tox
|
||||||
|
|
||||||
|
- name: Build package
|
||||||
|
run: |
|
||||||
|
python -m build
|
||||||
|
|
||||||
|
- name: Publish package to PyPI
|
||||||
|
uses: pypa/gh-action-pypi-publish@master
|
||||||
|
with:
|
||||||
|
user: __token__
|
||||||
|
password: ${{ secrets.pypi_token }}
|
||||||
|
|
||||||
|
- name: Publish GitHub release notes
|
||||||
|
env:
|
||||||
|
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
|
||||||
|
run: |
|
||||||
|
sudo apt-get install pandoc
|
||||||
|
tox -e publish-gh-release-notes
|
|
@ -1,4 +1,4 @@
|
||||||
name: main
|
name: test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
@ -187,46 +187,3 @@ jobs:
|
||||||
fail_ci_if_error: true
|
fail_ci_if_error: true
|
||||||
files: ./coverage.xml
|
files: ./coverage.xml
|
||||||
verbose: true
|
verbose: true
|
||||||
|
|
||||||
deploy:
|
|
||||||
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags') && github.repository == 'pytest-dev/pytest'
|
|
||||||
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
timeout-minutes: 30
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
|
|
||||||
needs: [build]
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
persist-credentials: false
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v2
|
|
||||||
with:
|
|
||||||
python-version: "3.7"
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
pip install --upgrade build tox
|
|
||||||
|
|
||||||
- name: Build package
|
|
||||||
run: |
|
|
||||||
python -m build
|
|
||||||
|
|
||||||
- name: Publish package to PyPI
|
|
||||||
uses: pypa/gh-action-pypi-publish@master
|
|
||||||
with:
|
|
||||||
user: __token__
|
|
||||||
password: ${{ secrets.pypi_token }}
|
|
||||||
|
|
||||||
- name: Publish GitHub release notes
|
|
||||||
env:
|
|
||||||
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
|
|
||||||
run: |
|
|
||||||
sudo apt-get install pandoc
|
|
||||||
tox -e publish-gh-release-notes
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 22.1.0
|
rev: 22.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
|
@ -10,7 +10,7 @@ repos:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies: [black==20.8b1]
|
additional_dependencies: [black==20.8b1]
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.1.0
|
rev: v4.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
|
@ -37,17 +37,17 @@ 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: v2.7.1
|
rev: v3.0.1
|
||||||
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: v2.31.0
|
rev: v2.32.0
|
||||||
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: v1.20.0
|
rev: v1.20.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
args: [--max-py-version=3.10]
|
args: [--max-py-version=3.10]
|
||||||
|
@ -56,7 +56,7 @@ repos:
|
||||||
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.931
|
rev: v0.942
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/)
|
files: ^(src/|testing/)
|
||||||
|
|
2
AUTHORS
2
AUTHORS
|
@ -185,6 +185,7 @@ Katerina Koukiou
|
||||||
Keri Volans
|
Keri Volans
|
||||||
Kevin Cox
|
Kevin Cox
|
||||||
Kevin J. Foley
|
Kevin J. Foley
|
||||||
|
Kian Eliasi
|
||||||
Kian-Meng Ang
|
Kian-Meng Ang
|
||||||
Kodi B. Arfer
|
Kodi B. Arfer
|
||||||
Kojo Idrissa
|
Kojo Idrissa
|
||||||
|
@ -289,6 +290,7 @@ Ruaridh Williamson
|
||||||
Russel Winder
|
Russel Winder
|
||||||
Ryan Wooden
|
Ryan Wooden
|
||||||
Saiprasad Kale
|
Saiprasad Kale
|
||||||
|
Samuel Colvin
|
||||||
Samuel Dion-Girardeau
|
Samuel Dion-Girardeau
|
||||||
Samuel Searles-Bryant
|
Samuel Searles-Bryant
|
||||||
Samuele Pedroni
|
Samuele Pedroni
|
||||||
|
|
|
@ -50,6 +50,8 @@ Fix bugs
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
|
Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
|
||||||
|
See also the `"status: easy" issues <https://github.com/pytest-dev/pytest/labels/status%3A%20easy>`_
|
||||||
|
that are friendly to new contributors.
|
||||||
|
|
||||||
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going
|
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going
|
||||||
to work on a particular issue, add a comment to that effect on the specific issue.
|
to work on a particular issue, add a comment to that effect on the specific issue.
|
||||||
|
|
|
@ -20,8 +20,8 @@
|
||||||
:target: https://codecov.io/gh/pytest-dev/pytest
|
:target: https://codecov.io/gh/pytest-dev/pytest
|
||||||
:alt: Code coverage Status
|
:alt: Code coverage Status
|
||||||
|
|
||||||
.. image:: https://github.com/pytest-dev/pytest/workflows/main/badge.svg
|
.. image:: https://github.com/pytest-dev/pytest/workflows/test/badge.svg
|
||||||
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Amain
|
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest
|
||||||
|
|
||||||
.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg
|
.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg
|
||||||
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main
|
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
Fixed test output for some data types where ``-v`` would show less information.
|
|
||||||
|
|
||||||
Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff.
|
|
|
@ -1,3 +0,0 @@
|
||||||
The deprecation of raising :class:`unittest.SkipTest` to skip collection of
|
|
||||||
tests during the pytest collection phase is reverted - this is now a supported
|
|
||||||
feature again.
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Introduce multiline display for warning matching via :py:func:`pytest.warns` and
|
||||||
|
enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`.
|
|
@ -1,15 +0,0 @@
|
||||||
As per our policy, the following features have been deprecated in the 6.X series and are now
|
|
||||||
removed:
|
|
||||||
|
|
||||||
* ``pytest._fillfuncargs`` function.
|
|
||||||
|
|
||||||
* ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead.
|
|
||||||
|
|
||||||
* ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead.
|
|
||||||
|
|
||||||
* ``-k foobar:`` syntax.
|
|
||||||
|
|
||||||
* ``pytest.collect`` module - import from ``pytest`` directly.
|
|
||||||
|
|
||||||
For more information consult
|
|
||||||
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
|
|
@ -1 +0,0 @@
|
||||||
pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``.
|
|
|
@ -1 +0,0 @@
|
||||||
Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
|
|
|
@ -1,10 +0,0 @@
|
||||||
Symbolic link components are no longer resolved in conftest paths.
|
|
||||||
This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice.
|
|
||||||
For example, given
|
|
||||||
|
|
||||||
tests/real/conftest.py
|
|
||||||
tests/real/test_it.py
|
|
||||||
tests/link -> tests/real
|
|
||||||
|
|
||||||
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
|
||||||
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
|
|
|
@ -1 +0,0 @@
|
||||||
When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width.
|
|
|
@ -1,3 +0,0 @@
|
||||||
Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
|
|
||||||
|
|
||||||
If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count.
|
|
|
@ -1,4 +0,0 @@
|
||||||
More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
|
|
||||||
be obtained by enabling :mod:`tracemalloc`.
|
|
||||||
|
|
||||||
See :ref:`resource-warnings` for more information.
|
|
|
@ -1 +0,0 @@
|
||||||
Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
|
|
|
@ -1,3 +0,0 @@
|
||||||
More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``.
|
|
||||||
Previously only `str`, `float`, `int` and `bool` were accepted;
|
|
||||||
now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted.
|
|
|
@ -1,3 +0,0 @@
|
||||||
:func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`).
|
|
||||||
|
|
||||||
Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order.
|
|
|
@ -1 +0,0 @@
|
||||||
:fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables.
|
|
|
@ -0,0 +1 @@
|
||||||
|
An unnecessary ``numpy`` import inside :func:`pytest.approx` was removed.
|
|
@ -1 +0,0 @@
|
||||||
Malformed ``pyproject.toml`` files now produce a clearer error message.
|
|
|
@ -0,0 +1 @@
|
||||||
|
Display assertion message without escaped newline characters with ``-vv``.
|
|
@ -0,0 +1 @@
|
||||||
|
Fix comparison of ``dataclasses`` with ``InitVar``.
|
|
@ -6,6 +6,8 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-7.1.1
|
||||||
|
release-7.1.0
|
||||||
release-7.0.1
|
release-7.0.1
|
||||||
release-7.0.0
|
release-7.0.0
|
||||||
release-7.0.0rc1
|
release-7.0.0rc1
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
pytest-7.1.0
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
The pytest team is proud to announce the 7.1.0 release!
|
||||||
|
|
||||||
|
This release contains new features, improvements, and bug fixes,
|
||||||
|
the full list of changes is available in the changelog:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/stable/changelog.html
|
||||||
|
|
||||||
|
For complete documentation, please visit:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/stable/
|
||||||
|
|
||||||
|
As usual, you can upgrade from PyPI via:
|
||||||
|
|
||||||
|
pip install -U pytest
|
||||||
|
|
||||||
|
Thanks to all of the contributors to this release:
|
||||||
|
|
||||||
|
* Akuli
|
||||||
|
* Andrew Svetlov
|
||||||
|
* Anthony Sottile
|
||||||
|
* Brett Holman
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Chris NeJame
|
||||||
|
* Dan Alvizu
|
||||||
|
* Elijah DeLee
|
||||||
|
* Emmanuel Arias
|
||||||
|
* Fabian Egli
|
||||||
|
* Florian Bruhin
|
||||||
|
* Gabor Szabo
|
||||||
|
* Hasan Ramezani
|
||||||
|
* Hugo van Kemenade
|
||||||
|
* Kian Meng, Ang
|
||||||
|
* Kojo Idrissa
|
||||||
|
* Masaru Tsuchiyama
|
||||||
|
* Olga Matoula
|
||||||
|
* P. L. Lim
|
||||||
|
* Ran Benita
|
||||||
|
* Tobias Deiminger
|
||||||
|
* Yuval Shimon
|
||||||
|
* eduardo naufel schettino
|
||||||
|
* Éric
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -0,0 +1,18 @@
|
||||||
|
pytest-7.1.1
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 7.1.1 has just been released to PyPI.
|
||||||
|
|
||||||
|
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||||
|
|
||||||
|
pip install --upgrade pytest
|
||||||
|
|
||||||
|
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||||
|
|
||||||
|
Thanks to all of the contributors to this release:
|
||||||
|
|
||||||
|
* Ran Benita
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -65,7 +65,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
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.
|
||||||
|
|
||||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1365
|
pytestconfig [session scope] -- .../_pytest/fixtures.py:1334
|
||||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||||
object.
|
object.
|
||||||
|
|
||||||
|
@ -134,7 +134,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:483
|
caplog -- .../_pytest/logging.py:487
|
||||||
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::
|
||||||
|
|
|
@ -28,6 +28,107 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 7.1.1 (2022-03-17)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#9767 <https://github.com/pytest-dev/pytest/issues/9767>`_: Fixed a regression in pytest 7.1.0 where some conftest.py files outside of the source tree (e.g. in the `site-packages` directory) were not picked up.
|
||||||
|
|
||||||
|
|
||||||
|
pytest 7.1.0 (2022-03-13)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Breaking Changes
|
||||||
|
----------------
|
||||||
|
|
||||||
|
- `#8838 <https://github.com/pytest-dev/pytest/issues/8838>`_: As per our policy, the following features have been deprecated in the 6.X series and are now
|
||||||
|
removed:
|
||||||
|
|
||||||
|
* ``pytest._fillfuncargs`` function.
|
||||||
|
|
||||||
|
* ``pytest_warning_captured`` hook - use ``pytest_warning_recorded`` instead.
|
||||||
|
|
||||||
|
* ``-k -foobar`` syntax - use ``-k 'not foobar'`` instead.
|
||||||
|
|
||||||
|
* ``-k foobar:`` syntax.
|
||||||
|
|
||||||
|
* ``pytest.collect`` module - import from ``pytest`` directly.
|
||||||
|
|
||||||
|
For more information consult
|
||||||
|
`Deprecations and Removals <https://docs.pytest.org/en/latest/deprecations.html>`__ in the docs.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9437 <https://github.com/pytest-dev/pytest/issues/9437>`_: Dropped support for Python 3.6, which reached `end-of-life <https://devguide.python.org/#status-of-python-branches>`__ at 2021-12-23.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improvements
|
||||||
|
------------
|
||||||
|
|
||||||
|
- `#5192 <https://github.com/pytest-dev/pytest/issues/5192>`_: Fixed test output for some data types where ``-v`` would show less information.
|
||||||
|
|
||||||
|
Also, when showing diffs for sequences, ``-q`` would produce full diffs instead of the expected diff.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9362 <https://github.com/pytest-dev/pytest/issues/9362>`_: pytest now avoids specialized assert formatting when it is detected that the default ``__eq__`` is overridden in ``attrs`` or ``dataclasses``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9536 <https://github.com/pytest-dev/pytest/issues/9536>`_: When ``-vv`` is given on command line, show skipping and xfail reasons in full instead of truncating them to fit the terminal width.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9644 <https://github.com/pytest-dev/pytest/issues/9644>`_: More information about the location of resources that led Python to raise :class:`ResourceWarning` can now
|
||||||
|
be obtained by enabling :mod:`tracemalloc`.
|
||||||
|
|
||||||
|
See :ref:`resource-warnings` for more information.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9678 <https://github.com/pytest-dev/pytest/issues/9678>`_: More types are now accepted in the ``ids`` argument to ``@pytest.mark.parametrize``.
|
||||||
|
Previously only `str`, `float`, `int` and `bool` were accepted;
|
||||||
|
now `bytes`, `complex`, `re.Pattern`, `Enum` and anything with a `__name__` are also accepted.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9692 <https://github.com/pytest-dev/pytest/issues/9692>`_: :func:`pytest.approx` now raises a :class:`TypeError` when given an unordered sequence (such as :class:`set`).
|
||||||
|
|
||||||
|
Note that this implies that custom classes which only implement ``__iter__`` and ``__len__`` are no longer supported as they don't guarantee order.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#8242 <https://github.com/pytest-dev/pytest/issues/8242>`_: The deprecation of raising :class:`unittest.SkipTest` to skip collection of
|
||||||
|
tests during the pytest collection phase is reverted - this is now a supported
|
||||||
|
feature again.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9493 <https://github.com/pytest-dev/pytest/issues/9493>`_: Symbolic link components are no longer resolved in conftest paths.
|
||||||
|
This means that if a conftest appears twice in collection tree, using symlinks, it will be executed twice.
|
||||||
|
For example, given
|
||||||
|
|
||||||
|
tests/real/conftest.py
|
||||||
|
tests/real/test_it.py
|
||||||
|
tests/link -> tests/real
|
||||||
|
|
||||||
|
running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``.
|
||||||
|
This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details).
|
||||||
|
|
||||||
|
|
||||||
|
- `#9626 <https://github.com/pytest-dev/pytest/issues/9626>`_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules.
|
||||||
|
|
||||||
|
If there were errors or skipped modules on collection, pytest would mistakenly subtract those from the selected count.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9645 <https://github.com/pytest-dev/pytest/issues/9645>`_: Fixed regression where ``--import-mode=importlib`` used together with :envvar:`PYTHONPATH` or :confval:`pythonpath` would cause import errors in test suites.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9708 <https://github.com/pytest-dev/pytest/issues/9708>`_: :fixture:`pytester` now requests a :fixture:`monkeypatch` fixture instead of creating one internally. This solves some issues with tests that involve pytest environment variables.
|
||||||
|
|
||||||
|
|
||||||
|
- `#9730 <https://github.com/pytest-dev/pytest/issues/9730>`_: Malformed ``pyproject.toml`` files now produce a clearer error message.
|
||||||
|
|
||||||
|
|
||||||
pytest 7.0.1 (2022-02-11)
|
pytest 7.0.1 (2022-02-11)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ The ``pytest_warning_captured`` hook
|
||||||
|
|
||||||
This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
|
This hook has an `item` parameter which cannot be serialized by ``pytest-xdist``.
|
||||||
|
|
||||||
Use the ``pytest_warning_recored`` hook instead, which replaces the ``item`` parameter
|
Use the ``pytest_warning_recorded`` hook instead, which replaces the ``item`` parameter
|
||||||
by a ``nodeid`` parameter.
|
by a ``nodeid`` parameter.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -155,7 +155,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert [0, 1, 2] == [0, 1, 3]
|
> assert [0, 1, 2] == [0, 1, 3]
|
||||||
E assert [0, 1, 2] == [0, 1, 3]
|
E assert [0, 1, 2] == [0, 1, 3]
|
||||||
E At index 2 diff: 2 != 3
|
E At index 2 diff: 2 != 3
|
||||||
E Use -v to get the full diff
|
E Use -v to get more diff
|
||||||
|
|
||||||
failure_demo.py:63: AssertionError
|
failure_demo.py:63: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||||
|
@ -168,7 +168,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert a == b
|
> assert a == b
|
||||||
E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
|
E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
|
||||||
E At index 100 diff: 1 != 2
|
E At index 100 diff: 1 != 2
|
||||||
E Use -v to get the full diff
|
E Use -v to get more diff
|
||||||
|
|
||||||
failure_demo.py:68: AssertionError
|
failure_demo.py:68: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||||
|
@ -215,7 +215,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert [1, 2] == [1, 2, 3]
|
> assert [1, 2] == [1, 2, 3]
|
||||||
E assert [1, 2] == [1, 2, 3]
|
E assert [1, 2] == [1, 2, 3]
|
||||||
E Right contains one more item: 3
|
E Right contains one more item: 3
|
||||||
E Use -v to get the full diff
|
E Use -v to get more diff
|
||||||
|
|
||||||
failure_demo.py:77: AssertionError
|
failure_demo.py:77: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||||
|
|
|
@ -22,7 +22,7 @@ Install ``pytest``
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ pytest --version
|
$ pytest --version
|
||||||
pytest 7.0.1
|
pytest 7.1.1
|
||||||
|
|
||||||
.. _`simpletest`:
|
.. _`simpletest`:
|
||||||
|
|
||||||
|
|
|
@ -201,7 +201,7 @@ if you run this module:
|
||||||
E '1'
|
E '1'
|
||||||
E Extra items in the right set:
|
E Extra items in the right set:
|
||||||
E '5'
|
E '5'
|
||||||
E Use -v to get the full diff
|
E Use -v to get more diff
|
||||||
|
|
||||||
test_assert2.py:4: AssertionError
|
test_assert2.py:4: AssertionError
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
|
|
|
@ -84,7 +84,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo
|
||||||
> assert fruits1 == fruits2
|
> assert fruits1 == fruits2
|
||||||
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
|
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
|
||||||
E At index 2 diff: 'grapes' != 'orange'
|
E At index 2 diff: 'grapes' != 'orange'
|
||||||
E Use -v to get the full diff
|
E Use -v to get more diff
|
||||||
|
|
||||||
test_verbosity_example.py:8: AssertionError
|
test_verbosity_example.py:8: AssertionError
|
||||||
____________________________ test_numbers_fail _____________________________
|
____________________________ test_numbers_fail _____________________________
|
||||||
|
@ -99,7 +99,7 @@ Executing pytest normally gives us this output (we are skipping the header to fo
|
||||||
E {'1': 1, '2': 2, '3': 3, '4': 4}
|
E {'1': 1, '2': 2, '3': 3, '4': 4}
|
||||||
E Right contains 4 more items:
|
E Right contains 4 more items:
|
||||||
E {'10': 10, '20': 20, '30': 30, '40': 40}
|
E {'10': 10, '20': 20, '30': 30, '40': 40}
|
||||||
E Use -v to get the full diff
|
E Use -v to get more diff
|
||||||
|
|
||||||
test_verbosity_example.py:14: AssertionError
|
test_verbosity_example.py:14: AssertionError
|
||||||
___________________________ test_long_text_fail ____________________________
|
___________________________ test_long_text_fail ____________________________
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
:orphan:
|
:orphan:
|
||||||
|
|
||||||
|
.. sidebar:: Next Open Trainings
|
||||||
|
|
||||||
|
- `PyConDE <https://2022.pycon.de/program/W93DBJ/>`__, April 11th 2022 (3h), Berlin, Germany
|
||||||
|
- `PyConIT <https://pycon.it/en/talk/pytest-simple-rapid-and-fun-testing-with-python>`__, June 3rd 2022 (4h), Florence, Italy
|
||||||
|
- `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>`.
|
||||||
|
|
||||||
..
|
..
|
||||||
.. sidebar:: Next Open Trainings
|
- `Europython <https://ep2022.europython.eu/>`__, July 11th to 17th (3h), Dublin, Ireland
|
||||||
|
- `CH Open Workshoptage <https://workshoptage.ch/>`__ (German), September 6th to 8th (1 day), Bern, Switzerland
|
||||||
- `Professional Testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, February 1st to 3rd, 2022, Leipzig (Germany) and remote.
|
|
||||||
|
|
||||||
Also see `previous talks and blogposts <talks.html>`_.
|
|
||||||
|
|
||||||
.. _features:
|
.. _features:
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -5,3 +5,6 @@ sphinx-removed-in>=0.2.0
|
||||||
sphinx>=3.1,<4
|
sphinx>=3.1,<4
|
||||||
sphinxcontrib-trio
|
sphinxcontrib-trio
|
||||||
sphinxcontrib-svg2pdfconverter
|
sphinxcontrib-svg2pdfconverter
|
||||||
|
|
||||||
|
# XXX: sphinx<4 is broken with latest jinja2
|
||||||
|
jinja2<3.1
|
||||||
|
|
|
@ -17,6 +17,8 @@ Books
|
||||||
Talks and blog postings
|
Talks and blog postings
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
|
- `pytest: Simple, rapid and fun testing with Python, <https://youtu.be/cSJ-X3TbQ1c?t=15752>`_ (@ 4:22:32), Florian Bruhin, WeAreDevelopers World Congress 2021
|
||||||
|
|
||||||
- Webinar: `pytest: Test Driven Development für Python (German) <https://bruhin.software/ins-pytest/>`_, Florian Bruhin, via mylearning.ch, 2020
|
- Webinar: `pytest: Test Driven Development für Python (German) <https://bruhin.software/ins-pytest/>`_, Florian Bruhin, via mylearning.ch, 2020
|
||||||
|
|
||||||
- Webinar: `Simplify Your Tests with Fixtures <https://blog.jetbrains.com/pycharm/2020/08/webinar-recording-simplify-your-tests-with-fixtures-with-oliver-bestwalter/>`_, Oliver Bestwalter, via JetBrains, 2020
|
- Webinar: `Simplify Your Tests with Fixtures <https://blog.jetbrains.com/pycharm/2020/08/webinar-recording-simplify-your-tests-with-fixtures-with-oliver-bestwalter/>`_, Oliver Bestwalter, via JetBrains, 2020
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from distutils.core import setup
|
from distutils.core import setup
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -672,10 +672,11 @@ class ExceptionInfo(Generic[E]):
|
||||||
If it matches `True` is returned, otherwise an `AssertionError` is raised.
|
If it matches `True` is returned, otherwise an `AssertionError` is raised.
|
||||||
"""
|
"""
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
msg = "Regex pattern {!r} does not match {!r}."
|
value = str(self.value)
|
||||||
if regexp == str(self.value):
|
msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
|
||||||
msg += " Did you mean to `re.escape()` the regex?"
|
if regexp == value:
|
||||||
assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value))
|
msg += "\n Did you mean to `re.escape()` the regex?"
|
||||||
|
assert re.search(regexp, value), msg
|
||||||
# Return True to allow for "assert excinfo.match()".
|
# Return True to allow for "assert excinfo.match()".
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,23 @@ def saferepr(obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE) -> str
|
||||||
return SafeRepr(maxsize).repr(obj)
|
return SafeRepr(maxsize).repr(obj)
|
||||||
|
|
||||||
|
|
||||||
|
def saferepr_unlimited(obj: object) -> str:
|
||||||
|
"""Return an unlimited-size safe repr-string for the given object.
|
||||||
|
|
||||||
|
As with saferepr, failing __repr__ functions of user instances
|
||||||
|
will be represented with a short exception info.
|
||||||
|
|
||||||
|
This function is a wrapper around simple repr.
|
||||||
|
|
||||||
|
Note: a cleaner solution would be to alter ``saferepr``this way
|
||||||
|
when maxsize=None, but that might affect some other code.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return repr(obj)
|
||||||
|
except Exception as exc:
|
||||||
|
return _format_repr_exception(exc, obj)
|
||||||
|
|
||||||
|
|
||||||
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
|
class AlwaysDispatchingPrettyPrinter(pprint.PrettyPrinter):
|
||||||
"""PrettyPrinter that always dispatches (regardless of width)."""
|
"""PrettyPrinter that always dispatches (regardless of width)."""
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ from typing import Sequence
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest import outcomes
|
from _pytest import outcomes
|
||||||
from _pytest._io.saferepr import _pformat_dispatch
|
from _pytest._io.saferepr import _pformat_dispatch
|
||||||
from _pytest._io.saferepr import safeformat
|
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
from _pytest._io.saferepr import saferepr_unlimited
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
|
|
||||||
# The _reprcompare attribute on the util module is used by the new assertion
|
# The _reprcompare attribute on the util module is used by the new assertion
|
||||||
|
@ -160,8 +160,8 @@ def assertrepr_compare(config, op: str, left: Any, right: Any) -> Optional[List[
|
||||||
"""Return specialised explanations for some operators/operands."""
|
"""Return specialised explanations for some operators/operands."""
|
||||||
verbose = config.getoption("verbose")
|
verbose = config.getoption("verbose")
|
||||||
if verbose > 1:
|
if verbose > 1:
|
||||||
left_repr = safeformat(left)
|
left_repr = saferepr_unlimited(left)
|
||||||
right_repr = safeformat(right)
|
right_repr = saferepr_unlimited(right)
|
||||||
else:
|
else:
|
||||||
# XXX: "15 chars indentation" is wrong
|
# XXX: "15 chars indentation" is wrong
|
||||||
# ("E AssertionError: assert "); should use term width.
|
# ("E AssertionError: assert "); should use term width.
|
||||||
|
@ -437,8 +437,10 @@ def _compare_eq_cls(left: Any, right: Any, verbose: int) -> List[str]:
|
||||||
if not has_default_eq(left):
|
if not has_default_eq(left):
|
||||||
return []
|
return []
|
||||||
if isdatacls(left):
|
if isdatacls(left):
|
||||||
all_fields = left.__dataclass_fields__
|
import dataclasses
|
||||||
fields_to_check = [field for field, info in all_fields.items() if info.compare]
|
|
||||||
|
all_fields = dataclasses.fields(left)
|
||||||
|
fields_to_check = [info.name for info in all_fields if info.compare]
|
||||||
elif isattrs(left):
|
elif isattrs(left):
|
||||||
all_fields = left.__attrs_attrs__
|
all_fields = left.__attrs_attrs__
|
||||||
fields_to_check = [field.name for field in all_fields if getattr(field, "eq")]
|
fields_to_check = [field.name for field in all_fields if getattr(field, "eq")]
|
||||||
|
|
|
@ -309,7 +309,9 @@ def _prepareconfig(
|
||||||
elif isinstance(args, os.PathLike):
|
elif isinstance(args, os.PathLike):
|
||||||
args = [os.fspath(args)]
|
args = [os.fspath(args)]
|
||||||
elif not isinstance(args, list):
|
elif not isinstance(args, list):
|
||||||
msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})"
|
msg = ( # type:ignore[unreachable]
|
||||||
|
"`args` parameter expected to be a list of strings, got: {!r} (type: {})"
|
||||||
|
)
|
||||||
raise TypeError(msg.format(args, type(args)))
|
raise TypeError(msg.format(args, type(args)))
|
||||||
|
|
||||||
config = get_config(args, plugins)
|
config = get_config(args, plugins)
|
||||||
|
@ -538,11 +540,7 @@ class PytestPluginManager(PluginManager):
|
||||||
"""
|
"""
|
||||||
if self._confcutdir is None:
|
if self._confcutdir is None:
|
||||||
return True
|
return True
|
||||||
try:
|
return path not in self._confcutdir.parents
|
||||||
path.relative_to(self._confcutdir)
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def _try_load_conftest(
|
def _try_load_conftest(
|
||||||
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
||||||
|
|
|
@ -397,7 +397,7 @@ if TYPE_CHECKING:
|
||||||
from _pytest.scope import _ScopeName
|
from _pytest.scope import _ScopeName
|
||||||
|
|
||||||
class _SkipMarkDecorator(MarkDecorator):
|
class _SkipMarkDecorator(MarkDecorator):
|
||||||
@overload # type: ignore[override,misc]
|
@overload # type: ignore[override,misc,no-overload-impl]
|
||||||
def __call__(self, arg: Markable) -> Markable:
|
def __call__(self, arg: Markable) -> Markable:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@ -415,7 +415,7 @@ if TYPE_CHECKING:
|
||||||
...
|
...
|
||||||
|
|
||||||
class _XfailMarkDecorator(MarkDecorator):
|
class _XfailMarkDecorator(MarkDecorator):
|
||||||
@overload # type: ignore[override,misc]
|
@overload # type: ignore[override,misc,no-overload-impl]
|
||||||
def __call__(self, arg: Markable) -> Markable:
|
def __call__(self, arg: Markable) -> Markable:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
|
@ -832,7 +832,7 @@ class Pytester:
|
||||||
return self._makefile(ext, args, kwargs)
|
return self._makefile(ext, args, kwargs)
|
||||||
|
|
||||||
def makeconftest(self, source: str) -> Path:
|
def makeconftest(self, source: str) -> Path:
|
||||||
"""Write a contest.py file with 'source' as contents."""
|
"""Write a conftest.py file with 'source' as contents."""
|
||||||
return self.makepyfile(conftest=source)
|
return self.makepyfile(conftest=source)
|
||||||
|
|
||||||
def makeini(self, source: str) -> Path:
|
def makeini(self, source: str) -> Path:
|
||||||
|
|
|
@ -319,7 +319,6 @@ class ApproxSequenceLike(ApproxBase):
|
||||||
|
|
||||||
def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
|
def _repr_compare(self, other_side: Sequence[float]) -> List[str]:
|
||||||
import math
|
import math
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
if len(self.expected) != len(other_side):
|
if len(self.expected) != len(other_side):
|
||||||
return [
|
return [
|
||||||
|
@ -340,7 +339,7 @@ class ApproxSequenceLike(ApproxBase):
|
||||||
abs_diff = abs(approx_value.expected - other_value)
|
abs_diff = abs(approx_value.expected - other_value)
|
||||||
max_abs_diff = max(max_abs_diff, abs_diff)
|
max_abs_diff = max(max_abs_diff, abs_diff)
|
||||||
if other_value == 0.0:
|
if other_value == 0.0:
|
||||||
max_rel_diff = np.inf
|
max_rel_diff = math.inf
|
||||||
else:
|
else:
|
||||||
max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value))
|
max_rel_diff = max(max_rel_diff, abs_diff / abs(other_value))
|
||||||
different_ids.append(i)
|
different_ids.append(i)
|
||||||
|
@ -573,7 +572,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase:
|
||||||
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
|
>>> {'a': 0.1 + 0.2, 'b': 0.2 + 0.4} == approx({'a': 0.3, 'b': 0.6})
|
||||||
True
|
True
|
||||||
|
|
||||||
The comparision will be true if both mappings have the same keys and their
|
The comparison will be true if both mappings have the same keys and their
|
||||||
respective values match the expected tolerances.
|
respective values match the expected tolerances.
|
||||||
|
|
||||||
**Tolerances**
|
**Tolerances**
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""Record warnings during test function execution."""
|
"""Record warnings during test function execution."""
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
|
from pprint import pformat
|
||||||
from types import TracebackType
|
from types import TracebackType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
@ -110,7 +111,7 @@ def warns(
|
||||||
r"""Assert that code raises a particular class of warning.
|
r"""Assert that code raises a particular class of warning.
|
||||||
|
|
||||||
Specifically, the parameter ``expected_warning`` can be a warning class or
|
Specifically, the parameter ``expected_warning`` can be a warning class or
|
||||||
sequence of warning classes, and the inside the ``with`` block must issue a warning of that class or
|
sequence of warning classes, and the code inside the ``with`` block must issue a warning of that class or
|
||||||
classes.
|
classes.
|
||||||
|
|
||||||
This helper produces a list of :class:`warnings.WarningMessage` objects,
|
This helper produces a list of :class:`warnings.WarningMessage` objects,
|
||||||
|
@ -142,10 +143,11 @@ def warns(
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
if not args:
|
if not args:
|
||||||
if kwargs:
|
if kwargs:
|
||||||
msg = "Unexpected keyword arguments passed to pytest.warns: "
|
argnames = ", ".join(sorted(kwargs))
|
||||||
msg += ", ".join(sorted(kwargs))
|
raise TypeError(
|
||||||
msg += "\nUse context-manager form instead?"
|
f"Unexpected keyword arguments passed to pytest.warns: {argnames}"
|
||||||
raise TypeError(msg)
|
"\nUse context-manager form instead?"
|
||||||
|
)
|
||||||
return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
|
return WarningsChecker(expected_warning, match_expr=match, _ispytest=True)
|
||||||
else:
|
else:
|
||||||
func = args[0]
|
func = args[0]
|
||||||
|
@ -191,7 +193,7 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||||
if issubclass(w.category, cls):
|
if issubclass(w.category, cls):
|
||||||
return self._list.pop(i)
|
return self._list.pop(i)
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise AssertionError("%r not found in warning list" % cls)
|
raise AssertionError(f"{cls!r} not found in warning list")
|
||||||
|
|
||||||
def clear(self) -> None:
|
def clear(self) -> None:
|
||||||
"""Clear the list of recorded warnings."""
|
"""Clear the list of recorded warnings."""
|
||||||
|
@ -202,7 +204,7 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||||
def __enter__(self) -> "WarningsRecorder": # type: ignore
|
def __enter__(self) -> "WarningsRecorder": # type: ignore
|
||||||
if self._entered:
|
if self._entered:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise RuntimeError("Cannot enter %r twice" % self)
|
raise RuntimeError(f"Cannot enter {self!r} twice")
|
||||||
_list = super().__enter__()
|
_list = super().__enter__()
|
||||||
# record=True means it's None.
|
# record=True means it's None.
|
||||||
assert _list is not None
|
assert _list is not None
|
||||||
|
@ -218,7 +220,7 @@ class WarningsRecorder(warnings.catch_warnings):
|
||||||
) -> None:
|
) -> None:
|
||||||
if not self._entered:
|
if not self._entered:
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise RuntimeError("Cannot exit %r without entering first" % self)
|
raise RuntimeError(f"Cannot exit {self!r} without entering first")
|
||||||
|
|
||||||
super().__exit__(exc_type, exc_val, exc_tb)
|
super().__exit__(exc_type, exc_val, exc_tb)
|
||||||
|
|
||||||
|
@ -268,16 +270,17 @@ class WarningsChecker(WarningsRecorder):
|
||||||
|
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
|
|
||||||
|
def found_str():
|
||||||
|
return pformat([record.message for record in self], indent=2)
|
||||||
|
|
||||||
# only check if we're not currently handling an exception
|
# only check if we're not currently handling an exception
|
||||||
if exc_type is None and exc_val is None and exc_tb is None:
|
if exc_type is None and exc_val is None and exc_tb is None:
|
||||||
if self.expected_warning is not None:
|
if self.expected_warning is not None:
|
||||||
if not any(issubclass(r.category, self.expected_warning) for r in self):
|
if not any(issubclass(r.category, self.expected_warning) for r in self):
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
fail(
|
fail(
|
||||||
"DID NOT WARN. No warnings of type {} were emitted. "
|
f"DID NOT WARN. No warnings of type {self.expected_warning} were emitted.\n"
|
||||||
"The list of emitted warnings is: {}.".format(
|
f"The list of emitted warnings is: {found_str()}."
|
||||||
self.expected_warning, [each.message for each in self]
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
elif self.match_expr is not None:
|
elif self.match_expr is not None:
|
||||||
for r in self:
|
for r in self:
|
||||||
|
@ -286,11 +289,8 @@ class WarningsChecker(WarningsRecorder):
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
fail(
|
fail(
|
||||||
"DID NOT WARN. No warnings of type {} matching"
|
f"""\
|
||||||
" ('{}') were emitted. The list of emitted warnings"
|
DID NOT WARN. No warnings of type {self.expected_warning} matching the regex were emitted.
|
||||||
" is: {}.".format(
|
Regex: {self.match_expr}
|
||||||
self.expected_warning,
|
Emitted warnings: {found_str()}"""
|
||||||
self.match_expr,
|
|
||||||
[each.message for each in self],
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -420,18 +420,20 @@ def test_match_raises_error(pytester: Pytester) -> None:
|
||||||
excinfo.match(r'[123]+')
|
excinfo.match(r'[123]+')
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest("--tb=short")
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
|
|
||||||
exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'."
|
match = [
|
||||||
result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"])
|
r"E .* AssertionError: Regex pattern did not match.",
|
||||||
|
r"E .* Regex: '\[123\]\+'",
|
||||||
|
r"E .* Input: 'division by zero'",
|
||||||
|
]
|
||||||
|
result.stdout.re_match_lines(match)
|
||||||
result.stdout.no_fnmatch_line("*__tracebackhide__ = True*")
|
result.stdout.no_fnmatch_line("*__tracebackhide__ = True*")
|
||||||
|
|
||||||
result = pytester.runpytest("--fulltrace")
|
result = pytester.runpytest("--fulltrace")
|
||||||
assert result.ret != 0
|
assert result.ret != 0
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.re_match_lines([r".*__tracebackhide__ = True.*", *match])
|
||||||
["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class TestFormattedExcinfo:
|
class TestFormattedExcinfo:
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from dataclasses import InitVar
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Foo:
|
||||||
|
init_only: InitVar[int]
|
||||||
|
real_attr: int
|
||||||
|
|
||||||
|
|
||||||
|
def test_demonstrate():
|
||||||
|
assert Foo(1, 2) == Foo(1, 3)
|
|
@ -2,6 +2,7 @@ import pytest
|
||||||
from _pytest._io.saferepr import _pformat_dispatch
|
from _pytest._io.saferepr import _pformat_dispatch
|
||||||
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
from _pytest._io.saferepr import saferepr_unlimited
|
||||||
|
|
||||||
|
|
||||||
def test_simple_repr():
|
def test_simple_repr():
|
||||||
|
@ -179,3 +180,23 @@ def test_broken_getattribute():
|
||||||
assert saferepr(SomeClass()).startswith(
|
assert saferepr(SomeClass()).startswith(
|
||||||
"<[RuntimeError() raised in repr()] SomeClass object at 0x"
|
"<[RuntimeError() raised in repr()] SomeClass object at 0x"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_saferepr_unlimited():
|
||||||
|
dict5 = {f"v{i}": i for i in range(5)}
|
||||||
|
assert saferepr_unlimited(dict5) == "{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4}"
|
||||||
|
|
||||||
|
dict_long = {f"v{i}": i for i in range(1_000)}
|
||||||
|
r = saferepr_unlimited(dict_long)
|
||||||
|
assert "..." not in r
|
||||||
|
assert "\n" not in r
|
||||||
|
|
||||||
|
|
||||||
|
def test_saferepr_unlimited_exc():
|
||||||
|
class A:
|
||||||
|
def __repr__(self):
|
||||||
|
raise ValueError(42)
|
||||||
|
|
||||||
|
assert saferepr_unlimited(A()).startswith(
|
||||||
|
"<[ValueError(42) raised in repr()] A object at 0x"
|
||||||
|
)
|
||||||
|
|
|
@ -11,5 +11,5 @@ pytest-rerunfailures==10.2
|
||||||
pytest-sugar==0.9.4
|
pytest-sugar==0.9.4
|
||||||
pytest-trio==0.7.0
|
pytest-trio==0.7.0
|
||||||
pytest-twisted==1.13.4
|
pytest-twisted==1.13.4
|
||||||
twisted==22.1.0
|
twisted==22.2.0
|
||||||
pytest-xvfb==2.0.0
|
pytest-xvfb==2.0.0
|
||||||
|
|
|
@ -92,9 +92,7 @@ SOME_INT = r"[0-9]+\s*"
|
||||||
|
|
||||||
|
|
||||||
class TestApprox:
|
class TestApprox:
|
||||||
def test_error_messages(self, assert_approx_raises_regex):
|
def test_error_messages_native_dtypes(self, assert_approx_raises_regex):
|
||||||
np = pytest.importorskip("numpy")
|
|
||||||
|
|
||||||
assert_approx_raises_regex(
|
assert_approx_raises_regex(
|
||||||
2.0,
|
2.0,
|
||||||
1.0,
|
1.0,
|
||||||
|
@ -135,6 +133,22 @@ class TestApprox:
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Specific test for comparison with 0.0 (relative diff will be 'inf')
|
||||||
|
assert_approx_raises_regex(
|
||||||
|
[0.0],
|
||||||
|
[1.0],
|
||||||
|
[
|
||||||
|
r" comparison failed. Mismatched elements: 1 / 1:",
|
||||||
|
rf" Max absolute difference: {SOME_FLOAT}",
|
||||||
|
r" Max relative difference: inf",
|
||||||
|
r" Index \| Obtained\s+\| Expected ",
|
||||||
|
rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_error_messages_numpy_dtypes(self, assert_approx_raises_regex):
|
||||||
|
np = pytest.importorskip("numpy")
|
||||||
|
|
||||||
a = np.linspace(0, 100, 20)
|
a = np.linspace(0, 100, 20)
|
||||||
b = np.linspace(0, 100, 20)
|
b = np.linspace(0, 100, 20)
|
||||||
a[10] += 0.5
|
a[10] += 0.5
|
||||||
|
@ -175,18 +189,6 @@ class TestApprox:
|
||||||
)
|
)
|
||||||
|
|
||||||
# Specific test for comparison with 0.0 (relative diff will be 'inf')
|
# Specific test for comparison with 0.0 (relative diff will be 'inf')
|
||||||
assert_approx_raises_regex(
|
|
||||||
[0.0],
|
|
||||||
[1.0],
|
|
||||||
[
|
|
||||||
r" comparison failed. Mismatched elements: 1 / 1:",
|
|
||||||
rf" Max absolute difference: {SOME_FLOAT}",
|
|
||||||
r" Max relative difference: inf",
|
|
||||||
r" Index \| Obtained\s+\| Expected ",
|
|
||||||
rf"\s*0\s*\| {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
assert_approx_raises_regex(
|
assert_approx_raises_regex(
|
||||||
np.array([0.0]),
|
np.array([0.0]),
|
||||||
np.array([1.0]),
|
np.array([1.0]),
|
||||||
|
|
|
@ -191,10 +191,12 @@ class TestRaises:
|
||||||
int("asdf")
|
int("asdf")
|
||||||
|
|
||||||
msg = "with base 16"
|
msg = "with base 16"
|
||||||
expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format(
|
expr = (
|
||||||
msg
|
"Regex pattern did not match.\n"
|
||||||
|
f" Regex: {msg!r}\n"
|
||||||
|
" Input: \"invalid literal for int() with base 10: 'asdf'\""
|
||||||
)
|
)
|
||||||
with pytest.raises(AssertionError, match=re.escape(expr)):
|
with pytest.raises(AssertionError, match="(?m)" + re.escape(expr)):
|
||||||
with pytest.raises(ValueError, match=msg):
|
with pytest.raises(ValueError, match=msg):
|
||||||
int("asdf", base=10)
|
int("asdf", base=10)
|
||||||
|
|
||||||
|
@ -217,7 +219,7 @@ class TestRaises:
|
||||||
with pytest.raises(AssertionError, match="'foo"):
|
with pytest.raises(AssertionError, match="'foo"):
|
||||||
raise AssertionError("'bar")
|
raise AssertionError("'bar")
|
||||||
(msg,) = excinfo.value.args
|
(msg,) = excinfo.value.args
|
||||||
assert msg == 'Regex pattern "\'foo" does not match "\'bar".'
|
assert msg == '''Regex pattern did not match.\n Regex: "'foo"\n Input: "'bar"'''
|
||||||
|
|
||||||
def test_match_failure_exact_string_message(self):
|
def test_match_failure_exact_string_message(self):
|
||||||
message = "Oh here is a message with (42) numbers in parameters"
|
message = "Oh here is a message with (42) numbers in parameters"
|
||||||
|
@ -226,9 +228,10 @@ class TestRaises:
|
||||||
raise AssertionError(message)
|
raise AssertionError(message)
|
||||||
(msg,) = excinfo.value.args
|
(msg,) = excinfo.value.args
|
||||||
assert msg == (
|
assert msg == (
|
||||||
"Regex pattern 'Oh here is a message with (42) numbers in "
|
"Regex pattern did not match.\n"
|
||||||
"parameters' does not match 'Oh here is a message with (42) "
|
" Regex: 'Oh here is a message with (42) numbers in parameters'\n"
|
||||||
"numbers in parameters'. Did you mean to `re.escape()` the regex?"
|
" Input: 'Oh here is a message with (42) numbers in parameters'\n"
|
||||||
|
" Did you mean to `re.escape()` the regex?"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_raises_match_wrong_type(self):
|
def test_raises_match_wrong_type(self):
|
||||||
|
|
|
@ -882,6 +882,13 @@ class TestAssert_reprcompare_dataclass:
|
||||||
result.assert_outcomes(failed=1, passed=0)
|
result.assert_outcomes(failed=1, passed=0)
|
||||||
result.stdout.no_re_match_line(".*Differing attributes.*")
|
result.stdout.no_re_match_line(".*Differing attributes.*")
|
||||||
|
|
||||||
|
def test_data_classes_with_initvar(self, pytester: Pytester) -> None:
|
||||||
|
p = pytester.copy_example("dataclasses/test_compare_initvar.py")
|
||||||
|
# issue 9820
|
||||||
|
result = pytester.runpytest(p, "-vv")
|
||||||
|
result.assert_outcomes(failed=1, passed=0)
|
||||||
|
result.stdout.no_re_match_line(".*AttributeError.*")
|
||||||
|
|
||||||
|
|
||||||
class TestAssert_reprcompare_attrsclass:
|
class TestAssert_reprcompare_attrsclass:
|
||||||
def test_attrs(self) -> None:
|
def test_attrs(self) -> None:
|
||||||
|
@ -1695,3 +1702,18 @@ def test_assertion_location_with_coverage(pytester: Pytester) -> None:
|
||||||
"*= 1 failed in*",
|
"*= 1 failed in*",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_reprcompare_verbose_long() -> None:
|
||||||
|
a = {f"v{i}": i for i in range(11)}
|
||||||
|
b = a.copy()
|
||||||
|
b["v2"] += 10
|
||||||
|
lines = callop("==", a, b, verbose=2)
|
||||||
|
assert lines is not None
|
||||||
|
assert lines[0] == (
|
||||||
|
"{'v0': 0, 'v1': 1, 'v2': 2, 'v3': 3, 'v4': 4, 'v5': 5, "
|
||||||
|
"'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}"
|
||||||
|
" == "
|
||||||
|
"{'v0': 0, 'v1': 1, 'v2': 12, 'v3': 3, 'v4': 4, 'v5': 5, "
|
||||||
|
"'v6': 6, 'v7': 7, 'v8': 8, 'v9': 9, 'v10': 10}"
|
||||||
|
)
|
||||||
|
|
|
@ -204,16 +204,8 @@ class TestAssertionRewrite:
|
||||||
def f4() -> None:
|
def f4() -> None:
|
||||||
assert sys == 42 # type: ignore[comparison-overlap]
|
assert sys == 42 # type: ignore[comparison-overlap]
|
||||||
|
|
||||||
verbose = request.config.getoption("verbose")
|
|
||||||
msg = getmsg(f4, {"sys": sys})
|
msg = getmsg(f4, {"sys": sys})
|
||||||
if verbose > 0:
|
assert msg == "assert sys == 42"
|
||||||
assert msg == (
|
|
||||||
"assert <module 'sys' (built-in)> == 42\n"
|
|
||||||
" +<module 'sys' (built-in)>\n"
|
|
||||||
" -42"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
assert msg == "assert sys == 42"
|
|
||||||
|
|
||||||
def f5() -> None:
|
def f5() -> None:
|
||||||
assert cls == 42 # type: ignore[name-defined] # noqa: F821
|
assert cls == 42 # type: ignore[name-defined] # noqa: F821
|
||||||
|
@ -224,20 +216,7 @@ class TestAssertionRewrite:
|
||||||
msg = getmsg(f5, {"cls": X})
|
msg = getmsg(f5, {"cls": X})
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
lines = msg.splitlines()
|
lines = msg.splitlines()
|
||||||
if verbose > 1:
|
assert lines == ["assert cls == 42"]
|
||||||
assert lines == [
|
|
||||||
f"assert {X!r} == 42",
|
|
||||||
f" +{X!r}",
|
|
||||||
" -42",
|
|
||||||
]
|
|
||||||
elif verbose > 0:
|
|
||||||
assert lines == [
|
|
||||||
"assert <class 'test_...e.<locals>.X'> == 42",
|
|
||||||
f" +{X!r}",
|
|
||||||
" -42",
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
assert lines == ["assert cls == 42"]
|
|
||||||
|
|
||||||
def test_assertrepr_compare_same_width(self, request) -> None:
|
def test_assertrepr_compare_same_width(self, request) -> None:
|
||||||
"""Should use same width/truncation with same initial width."""
|
"""Should use same width/truncation with same initial width."""
|
||||||
|
@ -279,14 +258,11 @@ class TestAssertionRewrite:
|
||||||
msg = getmsg(f, {"cls": Y})
|
msg = getmsg(f, {"cls": Y})
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
lines = msg.splitlines()
|
lines = msg.splitlines()
|
||||||
if request.config.getoption("verbose") > 0:
|
assert lines == [
|
||||||
assert lines == ["assert 3 == 2", " +3", " -2"]
|
"assert 3 == 2",
|
||||||
else:
|
" + where 3 = Y.foo",
|
||||||
assert lines == [
|
" + where Y = cls()",
|
||||||
"assert 3 == 2",
|
]
|
||||||
" + where 3 = Y.foo",
|
|
||||||
" + where Y = cls()",
|
|
||||||
]
|
|
||||||
|
|
||||||
def test_assert_already_has_message(self) -> None:
|
def test_assert_already_has_message(self) -> None:
|
||||||
def f():
|
def f():
|
||||||
|
@ -663,10 +639,7 @@ class TestAssertionRewrite:
|
||||||
assert len(values) == 11
|
assert len(values) == 11
|
||||||
|
|
||||||
msg = getmsg(f)
|
msg = getmsg(f)
|
||||||
if request.config.getoption("verbose") > 0:
|
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
|
||||||
assert msg == "assert 10 == 11\n +10\n -11"
|
|
||||||
else:
|
|
||||||
assert msg == "assert 10 == 11\n + where 10 = len([0, 1, 2, 3, 4, 5, ...])"
|
|
||||||
|
|
||||||
def test_custom_reprcompare(self, monkeypatch) -> None:
|
def test_custom_reprcompare(self, monkeypatch) -> None:
|
||||||
def my_reprcompare1(op, left, right) -> str:
|
def my_reprcompare1(op, left, right) -> str:
|
||||||
|
@ -732,10 +705,7 @@ class TestAssertionRewrite:
|
||||||
msg = getmsg(f)
|
msg = getmsg(f)
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
lines = util._format_lines([msg])
|
lines = util._format_lines([msg])
|
||||||
if request.config.getoption("verbose") > 0:
|
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
||||||
assert lines == ["assert 0 == 1\n +0\n -1"]
|
|
||||||
else:
|
|
||||||
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
|
||||||
|
|
||||||
def test_custom_repr_non_ascii(self) -> None:
|
def test_custom_repr_non_ascii(self) -> None:
|
||||||
def f() -> None:
|
def f() -> None:
|
||||||
|
@ -1059,7 +1029,7 @@ class TestAssertionRewriteHookDetails:
|
||||||
e = OSError()
|
e = OSError()
|
||||||
e.errno = 10
|
e.errno = 10
|
||||||
raise e
|
raise e
|
||||||
yield
|
yield # type:ignore[unreachable]
|
||||||
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
_pytest.assertion.rewrite, "atomic_write", atomic_write_failed
|
_pytest.assertion.rewrite, "atomic_write", atomic_write_failed
|
||||||
|
|
|
@ -651,7 +651,7 @@ class Test_getinitialnodes:
|
||||||
for parent in col.listchain():
|
for parent in col.listchain():
|
||||||
assert parent.config is config
|
assert parent.config is config
|
||||||
|
|
||||||
def test_pkgfile(self, pytester: Pytester) -> None:
|
def test_pkgfile(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
|
||||||
"""Verify nesting when a module is within a package.
|
"""Verify nesting when a module is within a package.
|
||||||
The parent chain should match: Module<x.py> -> Package<subdir> -> Session.
|
The parent chain should match: Module<x.py> -> Package<subdir> -> Session.
|
||||||
Session's parent should always be None.
|
Session's parent should always be None.
|
||||||
|
@ -660,7 +660,8 @@ class Test_getinitialnodes:
|
||||||
subdir = tmp_path.joinpath("subdir")
|
subdir = tmp_path.joinpath("subdir")
|
||||||
x = ensure_file(subdir / "x.py")
|
x = ensure_file(subdir / "x.py")
|
||||||
ensure_file(subdir / "__init__.py")
|
ensure_file(subdir / "__init__.py")
|
||||||
with subdir.cwd():
|
with monkeypatch.context() as mp:
|
||||||
|
mp.chdir(subdir)
|
||||||
config = pytester.parseconfigure(x)
|
config = pytester.parseconfigure(x)
|
||||||
col = pytester.getnode(config, x)
|
col = pytester.getnode(config, x)
|
||||||
assert col is not None
|
assert col is not None
|
||||||
|
@ -1188,8 +1189,7 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
|
||||||
"""
|
"""
|
||||||
% (str(subdir),)
|
% (str(subdir),)
|
||||||
)
|
)
|
||||||
with pytester.path.cwd():
|
result = pytester.runpytest()
|
||||||
result = pytester.runpytest()
|
|
||||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
@ -1200,8 +1200,7 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None:
|
||||||
testpaths = .
|
testpaths = .
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
with pytester.path.cwd():
|
result = pytester.runpytest("--collect-only")
|
||||||
result = pytester.runpytest("--collect-only")
|
|
||||||
result.stdout.fnmatch_lines(["collected 1 item"])
|
result.stdout.fnmatch_lines(["collected 1 item"])
|
||||||
|
|
||||||
|
|
||||||
|
@ -1224,7 +1223,8 @@ def test_collect_pyargs_with_testpaths(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep)
|
monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep)
|
||||||
with root.cwd():
|
with monkeypatch.context() as mp:
|
||||||
|
mp.chdir(root)
|
||||||
result = pytester.runpytest_subprocess()
|
result = pytester.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||||
|
|
||||||
|
|
|
@ -252,6 +252,34 @@ def test_conftest_confcutdir(pytester: Pytester) -> None:
|
||||||
result.stdout.no_fnmatch_line("*warning: could not load initial*")
|
result.stdout.no_fnmatch_line("*warning: could not load initial*")
|
||||||
|
|
||||||
|
|
||||||
|
def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) -> None:
|
||||||
|
"""When using `--pyargs` to run tests in an installed packages (located e.g.
|
||||||
|
in a site-packages in the PYTHONPATH), conftest files in there are picked
|
||||||
|
up.
|
||||||
|
|
||||||
|
Regression test for #9767.
|
||||||
|
"""
|
||||||
|
# pytester dir - the source tree.
|
||||||
|
# tmp_path - the simulated site-packages dir (not in source tree).
|
||||||
|
|
||||||
|
pytester.syspathinsert(tmp_path)
|
||||||
|
pytester.makepyprojecttoml("[tool.pytest.ini_options]")
|
||||||
|
tmp_path.joinpath("foo").mkdir()
|
||||||
|
tmp_path.joinpath("foo", "__init__.py").touch()
|
||||||
|
tmp_path.joinpath("foo", "conftest.py").write_text(
|
||||||
|
textwrap.dedent(
|
||||||
|
"""\
|
||||||
|
import pytest
|
||||||
|
@pytest.fixture
|
||||||
|
def fix(): return None
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
tmp_path.joinpath("foo", "test_it.py").write_text("def test_it(fix): pass")
|
||||||
|
result = pytester.runpytest("--pyargs", "foo")
|
||||||
|
assert result.ret == 0
|
||||||
|
|
||||||
|
|
||||||
def test_conftest_symlink(pytester: Pytester) -> None:
|
def test_conftest_symlink(pytester: Pytester) -> None:
|
||||||
"""`conftest.py` discovery follows normal path resolution and does not resolve symlinks."""
|
"""`conftest.py` discovery follows normal path resolution and does not resolve symlinks."""
|
||||||
# Structure:
|
# Structure:
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import re
|
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
@ -263,7 +262,7 @@ class TestWarns:
|
||||||
with pytest.warns(RuntimeWarning):
|
with pytest.warns(RuntimeWarning):
|
||||||
warnings.warn("user", UserWarning)
|
warnings.warn("user", UserWarning)
|
||||||
excinfo.match(
|
excinfo.match(
|
||||||
r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted. "
|
r"DID NOT WARN. No warnings of type \(.+RuntimeWarning.+,\) were emitted.\n"
|
||||||
r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
|
r"The list of emitted warnings is: \[UserWarning\('user',?\)\]."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -271,15 +270,15 @@ class TestWarns:
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
warnings.warn("runtime", RuntimeWarning)
|
warnings.warn("runtime", RuntimeWarning)
|
||||||
excinfo.match(
|
excinfo.match(
|
||||||
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. "
|
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
|
||||||
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)\]."
|
r"The list of emitted warnings is: \[RuntimeWarning\('runtime',?\)]."
|
||||||
)
|
)
|
||||||
|
|
||||||
with pytest.raises(pytest.fail.Exception) as excinfo:
|
with pytest.raises(pytest.fail.Exception) as excinfo:
|
||||||
with pytest.warns(UserWarning):
|
with pytest.warns(UserWarning):
|
||||||
pass
|
pass
|
||||||
excinfo.match(
|
excinfo.match(
|
||||||
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted. "
|
r"DID NOT WARN. No warnings of type \(.+UserWarning.+,\) were emitted.\n"
|
||||||
r"The list of emitted warnings is: \[\]."
|
r"The list of emitted warnings is: \[\]."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -289,18 +288,14 @@ class TestWarns:
|
||||||
warnings.warn("runtime", RuntimeWarning)
|
warnings.warn("runtime", RuntimeWarning)
|
||||||
warnings.warn("import", ImportWarning)
|
warnings.warn("import", ImportWarning)
|
||||||
|
|
||||||
message_template = (
|
messages = [each.message for each in warninfo]
|
||||||
"DID NOT WARN. No warnings of type {0} were emitted. "
|
expected_str = (
|
||||||
"The list of emitted warnings is: {1}."
|
f"DID NOT WARN. No warnings of type {warning_classes} were emitted.\n"
|
||||||
)
|
f"The list of emitted warnings is: {messages}."
|
||||||
excinfo.match(
|
|
||||||
re.escape(
|
|
||||||
message_template.format(
|
|
||||||
warning_classes, [each.message for each in warninfo]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assert str(excinfo.value) == expected_str
|
||||||
|
|
||||||
def test_record(self) -> None:
|
def test_record(self) -> None:
|
||||||
with pytest.warns(UserWarning) as record:
|
with pytest.warns(UserWarning) as record:
|
||||||
warnings.warn("user", UserWarning)
|
warnings.warn("user", UserWarning)
|
||||||
|
|
Loading…
Reference in New Issue