Compare commits
36 Commits
Author | SHA1 | Date |
---|---|---|
|
bfe8d565e4 | |
|
e19b287b5f | |
|
5dcd2be466 | |
|
9d47a39bdd | |
|
d66697ed9a | |
|
8e1bbe1a94 | |
|
d054a68931 | |
|
30a112583e | |
|
682fc81781 | |
|
331bc1be46 | |
|
69689c6eb5 | |
|
6c95cb607d | |
|
efcb81c492 | |
|
fecf977e56 | |
|
bcada5138a | |
|
8082d27d11 | |
|
25ebf53f72 | |
|
920512435a | |
|
2c1913e709 | |
|
94d6922261 | |
|
384d54e11f | |
|
2ae187e78d | |
|
c403dc538b | |
|
f72c4cf35b | |
|
6193bf73ca | |
|
9a413e4cb8 | |
|
a1f7a204df | |
|
dab199281c | |
|
c3d9dacd39 | |
|
06d759619d | |
|
a4121aa0b6 | |
|
6e26c2bf9b | |
|
23cf1feb97 | |
|
1a427d32d6 | |
|
cec5bfe058 | |
|
ef982aaf2b |
|
@ -43,6 +43,7 @@ jobs:
|
|||
"windows-py39",
|
||||
"windows-py310",
|
||||
"windows-py311",
|
||||
"windows-py312",
|
||||
|
||||
"ubuntu-py37",
|
||||
"ubuntu-py37-pluggy",
|
||||
|
@ -51,12 +52,13 @@ jobs:
|
|||
"ubuntu-py39",
|
||||
"ubuntu-py310",
|
||||
"ubuntu-py311",
|
||||
"ubuntu-py312",
|
||||
"ubuntu-pypy3",
|
||||
|
||||
"macos-py37",
|
||||
"macos-py38",
|
||||
"macos-py39",
|
||||
"macos-py310",
|
||||
"macos-py312",
|
||||
|
||||
"docs",
|
||||
"doctesting",
|
||||
|
@ -86,9 +88,13 @@ jobs:
|
|||
os: windows-latest
|
||||
tox_env: "py310-xdist"
|
||||
- name: "windows-py311"
|
||||
python: "3.11-dev"
|
||||
python: "3.11"
|
||||
os: windows-latest
|
||||
tox_env: "py311"
|
||||
- name: "windows-py312"
|
||||
python: "3.12-dev"
|
||||
os: windows-latest
|
||||
tox_env: "py312"
|
||||
|
||||
- name: "ubuntu-py37"
|
||||
python: "3.7"
|
||||
|
@ -116,10 +122,15 @@ jobs:
|
|||
os: ubuntu-latest
|
||||
tox_env: "py310-xdist"
|
||||
- name: "ubuntu-py311"
|
||||
python: "3.11-dev"
|
||||
python: "3.11"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py311"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-py312"
|
||||
python: "3.12-dev"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py312"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-pypy3"
|
||||
python: "pypy-3.7"
|
||||
os: ubuntu-latest
|
||||
|
@ -129,19 +140,19 @@ jobs:
|
|||
python: "3.7"
|
||||
os: macos-latest
|
||||
tox_env: "py37-xdist"
|
||||
- name: "macos-py38"
|
||||
python: "3.8"
|
||||
os: macos-latest
|
||||
tox_env: "py38-xdist"
|
||||
use_coverage: true
|
||||
- name: "macos-py39"
|
||||
python: "3.9"
|
||||
os: macos-latest
|
||||
tox_env: "py39-xdist"
|
||||
use_coverage: true
|
||||
- name: "macos-py310"
|
||||
python: "3.10"
|
||||
os: macos-latest
|
||||
tox_env: "py310-xdist"
|
||||
- name: "macos-py312"
|
||||
python: "3.12-dev"
|
||||
os: macos-latest
|
||||
tox_env: "py312-xdist"
|
||||
|
||||
- name: "plugins"
|
||||
python: "3.9"
|
||||
|
@ -168,6 +179,7 @@ jobs:
|
|||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
check-latest: ${{ endsWith(matrix.python, '-dev') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
|
|
|
@ -52,7 +52,7 @@ repos:
|
|||
rev: v2.2.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: ["--max-py-version=3.11", "--include-version-classifiers"]
|
||||
args: ["--max-py-version=3.12", "--include-version-classifiers"]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
|
|
3
AUTHORS
3
AUTHORS
|
@ -8,6 +8,7 @@ Abdeali JK
|
|||
Abdelrahman Elbehery
|
||||
Abhijeet Kasurde
|
||||
Adam Johnson
|
||||
Adam Stewart
|
||||
Adam Uhlir
|
||||
Ahn Ki-Wook
|
||||
Akiomi Kamakura
|
||||
|
@ -128,7 +129,6 @@ Erik M. Bray
|
|||
Evan Kepner
|
||||
Fabien Zarifian
|
||||
Fabio Zadrozny
|
||||
Felix Hofstätter
|
||||
Felix Nieuwenhuizen
|
||||
Feng Ma
|
||||
Florian Bruhin
|
||||
|
@ -167,6 +167,7 @@ Jake VanderPlas
|
|||
Jakob van Santen
|
||||
Jakub Mitoraj
|
||||
James Bourbeau
|
||||
James Frost
|
||||
Jan Balster
|
||||
Janne Vanhala
|
||||
Jason R. Coombs
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last.
|
|
@ -1 +0,0 @@
|
|||
Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods.
|
|
@ -1,3 +0,0 @@
|
|||
Allow ``-p`` arguments to include spaces (eg: ``-p no:logging`` instead of
|
||||
``-pno:logging``). Mostly useful in the ``addopts`` section of the configuration
|
||||
file.
|
|
@ -1 +0,0 @@
|
|||
pytest no longer depends on the `attrs` package (don't worry, nice diffs for attrs classes are still supported).
|
|
@ -1 +0,0 @@
|
|||
Added ``start`` and ``stop`` timestamps to ``TestReport`` objects.
|
|
@ -1 +0,0 @@
|
|||
Split the report header for ``rootdir``, ``config file`` and ``testpaths`` so each has its own line.
|
|
@ -1 +0,0 @@
|
|||
The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator.
|
|
@ -1 +0,0 @@
|
|||
:confval:`console_output_style` now supports ``progress-even-when-capture-no`` to force the use of the progress output even when capture is disabled. This is useful in large test suites where capture may have significant performance impact.
|
|
@ -1 +0,0 @@
|
|||
Fixed :fixture:`tmp_path` fixture always raising :class:`OSError` on ``emscripten`` platform due to missing :func:`os.getuid`.
|
|
@ -1 +0,0 @@
|
|||
Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully.
|
|
@ -1 +0,0 @@
|
|||
pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang <https://github.com/hylang/hy>__`.
|
|
@ -0,0 +1 @@
|
|||
The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.
|
|
@ -1 +0,0 @@
|
|||
Correctly handle ``__tracebackhide__`` for chained exceptions.
|
|
@ -1,2 +0,0 @@
|
|||
The full output of a test is no longer truncated if the truncation message would be longer than
|
||||
the hidden text. The line number shown has also been fixed.
|
|
@ -1 +0,0 @@
|
|||
``--log-disable`` CLI option added to disable individual loggers.
|
|
@ -1 +0,0 @@
|
|||
Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept.
|
|
@ -6,6 +6,9 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-7.3.2
|
||||
release-7.3.1
|
||||
release-7.3.0
|
||||
release-7.2.2
|
||||
release-7.2.1
|
||||
release-7.2.0
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
pytest-7.3.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 7.3.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
|
||||
* Alessio Izzo
|
||||
* Alex Hadley
|
||||
* Alice Purcell
|
||||
* Anthony Sottile
|
||||
* Anton Yakutovich
|
||||
* Ashish Kurmi
|
||||
* Babak Keyvani
|
||||
* Billy
|
||||
* Brandon Chinn
|
||||
* Bruno Oliveira
|
||||
* Cal Jacobson
|
||||
* Chanvin Xiao
|
||||
* Cheuk Ting Ho
|
||||
* Chris Wheeler
|
||||
* Daniel Garcia Moreno
|
||||
* Daniel Scheffler
|
||||
* Daniel Valenzuela
|
||||
* EmptyRabbit
|
||||
* Ezio Melotti
|
||||
* Felix Hofstätter
|
||||
* Florian Best
|
||||
* Florian Bruhin
|
||||
* Fredrik Berndtsson
|
||||
* Gabriel Landau
|
||||
* Garvit Shubham
|
||||
* Gergely Kalmár
|
||||
* HTRafal
|
||||
* Hugo van Kemenade
|
||||
* Ilya Konstantinov
|
||||
* Itxaso Aizpurua
|
||||
* James Gerity
|
||||
* Jay
|
||||
* John Litborn
|
||||
* Jon Parise
|
||||
* Jouke Witteveen
|
||||
* Kadino
|
||||
* Kevin C
|
||||
* Kian Eliasi
|
||||
* Klaus Rettinghaus
|
||||
* Kodi Arfer
|
||||
* Mahesh Vashishtha
|
||||
* Manuel Jacob
|
||||
* Marko Pacak
|
||||
* MatthewFlamm
|
||||
* Miro Hrončok
|
||||
* Nate Meyvis
|
||||
* Neil Girdhar
|
||||
* Nhieuvu1802
|
||||
* Nipunn Koorapati
|
||||
* Ofek Lev
|
||||
* Paul Kehrer
|
||||
* Paul Müller
|
||||
* Paul Reece
|
||||
* Pax
|
||||
* Pete Baughman
|
||||
* Peyman Salehi
|
||||
* Philipp A
|
||||
* Pierre Sassoulas
|
||||
* Prerak Patel
|
||||
* Ramsey
|
||||
* Ran Benita
|
||||
* Robert O'Shea
|
||||
* Ronny Pfannschmidt
|
||||
* Rowin
|
||||
* Ruth Comer
|
||||
* Samuel Colvin
|
||||
* Samuel Gaist
|
||||
* Sandro Tosi
|
||||
* Santiago Castro
|
||||
* Shantanu
|
||||
* Simon K
|
||||
* Stefanie Molin
|
||||
* Stephen Rosen
|
||||
* Sviatoslav Sydorenko
|
||||
* Tatiana Ovary
|
||||
* Teejay
|
||||
* Thierry Moisan
|
||||
* Thomas Grainger
|
||||
* Tim Hoffmann
|
||||
* Tobias Diez
|
||||
* Tony Narlock
|
||||
* Vivaan Verma
|
||||
* Wolfremium
|
||||
* Yannick PÉROUX
|
||||
* Yusuke Kadowaki
|
||||
* Zac Hatfield-Dodds
|
||||
* Zach OBrien
|
||||
* aizpurua23a
|
||||
* bitzge
|
||||
* bluthej
|
||||
* gresm
|
||||
* holesch
|
||||
* itxasos23
|
||||
* johnkangw
|
||||
* q0w
|
||||
* rdb
|
||||
* s-padmanaban
|
||||
* skhomuti
|
||||
* sommersoft
|
||||
* vin01
|
||||
* wim glenn
|
||||
* wodny
|
||||
* zx.qiu
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -0,0 +1,18 @@
|
|||
pytest-7.3.1
|
||||
=======================================
|
||||
|
||||
pytest 7.3.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
|
|
@ -0,0 +1,21 @@
|
|||
pytest-7.3.2
|
||||
=======================================
|
||||
|
||||
pytest 7.3.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:
|
||||
|
||||
* Adam J. Stewart
|
||||
* Alessio Izzo
|
||||
* Bruno Oliveira
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -92,3 +92,5 @@ pytest version min. Python version
|
|||
5.0 - 6.1 3.5+
|
||||
3.3 - 4.6 2.7, 3.4+
|
||||
============== ===================
|
||||
|
||||
`Status of Python Versions <https://devguide.python.org/versions/>`__.
|
||||
|
|
|
@ -207,7 +207,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
|||
* caplog.record_tuples -> list of (logger_name, level, message) tuples
|
||||
* caplog.clear() -> clear captured records and formatted log output string
|
||||
|
||||
monkeypatch -- .../_pytest/monkeypatch.py:29
|
||||
monkeypatch -- .../_pytest/monkeypatch.py:30
|
||||
A convenient fixture for monkey-patching.
|
||||
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
|
|
|
@ -28,6 +28,129 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 7.3.2 (2023-06-10)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10169 <https://github.com/pytest-dev/pytest/issues/10169>`_: Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems.
|
||||
|
||||
|
||||
- `#10894 <https://github.com/pytest-dev/pytest/issues/10894>`_: Support for Python 3.12 (beta at the time of writing).
|
||||
|
||||
|
||||
- `#10987 <https://github.com/pytest-dev/pytest/issues/10987>`_: :confval:`testpaths` is now honored to load root ``conftests``.
|
||||
|
||||
|
||||
- `#10999 <https://github.com/pytest-dev/pytest/issues/10999>`_: The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments.
|
||||
|
||||
|
||||
- `#11028 <https://github.com/pytest-dev/pytest/issues/11028>`_: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call.
|
||||
|
||||
|
||||
- `#11054 <https://github.com/pytest-dev/pytest/issues/11054>`_: Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files).
|
||||
|
||||
|
||||
pytest 7.3.1 (2023-04-14)
|
||||
=========================
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#10875 <https://github.com/pytest-dev/pytest/issues/10875>`_: Python 3.12 support: fixed ``RuntimeError: TestResult has no addDuration method`` when running ``unittest`` tests.
|
||||
|
||||
|
||||
- `#10890 <https://github.com/pytest-dev/pytest/issues/10890>`_: Python 3.12 support: fixed ``shutil.rmtree(onerror=...)`` deprecation warning when using :fixture:`tmp_path`.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10896 <https://github.com/pytest-dev/pytest/issues/10896>`_: Fixed performance regression related to :fixture:`tmp_path` and the new :confval:`tmp_path_retention_policy` option.
|
||||
|
||||
|
||||
- `#10903 <https://github.com/pytest-dev/pytest/issues/10903>`_: Fix crash ``INTERNALERROR IndexError: list index out of range`` which happens when displaying an exception where all entries are hidden.
|
||||
This reverts the change "Correctly handle ``__tracebackhide__`` for chained exceptions." introduced in version 7.3.0.
|
||||
|
||||
|
||||
pytest 7.3.0 (2023-04-08)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#10525 <https://github.com/pytest-dev/pytest/issues/10525>`_: Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods.
|
||||
|
||||
|
||||
- `#10755 <https://github.com/pytest-dev/pytest/issues/10755>`_: :confval:`console_output_style` now supports ``progress-even-when-capture-no`` to force the use of the progress output even when capture is disabled. This is useful in large test suites where capture may have significant performance impact.
|
||||
|
||||
|
||||
- `#7431 <https://github.com/pytest-dev/pytest/issues/7431>`_: ``--log-disable`` CLI option added to disable individual loggers.
|
||||
|
||||
|
||||
- `#8141 <https://github.com/pytest-dev/pytest/issues/8141>`_: Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept.
|
||||
|
||||
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#10226 <https://github.com/pytest-dev/pytest/issues/10226>`_: If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last.
|
||||
|
||||
|
||||
- `#10658 <https://github.com/pytest-dev/pytest/issues/10658>`_: Allow ``-p`` arguments to include spaces (eg: ``-p no:logging`` instead of
|
||||
``-pno:logging``). Mostly useful in the ``addopts`` section of the configuration
|
||||
file.
|
||||
|
||||
|
||||
- `#10710 <https://github.com/pytest-dev/pytest/issues/10710>`_: Added ``start`` and ``stop`` timestamps to ``TestReport`` objects.
|
||||
|
||||
|
||||
- `#10727 <https://github.com/pytest-dev/pytest/issues/10727>`_: Split the report header for ``rootdir``, ``config file`` and ``testpaths`` so each has its own line.
|
||||
|
||||
|
||||
- `#10840 <https://github.com/pytest-dev/pytest/issues/10840>`_: pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang <https://github.com/hylang/hy>__`.
|
||||
|
||||
|
||||
- `#6267 <https://github.com/pytest-dev/pytest/issues/6267>`_: The full output of a test is no longer truncated if the truncation message would be longer than
|
||||
the hidden text. The line number shown has also been fixed.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10743 <https://github.com/pytest-dev/pytest/issues/10743>`_: The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator.
|
||||
|
||||
|
||||
- `#10765 <https://github.com/pytest-dev/pytest/issues/10765>`_: Fixed :fixture:`tmp_path` fixture always raising :class:`OSError` on ``emscripten`` platform due to missing :func:`os.getuid`.
|
||||
|
||||
|
||||
- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Correctly handle ``__tracebackhide__`` for chained exceptions.
|
||||
NOTE: This change was reverted in version 7.3.1.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#10782 <https://github.com/pytest-dev/pytest/issues/10782>`_: Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#10669 <https://github.com/pytest-dev/pytest/issues/10669>`_: pytest no longer directly depends on the `attrs <https://www.attrs.org/en/stable/>`__ package. While
|
||||
we at pytest all love the package dearly and would like to thank the ``attrs`` team for many years of cooperation and support,
|
||||
it makes sense for ``pytest`` to have as little external dependencies as possible, as this helps downstream projects.
|
||||
With that in mind, we have replaced the pytest's limited internal usage to use the standard library's ``dataclasses`` instead.
|
||||
|
||||
Nice diffs for ``attrs`` classes are still supported though.
|
||||
|
||||
|
||||
pytest 7.2.2 (2023-03-03)
|
||||
=========================
|
||||
|
||||
|
@ -468,7 +591,7 @@ Breaking Changes
|
|||
- `#7259 <https://github.com/pytest-dev/pytest/issues/7259>`_: The :ref:`Node.reportinfo() <non-python tests>` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`.
|
||||
|
||||
Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation.
|
||||
Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted.
|
||||
Since `py.path.local` is an `os.PathLike[str]`, these plugins are unaffacted.
|
||||
|
||||
Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`.
|
||||
Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead.
|
||||
|
@ -3968,7 +4091,7 @@ Removals
|
|||
See our :ref:`docs <calling fixtures directly deprecated>` on information on how to update your code.
|
||||
|
||||
|
||||
- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check.
|
||||
- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than an existence check.
|
||||
|
||||
Use ``Node.get_closest_marker(name)`` as a replacement.
|
||||
|
||||
|
|
|
@ -273,6 +273,9 @@ html_show_sourcelink = False
|
|||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "pytestdoc"
|
||||
|
||||
# The base URL which points to the root of the HTML documentation. It is used
|
||||
# to indicate the location of document using the canonical link relation (#12363).
|
||||
html_baseurl = "https://docs.pytest.org/en/stable/"
|
||||
|
||||
# -- Options for LaTeX output --------------------------------------------------
|
||||
|
||||
|
@ -341,7 +344,7 @@ epub_copyright = "2013, holger krekel et alii"
|
|||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
# epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# The unique identifier of the text. This can be an ISBN number
|
||||
# or the project homepage.
|
||||
# epub_identifier = ''
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ class YamlItem(pytest.Item):
|
|||
" no further details known at this point.",
|
||||
]
|
||||
)
|
||||
return super().repr_failure(excinfo)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.path, 0, f"usecase: {self.name}"
|
||||
|
|
|
@ -502,8 +502,12 @@ Running it results in some skips if we don't have all the python interpreters in
|
|||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
........................... [100%]
|
||||
27 passed in 0.12s
|
||||
sssssssssssssssssssssssssss [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [9] multipython.py:69: 'python3.5' not found
|
||||
SKIPPED [9] multipython.py:69: 'python3.6' not found
|
||||
SKIPPED [9] multipython.py:69: 'python3.7' not found
|
||||
27 skipped in 0.12s
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
|
|
|
@ -70,12 +70,12 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
|
||||
> assert not f()
|
||||
E assert not 42
|
||||
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef0002>()
|
||||
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef0006>()
|
||||
|
||||
failure_demo.py:39: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0006>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0007>
|
||||
|
||||
def test_eq_text(self):
|
||||
> assert "spam" == "eggs"
|
||||
|
@ -86,7 +86,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:44: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0007>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0008>
|
||||
|
||||
def test_eq_similar_text(self):
|
||||
> assert "foo 1 bar" == "foo 2 bar"
|
||||
|
@ -99,7 +99,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:47: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0008>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0009>
|
||||
|
||||
def test_eq_multiline_text(self):
|
||||
> assert "foo\nspam\nbar" == "foo\neggs\nbar"
|
||||
|
@ -112,7 +112,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:50: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0009>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000a>
|
||||
|
||||
def test_eq_long_text(self):
|
||||
a = "1" * 100 + "a" + "2" * 100
|
||||
|
@ -129,7 +129,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:55: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000a>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000b>
|
||||
|
||||
def test_eq_long_text_multiline(self):
|
||||
a = "1\n" * 100 + "a" + "2\n" * 100
|
||||
|
@ -149,7 +149,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:60: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000b>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000c>
|
||||
|
||||
def test_eq_list(self):
|
||||
> assert [0, 1, 2] == [0, 1, 3]
|
||||
|
@ -160,7 +160,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:63: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000c>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000d>
|
||||
|
||||
def test_eq_list_long(self):
|
||||
a = [0] * 100 + [1] + [3] * 100
|
||||
|
@ -173,7 +173,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:68: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000d>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000e>
|
||||
|
||||
def test_eq_dict(self):
|
||||
> assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0}
|
||||
|
@ -190,7 +190,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:71: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000e>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000f>
|
||||
|
||||
def test_eq_set(self):
|
||||
> assert {0, 10, 11, 12} == {0, 20, 21}
|
||||
|
@ -207,7 +207,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:74: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef000f>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0010>
|
||||
|
||||
def test_eq_longer_list(self):
|
||||
> assert [1, 2] == [1, 2, 3]
|
||||
|
@ -218,7 +218,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:77: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0010>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0011>
|
||||
|
||||
def test_in_list(self):
|
||||
> assert 1 in [0, 2, 3, 4, 5]
|
||||
|
@ -227,7 +227,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:80: AssertionError
|
||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0011>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0012>
|
||||
|
||||
def test_not_in_text_multiline(self):
|
||||
text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail"
|
||||
|
@ -245,7 +245,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:84: AssertionError
|
||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0012>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0013>
|
||||
|
||||
def test_not_in_text_single(self):
|
||||
text = "single foo line"
|
||||
|
@ -258,7 +258,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:88: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0013>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0014>
|
||||
|
||||
def test_not_in_text_single_long(self):
|
||||
text = "head " * 50 + "foo " + "tail " * 20
|
||||
|
@ -271,7 +271,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:92: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0014>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0015>
|
||||
|
||||
def test_not_in_text_single_long_term(self):
|
||||
text = "head " * 50 + "f" * 70 + "tail " * 20
|
||||
|
@ -284,7 +284,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:96: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0015>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0016>
|
||||
|
||||
def test_eq_dataclass(self):
|
||||
from dataclasses import dataclass
|
||||
|
@ -311,7 +311,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:108: AssertionError
|
||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0016>
|
||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef0017>
|
||||
|
||||
def test_eq_attrs(self):
|
||||
import attr
|
||||
|
@ -345,7 +345,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
i = Foo()
|
||||
> assert i.b == 2
|
||||
E assert 1 == 2
|
||||
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef0017>.b
|
||||
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef0018>.b
|
||||
|
||||
failure_demo.py:128: AssertionError
|
||||
_________________________ test_attribute_instance __________________________
|
||||
|
@ -356,8 +356,8 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
|
||||
> assert Foo().b == 2
|
||||
E AssertionError: assert 1 == 2
|
||||
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef0018>.b
|
||||
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef0018> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
||||
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef0019>.b
|
||||
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef0019> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
||||
|
||||
failure_demo.py:135: AssertionError
|
||||
__________________________ test_attribute_failure __________________________
|
||||
|
@ -375,7 +375,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:146:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef0019>
|
||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef001a>
|
||||
|
||||
def _get_b(self):
|
||||
> raise Exception("Failed to get attrib")
|
||||
|
@ -393,15 +393,15 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
|
||||
> assert Foo().b == Bar().b
|
||||
E AssertionError: assert 1 == 2
|
||||
E + where 1 = <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef001a>.b
|
||||
E + where <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef001a> = <class 'failure_demo.test_attribute_multiple.<locals>.Foo'>()
|
||||
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef001b>.b
|
||||
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef001b> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
||||
E + where 1 = <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef001b>.b
|
||||
E + where <failure_demo.test_attribute_multiple.<locals>.Foo object at 0xdeadbeef001b> = <class 'failure_demo.test_attribute_multiple.<locals>.Foo'>()
|
||||
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef001c>.b
|
||||
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef001c> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
||||
|
||||
failure_demo.py:156: AssertionError
|
||||
__________________________ TestRaises.test_raises __________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001c>
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001d>
|
||||
|
||||
def test_raises(self):
|
||||
s = "qwe"
|
||||
|
@ -411,7 +411,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:166: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001d>
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001e>
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
> raises(OSError, int, "3")
|
||||
|
@ -420,7 +420,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:169: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001e>
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001f>
|
||||
|
||||
def test_raise(self):
|
||||
> raise ValueError("demo error")
|
||||
|
@ -429,7 +429,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:172: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef001f>
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0020>
|
||||
|
||||
def test_tupleerror(self):
|
||||
> a, b = [1] # NOQA
|
||||
|
@ -438,7 +438,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:175: ValueError
|
||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0020>
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0021>
|
||||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
items = [1, 2, 3]
|
||||
|
@ -451,7 +451,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0021>
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0022>
|
||||
|
||||
def test_some_error(self):
|
||||
> if namenotexi: # NOQA
|
||||
|
@ -482,7 +482,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
abc-123:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0022>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0023>
|
||||
|
||||
def test_complex_error(self):
|
||||
def f():
|
||||
|
@ -508,7 +508,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:6: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0023>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0024>
|
||||
|
||||
def test_z1_unpack_error(self):
|
||||
items = []
|
||||
|
@ -518,7 +518,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:217: ValueError
|
||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0024>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0025>
|
||||
|
||||
def test_z2_type_error(self):
|
||||
items = 3
|
||||
|
@ -528,20 +528,20 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:221: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0025>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0026>
|
||||
|
||||
def test_startswith(self):
|
||||
s = "123"
|
||||
g = "456"
|
||||
> assert s.startswith(g)
|
||||
E AssertionError: assert False
|
||||
E + where False = <built-in method startswith of str object at 0xdeadbeef0026>('456')
|
||||
E + where <built-in method startswith of str object at 0xdeadbeef0026> = '123'.startswith
|
||||
E + where False = <built-in method startswith of str object at 0xdeadbeef0027>('456')
|
||||
E + where <built-in method startswith of str object at 0xdeadbeef0027> = '123'.startswith
|
||||
|
||||
failure_demo.py:226: AssertionError
|
||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0027>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef0028>
|
||||
|
||||
def test_startswith_nested(self):
|
||||
def f():
|
||||
|
@ -552,15 +552,15 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
|
||||
> assert f().startswith(g())
|
||||
E AssertionError: assert False
|
||||
E + where False = <built-in method startswith of str object at 0xdeadbeef0026>('456')
|
||||
E + where <built-in method startswith of str object at 0xdeadbeef0026> = '123'.startswith
|
||||
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0028>()
|
||||
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef0029>()
|
||||
E + where False = <built-in method startswith of str object at 0xdeadbeef0027>('456')
|
||||
E + where <built-in method startswith of str object at 0xdeadbeef0027> = '123'.startswith
|
||||
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0029>()
|
||||
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef002a>()
|
||||
|
||||
failure_demo.py:235: AssertionError
|
||||
_____________________ TestMoreErrors.test_global_func ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002a>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
|
||||
|
||||
def test_global_func(self):
|
||||
> assert isinstance(globf(42), float)
|
||||
|
@ -571,18 +571,18 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:238: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
|
||||
|
||||
def test_instance(self):
|
||||
self.x = 6 * 7
|
||||
> assert self.x != 42
|
||||
E assert 42 != 42
|
||||
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>.x
|
||||
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>.x
|
||||
|
||||
failure_demo.py:242: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
|
||||
|
||||
def test_compare(self):
|
||||
> assert globf(10) < 5
|
||||
|
@ -592,7 +592,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:245: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef002e>
|
||||
|
||||
def test_try_finally(self):
|
||||
x = 1
|
||||
|
@ -603,7 +603,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:250: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002e>
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
|
||||
|
||||
def test_single_line(self):
|
||||
class A:
|
||||
|
@ -618,7 +618,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:261: AssertionError
|
||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
|
||||
|
||||
def test_multiline(self):
|
||||
class A:
|
||||
|
@ -637,7 +637,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
failure_demo.py:268: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
|
||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0031>
|
||||
|
||||
def test_custom_repr(self):
|
||||
class JSON:
|
||||
|
|
|
@ -22,7 +22,7 @@ Install ``pytest``
|
|||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 7.2.0.dev534+ga2c84caaa.d20230317
|
||||
pytest 7.3.2
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
:orphan:
|
||||
|
||||
..
|
||||
.. sidebar:: Next Open Trainings
|
||||
.. sidebar:: Next Open Trainings
|
||||
|
||||
- `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
|
||||
- `pytest tips and tricks for a better testsuite <https://ep2023.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2023 <https://ep2023.europython.eu/>`_, July 18th (3h), Prague/Remote
|
||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote
|
||||
|
||||
Also see :doc:`previous talks and blogposts <talks>`.
|
||||
Also see :doc:`previous talks and blogposts <talks>`.
|
||||
|
||||
.. _features:
|
||||
|
||||
|
|
|
@ -1049,11 +1049,11 @@ 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.
|
||||
When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative 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.
|
||||
When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable.
|
||||
|
||||
.. envvar:: PYTEST_ADDOPTS
|
||||
|
||||
|
@ -1713,13 +1713,12 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
|
||||
.. confval:: testpaths
|
||||
|
||||
|
||||
|
||||
Sets list of directories that should be searched for tests when
|
||||
no specific directories, files or test ids are given in the command line when
|
||||
executing pytest from the :ref:`rootdir <rootdir>` directory.
|
||||
File system paths may use shell-style wildcards, including the recursive
|
||||
``**`` pattern.
|
||||
|
||||
Useful when all project tests are in a known location to speed up
|
||||
test collection and to avoid picking up undesired tests by accident.
|
||||
|
||||
|
@ -1728,8 +1727,17 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
[pytest]
|
||||
testpaths = testing doc
|
||||
|
||||
This tells pytest to only look for tests in ``testing`` and ``doc``
|
||||
directories when executing from the root directory.
|
||||
This configuration means that executing:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
pytest
|
||||
|
||||
has the same practical effects as executing:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
pytest testing doc
|
||||
|
||||
|
||||
.. confval:: tmp_path_retention_count
|
||||
|
@ -1744,7 +1752,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
[pytest]
|
||||
tmp_path_retention_count = 3
|
||||
|
||||
Default: 3
|
||||
Default: ``3``
|
||||
|
||||
|
||||
.. confval:: tmp_path_retention_policy
|
||||
|
@ -1763,7 +1771,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
[pytest]
|
||||
tmp_path_retention_policy = "all"
|
||||
|
||||
Default: all
|
||||
Default: ``all``
|
||||
|
||||
|
||||
.. confval:: usefixtures
|
||||
|
@ -1996,7 +2004,7 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
|||
Auto-indent multiline messages passed to the logging
|
||||
module. Accepts true|on, false|off or an integer.
|
||||
--log-disable=LOGGER_DISABLE
|
||||
Disable a logger by name. Can be passed multipe
|
||||
Disable a logger by name. Can be passed multiple
|
||||
times.
|
||||
|
||||
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
|
||||
|
|
|
@ -22,6 +22,7 @@ classifiers =
|
|||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
Programming Language :: Python :: 3.11
|
||||
Programming Language :: Python :: 3.12
|
||||
Topic :: Software Development :: Libraries
|
||||
Topic :: Software Development :: Testing
|
||||
Topic :: Utilities
|
||||
|
@ -73,6 +74,7 @@ testing =
|
|||
nose
|
||||
pygments>=2.7.2
|
||||
requests
|
||||
setuptools
|
||||
xmlschema
|
||||
|
||||
[options.package_data]
|
||||
|
|
|
@ -411,13 +411,13 @@ class Traceback(List[TracebackEntry]):
|
|||
"""
|
||||
return Traceback(filter(fn, self), self._excinfo)
|
||||
|
||||
def getcrashentry(self) -> Optional[TracebackEntry]:
|
||||
def getcrashentry(self) -> TracebackEntry:
|
||||
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
|
||||
for i in range(-1, -len(self) - 1, -1):
|
||||
entry = self[i]
|
||||
if not entry.ishidden():
|
||||
return entry
|
||||
return None
|
||||
return self[-1]
|
||||
|
||||
def recursionindex(self) -> Optional[int]:
|
||||
"""Return the index of the frame/TracebackEntry where recursion originates if
|
||||
|
@ -602,13 +602,11 @@ class ExceptionInfo(Generic[E]):
|
|||
"""
|
||||
return isinstance(self.value, exc)
|
||||
|
||||
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
||||
def _getreprcrash(self) -> "ReprFileLocation":
|
||||
exconly = self.exconly(tryshort=True)
|
||||
entry = self.traceback.getcrashentry()
|
||||
if entry:
|
||||
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
||||
return ReprFileLocation(path, lineno + 1, exconly)
|
||||
return None
|
||||
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
||||
return ReprFileLocation(path, lineno + 1, exconly)
|
||||
|
||||
def getrepr(
|
||||
self,
|
||||
|
@ -946,14 +944,9 @@ class FormattedExcinfo:
|
|||
)
|
||||
else:
|
||||
reprtraceback = self.repr_traceback(excinfo_)
|
||||
|
||||
# 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)]
|
||||
reprcrash: Optional[ReprFileLocation] = (
|
||||
excinfo_._getreprcrash() if self.style != "value" else None
|
||||
)
|
||||
else:
|
||||
# Fallback to native repr if the exception doesn't have a traceback:
|
||||
# ExceptionInfo objects require a full traceback to work.
|
||||
|
@ -961,8 +954,8 @@ class FormattedExcinfo:
|
|||
traceback.format_exception(type(e), e, None)
|
||||
)
|
||||
reprcrash = None
|
||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||
|
||||
repr_chain += [(reprtraceback, reprcrash, descr)]
|
||||
if e.__cause__ is not None and self.chain:
|
||||
e = e.__cause__
|
||||
excinfo_ = (
|
||||
|
@ -1053,7 +1046,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
|||
@dataclasses.dataclass(eq=False)
|
||||
class ReprExceptionInfo(ExceptionRepr):
|
||||
reprtraceback: "ReprTraceback"
|
||||
reprcrash: Optional["ReprFileLocation"]
|
||||
reprcrash: "ReprFileLocation"
|
||||
|
||||
def toterminal(self, tw: TerminalWriter) -> None:
|
||||
self.reprtraceback.toterminal(tw)
|
||||
|
|
|
@ -953,7 +953,7 @@ class LocalPath:
|
|||
else:
|
||||
p.dirpath()._ensuredirs()
|
||||
if not p.check(file=1):
|
||||
p.open("w").close()
|
||||
p.open("wb").close()
|
||||
return p
|
||||
|
||||
@overload
|
||||
|
|
|
@ -46,8 +46,14 @@ if TYPE_CHECKING:
|
|||
|
||||
if sys.version_info >= (3, 8):
|
||||
namedExpr = ast.NamedExpr
|
||||
astNameConstant = ast.Constant
|
||||
astStr = ast.Constant
|
||||
astNum = ast.Constant
|
||||
else:
|
||||
namedExpr = ast.Expr
|
||||
astNameConstant = ast.NameConstant
|
||||
astStr = ast.Str
|
||||
astNum = ast.Num
|
||||
|
||||
|
||||
assertstate_key = StashKey["AssertionState"]()
|
||||
|
@ -680,9 +686,12 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
if (
|
||||
expect_docstring
|
||||
and isinstance(item, ast.Expr)
|
||||
and isinstance(item.value, ast.Str)
|
||||
and isinstance(item.value, astStr)
|
||||
):
|
||||
doc = item.value.s
|
||||
if sys.version_info >= (3, 8):
|
||||
doc = item.value.value
|
||||
else:
|
||||
doc = item.value.s
|
||||
if self.is_rewrite_disabled(doc):
|
||||
return
|
||||
expect_docstring = False
|
||||
|
@ -814,7 +823,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
current = self.stack.pop()
|
||||
if self.stack:
|
||||
self.explanation_specifiers = self.stack[-1]
|
||||
keys = [ast.Str(key) for key in current.keys()]
|
||||
keys = [astStr(key) for key in current.keys()]
|
||||
format_dict = ast.Dict(keys, list(current.values()))
|
||||
form = ast.BinOp(expl_expr, ast.Mod(), format_dict)
|
||||
name = "@py_format" + str(next(self.variable_counter))
|
||||
|
@ -868,16 +877,16 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
negation = ast.UnaryOp(ast.Not(), top_condition)
|
||||
|
||||
if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook
|
||||
msg = self.pop_format_context(ast.Str(explanation))
|
||||
msg = self.pop_format_context(astStr(explanation))
|
||||
|
||||
# Failed
|
||||
if assert_.msg:
|
||||
assertmsg = self.helper("_format_assertmsg", assert_.msg)
|
||||
gluestr = "\n>assert "
|
||||
else:
|
||||
assertmsg = ast.Str("")
|
||||
assertmsg = astStr("")
|
||||
gluestr = "assert "
|
||||
err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg)
|
||||
err_explanation = ast.BinOp(astStr(gluestr), ast.Add(), msg)
|
||||
err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation)
|
||||
err_name = ast.Name("AssertionError", ast.Load())
|
||||
fmt = self.helper("_format_explanation", err_msg)
|
||||
|
@ -893,8 +902,8 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
hook_call_pass = ast.Expr(
|
||||
self.helper(
|
||||
"_call_assertion_pass",
|
||||
ast.Num(assert_.lineno),
|
||||
ast.Str(orig),
|
||||
astNum(assert_.lineno),
|
||||
astStr(orig),
|
||||
fmt_pass,
|
||||
)
|
||||
)
|
||||
|
@ -913,7 +922,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
variables = [
|
||||
ast.Name(name, ast.Store()) for name in self.format_variables
|
||||
]
|
||||
clear_format = ast.Assign(variables, ast.NameConstant(None))
|
||||
clear_format = ast.Assign(variables, astNameConstant(None))
|
||||
self.statements.append(clear_format)
|
||||
|
||||
else: # Original assertion rewriting
|
||||
|
@ -924,9 +933,9 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
assertmsg = self.helper("_format_assertmsg", assert_.msg)
|
||||
explanation = "\n>assert " + explanation
|
||||
else:
|
||||
assertmsg = ast.Str("")
|
||||
assertmsg = astStr("")
|
||||
explanation = "assert " + explanation
|
||||
template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation))
|
||||
template = ast.BinOp(assertmsg, ast.Add(), astStr(explanation))
|
||||
msg = self.pop_format_context(template)
|
||||
fmt = self.helper("_format_explanation", msg)
|
||||
err_name = ast.Name("AssertionError", ast.Load())
|
||||
|
@ -938,7 +947,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
# Clear temporary variables by setting them to None.
|
||||
if self.variables:
|
||||
variables = [ast.Name(name, ast.Store()) for name in self.variables]
|
||||
clear = ast.Assign(variables, ast.NameConstant(None))
|
||||
clear = ast.Assign(variables, astNameConstant(None))
|
||||
self.statements.append(clear)
|
||||
# Fix locations (line numbers/column offsets).
|
||||
for stmt in self.statements:
|
||||
|
@ -952,20 +961,20 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
# 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])
|
||||
inlocs = ast.Compare(astStr(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))
|
||||
expr = ast.IfExp(test, self.display(name), astStr(target_id))
|
||||
return name, self.explanation_param(expr)
|
||||
|
||||
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
|
||||
# Display the repr of the name if it's a local variable or
|
||||
# _should_repr_global_name() thinks it's acceptable.
|
||||
locs = ast.Call(self.builtin("locals"), [], [])
|
||||
inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs])
|
||||
inlocs = ast.Compare(astStr(name.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(name.id))
|
||||
expr = ast.IfExp(test, self.display(name), astStr(name.id))
|
||||
return name, self.explanation_param(expr)
|
||||
|
||||
def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
|
||||
|
@ -996,12 +1005,14 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
]
|
||||
):
|
||||
pytest_temp = self.variable()
|
||||
self.variables_overwrite[v.left.target.id] = pytest_temp
|
||||
self.variables_overwrite[
|
||||
v.left.target.id
|
||||
] = v.left # type:ignore[assignment]
|
||||
v.left.target.id = pytest_temp
|
||||
self.push_format_context()
|
||||
res, expl = self.visit(v)
|
||||
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
|
||||
expl_format = self.pop_format_context(ast.Str(expl))
|
||||
expl_format = self.pop_format_context(astStr(expl))
|
||||
call = ast.Call(app, [expl_format], [])
|
||||
self.expl_stmts.append(ast.Expr(call))
|
||||
if i < levels:
|
||||
|
@ -1013,7 +1024,7 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
self.statements = body = inner
|
||||
self.statements = save
|
||||
self.expl_stmts = fail_save
|
||||
expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or))
|
||||
expl_template = self.helper("_format_boolop", expl_list, astNum(is_or))
|
||||
expl = self.pop_format_context(expl_template)
|
||||
return ast.Name(res_var, ast.Load()), self.explanation_param(expl)
|
||||
|
||||
|
@ -1037,10 +1048,19 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
new_args = []
|
||||
new_kwargs = []
|
||||
for arg in call.args:
|
||||
if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite:
|
||||
arg = self.variables_overwrite[arg.id] # type:ignore[assignment]
|
||||
res, expl = self.visit(arg)
|
||||
arg_expls.append(expl)
|
||||
new_args.append(res)
|
||||
for keyword in call.keywords:
|
||||
if (
|
||||
isinstance(keyword.value, ast.Name)
|
||||
and keyword.value.id in self.variables_overwrite
|
||||
):
|
||||
keyword.value = self.variables_overwrite[
|
||||
keyword.value.id
|
||||
] # type:ignore[assignment]
|
||||
res, expl = self.visit(keyword.value)
|
||||
new_kwargs.append(ast.keyword(keyword.arg, res))
|
||||
if keyword.arg:
|
||||
|
@ -1075,7 +1095,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
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]
|
||||
comp.left = self.variables_overwrite[
|
||||
comp.left.id
|
||||
] # type:ignore[assignment]
|
||||
if isinstance(comp.left, namedExpr):
|
||||
self.variables_overwrite[
|
||||
comp.left.target.id
|
||||
] = comp.left # type:ignore[assignment]
|
||||
left_res, left_expl = self.visit(comp.left)
|
||||
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
|
||||
left_expl = f"({left_expl})"
|
||||
|
@ -1093,15 +1119,17 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
and next_operand.target.id == left_res.id
|
||||
):
|
||||
next_operand.target.id = self.variable()
|
||||
self.variables_overwrite[left_res.id] = next_operand.target.id
|
||||
self.variables_overwrite[
|
||||
left_res.id
|
||||
] = next_operand # type:ignore[assignment]
|
||||
next_res, next_expl = self.visit(next_operand)
|
||||
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
|
||||
next_expl = f"({next_expl})"
|
||||
results.append(next_res)
|
||||
sym = BINOP_MAP[op.__class__]
|
||||
syms.append(ast.Str(sym))
|
||||
syms.append(astStr(sym))
|
||||
expl = f"{left_expl} {sym} {next_expl}"
|
||||
expls.append(ast.Str(expl))
|
||||
expls.append(astStr(expl))
|
||||
res_expr = ast.Compare(left_res, [op], [next_res])
|
||||
self.statements.append(ast.Assign([store_names[i]], res_expr))
|
||||
left_res, left_expl = next_res, next_expl
|
||||
|
|
|
@ -213,7 +213,7 @@ class LFPluginCollWrapper:
|
|||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def pytest_make_collect_report(self, collector: nodes.Collector):
|
||||
if isinstance(collector, Session):
|
||||
if isinstance(collector, (Session, Package)):
|
||||
out = yield
|
||||
res: CollectReport = out.get_result()
|
||||
|
||||
|
|
|
@ -241,7 +241,7 @@ class DontReadFromInput(TextIO):
|
|||
raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()")
|
||||
|
||||
def truncate(self, size: Optional[int] = None) -> int:
|
||||
raise UnsupportedOperation("cannont truncate stdin")
|
||||
raise UnsupportedOperation("cannot truncate stdin")
|
||||
|
||||
def write(self, data: str) -> int:
|
||||
raise UnsupportedOperation("cannot write to stdin")
|
||||
|
|
|
@ -526,7 +526,10 @@ class PytestPluginManager(PluginManager):
|
|||
# Internal API for local conftest plugin handling.
|
||||
#
|
||||
def _set_initial_conftests(
|
||||
self, namespace: argparse.Namespace, rootpath: Path
|
||||
self,
|
||||
namespace: argparse.Namespace,
|
||||
rootpath: Path,
|
||||
testpaths_ini: Sequence[str],
|
||||
) -> None:
|
||||
"""Load initial conftest files given a preparsed "namespace".
|
||||
|
||||
|
@ -543,7 +546,7 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
self._noconftest = namespace.noconftest
|
||||
self._using_pyargs = namespace.pyargs
|
||||
testpaths = namespace.file_or_dir
|
||||
testpaths = namespace.file_or_dir + testpaths_ini
|
||||
foundanchor = False
|
||||
for testpath in testpaths:
|
||||
path = str(testpath)
|
||||
|
@ -552,7 +555,14 @@ class PytestPluginManager(PluginManager):
|
|||
if i != -1:
|
||||
path = path[:i]
|
||||
anchor = absolutepath(current / path)
|
||||
if anchor.exists(): # we found some file object
|
||||
|
||||
# Ensure we do not break if what appears to be an anchor
|
||||
# is in fact a very long option (#10169).
|
||||
try:
|
||||
anchor_exists = anchor.exists()
|
||||
except OSError: # pragma: no cover
|
||||
anchor_exists = False
|
||||
if anchor_exists:
|
||||
self._try_load_conftest(anchor, namespace.importmode, rootpath)
|
||||
foundanchor = True
|
||||
if not foundanchor:
|
||||
|
@ -1131,7 +1141,9 @@ class Config:
|
|||
@hookimpl(trylast=True)
|
||||
def pytest_load_initial_conftests(self, early_config: "Config") -> None:
|
||||
self.pluginmanager._set_initial_conftests(
|
||||
early_config.known_args_namespace, rootpath=early_config.rootpath
|
||||
early_config.known_args_namespace,
|
||||
rootpath=early_config.rootpath,
|
||||
testpaths_ini=self.getini("testpaths"),
|
||||
)
|
||||
|
||||
def _initini(self, args: Sequence[str]) -> None:
|
||||
|
|
|
@ -2,7 +2,6 @@ import io
|
|||
import os
|
||||
import sys
|
||||
from typing import Generator
|
||||
from typing import TextIO
|
||||
|
||||
import pytest
|
||||
from _pytest.config import Config
|
||||
|
@ -11,7 +10,7 @@ from _pytest.nodes import Item
|
|||
from _pytest.stash import StashKey
|
||||
|
||||
|
||||
fault_handler_stderr_key = StashKey[TextIO]()
|
||||
fault_handler_stderr_fd_key = StashKey[int]()
|
||||
fault_handler_originally_enabled_key = StashKey[bool]()
|
||||
|
||||
|
||||
|
@ -26,10 +25,9 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
def pytest_configure(config: Config) -> None:
|
||||
import faulthandler
|
||||
|
||||
stderr_fd_copy = os.dup(get_stderr_fileno())
|
||||
config.stash[fault_handler_stderr_key] = open(stderr_fd_copy, "w")
|
||||
config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno())
|
||||
config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled()
|
||||
faulthandler.enable(file=config.stash[fault_handler_stderr_key])
|
||||
faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key])
|
||||
|
||||
|
||||
def pytest_unconfigure(config: Config) -> None:
|
||||
|
@ -37,9 +35,9 @@ def pytest_unconfigure(config: Config) -> None:
|
|||
|
||||
faulthandler.disable()
|
||||
# Close the dup file installed during pytest_configure.
|
||||
if fault_handler_stderr_key in config.stash:
|
||||
config.stash[fault_handler_stderr_key].close()
|
||||
del config.stash[fault_handler_stderr_key]
|
||||
if fault_handler_stderr_fd_key in config.stash:
|
||||
os.close(config.stash[fault_handler_stderr_fd_key])
|
||||
del config.stash[fault_handler_stderr_fd_key]
|
||||
if config.stash.get(fault_handler_originally_enabled_key, False):
|
||||
# Re-enable the faulthandler if it was originally enabled.
|
||||
faulthandler.enable(file=get_stderr_fileno())
|
||||
|
@ -67,10 +65,10 @@ def get_timeout_config_value(config: Config) -> float:
|
|||
@pytest.hookimpl(hookwrapper=True, trylast=True)
|
||||
def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]:
|
||||
timeout = get_timeout_config_value(item.config)
|
||||
stderr = item.config.stash[fault_handler_stderr_key]
|
||||
if timeout > 0 and stderr is not None:
|
||||
if timeout > 0:
|
||||
import faulthandler
|
||||
|
||||
stderr = item.config.stash[fault_handler_stderr_fd_key]
|
||||
faulthandler.dump_traceback_later(timeout, file=stderr)
|
||||
try:
|
||||
yield
|
||||
|
|
|
@ -21,7 +21,7 @@ if TYPE_CHECKING:
|
|||
from typing_extensions import Literal
|
||||
|
||||
from _pytest._code.code import ExceptionRepr
|
||||
from _pytest.code import ExceptionInfo
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config import PytestPluginManager
|
||||
|
|
|
@ -302,7 +302,7 @@ def pytest_addoption(parser: Parser) -> None:
|
|||
action="append",
|
||||
default=[],
|
||||
dest="logger_disable",
|
||||
help="Disable a logger by name. Can be passed multipe times.",
|
||||
help="Disable a logger by name. Can be passed multiple times.",
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ import ast
|
|||
import dataclasses
|
||||
import enum
|
||||
import re
|
||||
import sys
|
||||
import types
|
||||
from typing import Callable
|
||||
from typing import Iterator
|
||||
|
@ -26,6 +27,11 @@ from typing import NoReturn
|
|||
from typing import Optional
|
||||
from typing import Sequence
|
||||
|
||||
if sys.version_info >= (3, 8):
|
||||
astNameConstant = ast.Constant
|
||||
else:
|
||||
astNameConstant = ast.NameConstant
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Expression",
|
||||
|
@ -132,7 +138,7 @@ IDENT_PREFIX = "$"
|
|||
|
||||
def expression(s: Scanner) -> ast.Expression:
|
||||
if s.accept(TokenType.EOF):
|
||||
ret: ast.expr = ast.NameConstant(False)
|
||||
ret: ast.expr = astNameConstant(False)
|
||||
else:
|
||||
ret = expr(s)
|
||||
s.accept(TokenType.EOF, reject=True)
|
||||
|
|
|
@ -7,6 +7,7 @@ from contextlib import contextmanager
|
|||
from typing import Any
|
||||
from typing import Generator
|
||||
from typing import List
|
||||
from typing import Mapping
|
||||
from typing import MutableMapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
|
@ -129,7 +130,7 @@ class MonkeyPatch:
|
|||
|
||||
def __init__(self) -> None:
|
||||
self._setattr: List[Tuple[object, str, object]] = []
|
||||
self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = []
|
||||
self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = []
|
||||
self._cwd: Optional[str] = None
|
||||
self._savesyspath: Optional[List[str]] = None
|
||||
|
||||
|
@ -290,12 +291,13 @@ class MonkeyPatch:
|
|||
self._setattr.append((target, name, oldval))
|
||||
delattr(target, name)
|
||||
|
||||
def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None:
|
||||
def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None:
|
||||
"""Set dictionary entry ``name`` to value."""
|
||||
self._setitem.append((dic, name, dic.get(name, notset)))
|
||||
dic[name] = value
|
||||
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
|
||||
dic[name] = value # type: ignore[index]
|
||||
|
||||
def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None:
|
||||
def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None:
|
||||
"""Delete ``name`` from dict.
|
||||
|
||||
Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to
|
||||
|
@ -306,7 +308,8 @@ class MonkeyPatch:
|
|||
raise KeyError(name)
|
||||
else:
|
||||
self._setitem.append((dic, name, dic.get(name, notset)))
|
||||
del dic[name]
|
||||
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
|
||||
del dic[name] # type: ignore[attr-defined]
|
||||
|
||||
def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
|
||||
"""Set environment variable ``name`` to ``value``.
|
||||
|
@ -401,11 +404,13 @@ class MonkeyPatch:
|
|||
for dictionary, key, value in reversed(self._setitem):
|
||||
if value is notset:
|
||||
try:
|
||||
del dictionary[key]
|
||||
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
|
||||
del dictionary[key] # type: ignore[attr-defined]
|
||||
except KeyError:
|
||||
pass # Was already deleted, so we have the desired state.
|
||||
else:
|
||||
dictionary[key] = value
|
||||
# Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict
|
||||
dictionary[key] = value # type: ignore[index]
|
||||
self._setitem[:] = []
|
||||
if self._savesyspath is not None:
|
||||
sys.path[:] = self._savesyspath
|
||||
|
|
|
@ -6,6 +6,7 @@ import itertools
|
|||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import types
|
||||
import uuid
|
||||
import warnings
|
||||
from enum import Enum
|
||||
|
@ -28,6 +29,8 @@ from typing import Iterable
|
|||
from typing import Iterator
|
||||
from typing import Optional
|
||||
from typing import Set
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
|
@ -63,21 +66,33 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath:
|
|||
return path.joinpath(".lock")
|
||||
|
||||
|
||||
def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
||||
def on_rm_rf_error(
|
||||
func,
|
||||
path: str,
|
||||
excinfo: Union[
|
||||
BaseException,
|
||||
Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]],
|
||||
],
|
||||
*,
|
||||
start_path: Path,
|
||||
) -> bool:
|
||||
"""Handle known read-only errors during rmtree.
|
||||
|
||||
The returned value is used only by our own tests.
|
||||
"""
|
||||
exctype, excvalue = exc[:2]
|
||||
if isinstance(excinfo, BaseException):
|
||||
exc = excinfo
|
||||
else:
|
||||
exc = excinfo[1]
|
||||
|
||||
# Another process removed the file in the middle of the "rm_rf" (xdist for example).
|
||||
# More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018
|
||||
if isinstance(excvalue, FileNotFoundError):
|
||||
if isinstance(exc, FileNotFoundError):
|
||||
return False
|
||||
|
||||
if not isinstance(excvalue, PermissionError):
|
||||
if not isinstance(exc, PermissionError):
|
||||
warnings.warn(
|
||||
PytestWarning(f"(rm_rf) error removing {path}\n{exctype}: {excvalue}")
|
||||
PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}")
|
||||
)
|
||||
return False
|
||||
|
||||
|
@ -86,7 +101,7 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool:
|
|||
warnings.warn(
|
||||
PytestWarning(
|
||||
"(rm_rf) unknown function {} when removing {}:\n{}: {}".format(
|
||||
func, path, exctype, excvalue
|
||||
func, path, type(exc), exc
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -149,7 +164,10 @@ def rm_rf(path: Path) -> None:
|
|||
are read-only."""
|
||||
path = ensure_extended_length_path(path)
|
||||
onerror = partial(on_rm_rf_error, start_path=path)
|
||||
shutil.rmtree(str(path), onerror=onerror)
|
||||
if sys.version_info >= (3, 12):
|
||||
shutil.rmtree(str(path), onexc=onerror)
|
||||
else:
|
||||
shutil.rmtree(str(path), onerror=onerror)
|
||||
|
||||
|
||||
def find_prefixed(root: Path, prefix: str) -> Iterator[Path]:
|
||||
|
@ -335,7 +353,7 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]:
|
|||
yield path
|
||||
|
||||
|
||||
def cleanup_dead_symlink(root: Path):
|
||||
def cleanup_dead_symlinks(root: Path):
|
||||
for left_dir in root.iterdir():
|
||||
if left_dir.is_symlink():
|
||||
if not left_dir.resolve().exists():
|
||||
|
@ -353,7 +371,7 @@ def cleanup_numbered_dir(
|
|||
for path in root.glob("garbage-*"):
|
||||
try_cleanup(path, consider_lock_dead_if_created_before)
|
||||
|
||||
cleanup_dead_symlink(root)
|
||||
cleanup_dead_symlinks(root)
|
||||
|
||||
|
||||
def make_numbered_dir_with_cleanup(
|
||||
|
|
|
@ -347,10 +347,6 @@ class TestReport(BaseReport):
|
|||
elif isinstance(excinfo.value, skip.Exception):
|
||||
outcome = "skipped"
|
||||
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:
|
||||
path, line = item.reportinfo()[:2]
|
||||
assert line is not None
|
||||
|
|
|
@ -28,7 +28,7 @@ from .pathlib import LOCK_TIMEOUT
|
|||
from .pathlib import make_numbered_dir
|
||||
from .pathlib import make_numbered_dir_with_cleanup
|
||||
from .pathlib import rm_rf
|
||||
from .pathlib import cleanup_dead_symlink
|
||||
from .pathlib import cleanup_dead_symlinks
|
||||
from _pytest.compat import final, get_user_id
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import ExitCode
|
||||
|
@ -100,7 +100,7 @@ class TempPathFactory:
|
|||
policy = config.getini("tmp_path_retention_policy")
|
||||
if policy not in ("all", "failed", "none"):
|
||||
raise ValueError(
|
||||
f"tmp_path_retention_policy must be either all, failed, none. Current intput: {policy}."
|
||||
f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}."
|
||||
)
|
||||
|
||||
return cls(
|
||||
|
@ -289,31 +289,30 @@ def tmp_path(
|
|||
|
||||
del request.node.stash[tmppath_result_key]
|
||||
|
||||
# remove dead symlink
|
||||
basetemp = tmp_path_factory._basetemp
|
||||
if basetemp is None:
|
||||
return
|
||||
cleanup_dead_symlink(basetemp)
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]):
|
||||
"""After each session, remove base directory if all the tests passed,
|
||||
the policy is "failed", and the basetemp is not specified by a user.
|
||||
"""
|
||||
tmp_path_factory: TempPathFactory = session.config._tmp_path_factory
|
||||
if tmp_path_factory._basetemp is None:
|
||||
basetemp = tmp_path_factory._basetemp
|
||||
if basetemp is None:
|
||||
return
|
||||
|
||||
policy = tmp_path_factory._retention_policy
|
||||
if (
|
||||
exitstatus == 0
|
||||
and policy == "failed"
|
||||
and tmp_path_factory._given_basetemp is None
|
||||
):
|
||||
passed_dir = tmp_path_factory._basetemp
|
||||
if passed_dir.exists():
|
||||
if basetemp.is_dir():
|
||||
# We do a "best effort" to remove files, but it might not be possible due to some leaked resource,
|
||||
# permissions, etc, in which case we ignore it.
|
||||
rmtree(passed_dir, ignore_errors=True)
|
||||
rmtree(basetemp, ignore_errors=True)
|
||||
|
||||
# Remove dead symlinks.
|
||||
if basetemp.is_dir():
|
||||
cleanup_dead_symlinks(basetemp)
|
||||
|
||||
|
||||
@hookimpl(tryfirst=True, hookwrapper=True)
|
||||
|
|
|
@ -298,6 +298,9 @@ class TestCaseFunction(Function):
|
|||
def stopTest(self, testcase: "unittest.TestCase") -> None:
|
||||
pass
|
||||
|
||||
def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None:
|
||||
pass
|
||||
|
||||
def runtest(self) -> None:
|
||||
from _pytest.debugging import maybe_wrap_pytest_function_for_tracing
|
||||
|
||||
|
|
|
@ -149,7 +149,7 @@ def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None:
|
|||
"""
|
||||
Issue the warning :param:`message` for the definition of the given :param:`method`
|
||||
|
||||
this helps to log warnigns for functions defined prior to finding an issue with them
|
||||
this helps to log warnings for functions defined prior to finding an issue with them
|
||||
(like hook wrappers being marked in a legacy mechanism)
|
||||
"""
|
||||
lineno = method.__code__.co_firstlineno
|
||||
|
|
|
@ -695,11 +695,15 @@ class TestInvocationVariants:
|
|||
monkeypatch.chdir("world")
|
||||
|
||||
# pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages.
|
||||
# pgk_resources has been deprecated entirely.
|
||||
# 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"
|
||||
ignore_w = (
|
||||
r"-Wignore:Deprecated call to `pkg_resources.declare_namespace",
|
||||
r"-Wignore:pkg_resources is deprecated",
|
||||
)
|
||||
result = pytester.runpytest(
|
||||
"--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w
|
||||
"--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", *ignore_w
|
||||
)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
|
|
|
@ -294,7 +294,6 @@ class TestTraceback_f_g_h:
|
|||
excinfo = pytest.raises(ValueError, f)
|
||||
tb = excinfo.traceback
|
||||
entry = tb.getcrashentry()
|
||||
assert entry is not None
|
||||
co = _pytest._code.Code.from_function(h)
|
||||
assert entry.frame.code.path == co.path
|
||||
assert entry.lineno == co.firstlineno + 1
|
||||
|
@ -312,7 +311,10 @@ class TestTraceback_f_g_h:
|
|||
excinfo = pytest.raises(ValueError, f)
|
||||
tb = excinfo.traceback
|
||||
entry = tb.getcrashentry()
|
||||
assert entry is None
|
||||
co = _pytest._code.Code.from_function(g)
|
||||
assert entry.frame.code.path == co.path
|
||||
assert entry.lineno == co.firstlineno + 2
|
||||
assert entry.frame.code.name == "g"
|
||||
|
||||
|
||||
def test_excinfo_exconly():
|
||||
|
@ -1573,3 +1575,20 @@ def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None:
|
|||
# with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it
|
||||
pytest.importorskip("exceptiongroup")
|
||||
_exceptiongroup_common(pytester, outer_chain, inner_chain, native=False)
|
||||
|
||||
|
||||
def test_all_entries_hidden_doesnt_crash(pytester: Pytester) -> None:
|
||||
"""Regression test for #10903.
|
||||
|
||||
We're not really sure what should be *displayed* here, so this test
|
||||
just verified that at least it doesn't crash.
|
||||
"""
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def test():
|
||||
__tracebackhide__ = True
|
||||
1 / 0
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 1
|
||||
|
|
|
@ -897,25 +897,29 @@ class TestConftestCustomization:
|
|||
def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None:
|
||||
"""Ensure we can collect files with weird file extensions as Python
|
||||
modules (#2369)"""
|
||||
# We'll implement a little finder and loader to import files containing
|
||||
# Implement a little meta path finder to import files containing
|
||||
# Python source code whose file extension is ".narf".
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import sys, os, imp
|
||||
import sys
|
||||
import os.path
|
||||
from importlib.util import spec_from_loader
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from _pytest.python import Module
|
||||
|
||||
class Loader(object):
|
||||
def load_module(self, name):
|
||||
return imp.load_source(name, name + ".narf")
|
||||
class Finder(object):
|
||||
def find_module(self, name, path=None):
|
||||
if os.path.exists(name + ".narf"):
|
||||
return Loader()
|
||||
sys.meta_path.append(Finder())
|
||||
class MetaPathFinder:
|
||||
def find_spec(self, fullname, path, target=None):
|
||||
if os.path.exists(fullname + ".narf"):
|
||||
return spec_from_loader(
|
||||
fullname,
|
||||
SourceFileLoader(fullname, fullname + ".narf"),
|
||||
)
|
||||
sys.meta_path.append(MetaPathFinder())
|
||||
|
||||
def pytest_collect_file(file_path, parent):
|
||||
if file_path.suffix == ".narf":
|
||||
return Module.from_parent(path=file_path, parent=parent)"""
|
||||
return Module.from_parent(path=file_path, parent=parent)
|
||||
"""
|
||||
)
|
||||
pytester.makefile(
|
||||
".narf",
|
||||
|
|
|
@ -1436,6 +1436,96 @@ class TestIssue10743:
|
|||
assert result.ret == 0
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.version_info < (3, 8), reason="walrus operator not available in py<38"
|
||||
)
|
||||
class TestIssue11028:
|
||||
def test_assertion_walrus_operator_in_operand(self, pytester: Pytester) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def test_in_string():
|
||||
assert (obj := "foo") in obj
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
def test_assertion_walrus_operator_in_operand_json_dumps(
|
||||
self, pytester: Pytester
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
import json
|
||||
|
||||
def test_json_encoder():
|
||||
assert (obj := "foo") in json.dumps(obj)
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
def test_assertion_walrus_operator_equals_operand_function(
|
||||
self, pytester: Pytester
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def f(a):
|
||||
return a
|
||||
|
||||
def test_call_other_function_arg():
|
||||
assert (obj := "foo") == f(obj)
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
def test_assertion_walrus_operator_equals_operand_function_keyword_arg(
|
||||
self, pytester: Pytester
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def f(a='test'):
|
||||
return a
|
||||
|
||||
def test_call_other_function_k_arg():
|
||||
assert (obj := "foo") == f(a=obj)
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
def test_assertion_walrus_operator_equals_operand_function_arg_as_function(
|
||||
self, pytester: Pytester
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def f(a='test'):
|
||||
return a
|
||||
|
||||
def test_function_of_function():
|
||||
assert (obj := "foo") == f(f(obj))
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 0
|
||||
|
||||
def test_assertion_walrus_operator_gt_operand_function(
|
||||
self, pytester: Pytester
|
||||
) -> None:
|
||||
pytester.makepyfile(
|
||||
"""
|
||||
def add_one(a):
|
||||
return a + 1
|
||||
|
||||
def test_gt():
|
||||
assert (obj := 4) > add_one(obj)
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
assert result.ret == 1
|
||||
result.stdout.fnmatch_lines(["*assert 4 > 5", "*where 5 = add_one(4)"])
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
|
||||
)
|
||||
|
|
|
@ -420,7 +420,13 @@ class TestLastFailed:
|
|||
result = pytester.runpytest()
|
||||
result.stdout.fnmatch_lines(["*1 failed in*"])
|
||||
|
||||
def test_terminal_report_lastfailed(self, pytester: Pytester) -> None:
|
||||
@pytest.mark.parametrize("parent", ("session", "package"))
|
||||
def test_terminal_report_lastfailed(self, pytester: Pytester, parent: str) -> None:
|
||||
if parent == "package":
|
||||
pytester.makepyfile(
|
||||
__init__="",
|
||||
)
|
||||
|
||||
test_a = pytester.makepyfile(
|
||||
test_a="""
|
||||
def test_a1(): pass
|
||||
|
|
|
@ -1247,6 +1247,48 @@ def test_collect_pyargs_with_testpaths(
|
|||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
|
||||
|
||||
def test_initial_conftests_with_testpaths(pytester: Pytester) -> None:
|
||||
"""The testpaths ini option should load conftests in those paths as 'initial' (#10987)."""
|
||||
p = pytester.mkdir("some_path")
|
||||
p.joinpath("conftest.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""
|
||||
def pytest_sessionstart(session):
|
||||
raise Exception("pytest_sessionstart hook successfully run")
|
||||
"""
|
||||
)
|
||||
)
|
||||
pytester.makeini(
|
||||
"""
|
||||
[pytest]
|
||||
testpaths = some_path
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
"INTERNALERROR* Exception: pytest_sessionstart hook successfully run"
|
||||
)
|
||||
|
||||
|
||||
def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None:
|
||||
"""Long option values do not break initial conftests handling (#10169)."""
|
||||
option_value = "x" * 1024 * 1000
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--xx", default=None)
|
||||
"""
|
||||
)
|
||||
pytester.makepyfile(
|
||||
f"""
|
||||
def test_foo(request):
|
||||
assert request.config.getoption("xx") == {option_value!r}
|
||||
"""
|
||||
)
|
||||
result = pytester.runpytest(f"--xx={option_value}")
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_collect_symlink_file_arg(pytester: Pytester) -> None:
|
||||
"""Collect a direct symlink works even if it does not match python_files (#4325)."""
|
||||
real = pytester.makepyfile(
|
||||
|
|
|
@ -35,7 +35,7 @@ def conftest_setinitial(
|
|||
self.importmode = "prepend"
|
||||
|
||||
namespace = cast(argparse.Namespace, Namespace())
|
||||
conftest._set_initial_conftests(namespace, rootpath=Path(args[0]))
|
||||
conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[])
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_sys_snapshot")
|
||||
|
|
|
@ -425,9 +425,7 @@ def test_context_classmethod() -> None:
|
|||
assert A.x == 1
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings(
|
||||
"ignore:Deprecated call to `pkg_resources.declare_namespace"
|
||||
)
|
||||
@pytest.mark.filterwarnings(r"ignore:.*\bpkg_resources\b:DeprecationWarning")
|
||||
def test_syspath_prepend_with_namespace_packages(
|
||||
pytester: Pytester, monkeypatch: MonkeyPatch
|
||||
) -> None:
|
||||
|
|
|
@ -82,7 +82,7 @@ def test_no_ini(pytester: Pytester, file_structure) -> None:
|
|||
|
||||
def test_clean_up(pytester: Pytester) -> None:
|
||||
"""Test that the plugin cleans up after itself."""
|
||||
# This is tough to test behaviorly because the cleanup really runs last.
|
||||
# This is tough to test behaviorally because the cleanup really runs last.
|
||||
# So the test make several implementation assumptions:
|
||||
# - Cleanup is done in pytest_unconfigure().
|
||||
# - Not a hookwrapper.
|
||||
|
|
|
@ -512,20 +512,20 @@ class TestRmRf:
|
|||
|
||||
# unknown exception
|
||||
with pytest.warns(pytest.PytestWarning):
|
||||
exc_info1 = (None, RuntimeError(), None)
|
||||
exc_info1 = (RuntimeError, RuntimeError(), None)
|
||||
on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path)
|
||||
assert fn.is_file()
|
||||
|
||||
# we ignore FileNotFoundError
|
||||
exc_info2 = (None, FileNotFoundError(), None)
|
||||
exc_info2 = (FileNotFoundError, FileNotFoundError(), None)
|
||||
assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path)
|
||||
|
||||
# unknown function
|
||||
with pytest.warns(
|
||||
pytest.PytestWarning,
|
||||
match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ",
|
||||
match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\n<class 'PermissionError'>: ",
|
||||
):
|
||||
exc_info3 = (None, PermissionError(), None)
|
||||
exc_info3 = (PermissionError, PermissionError(), None)
|
||||
on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path)
|
||||
assert fn.is_file()
|
||||
|
||||
|
@ -533,12 +533,12 @@ class TestRmRf:
|
|||
with warnings.catch_warnings():
|
||||
warnings.simplefilter("ignore")
|
||||
with pytest.warns(None) as warninfo: # type: ignore[call-overload]
|
||||
exc_info4 = (None, PermissionError(), None)
|
||||
exc_info4 = PermissionError()
|
||||
on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path)
|
||||
assert fn.is_file()
|
||||
assert not [x.message for x in warninfo]
|
||||
|
||||
exc_info5 = (None, PermissionError(), None)
|
||||
exc_info5 = PermissionError()
|
||||
on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path)
|
||||
assert not fn.is_file()
|
||||
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
def test_tbh_chained(testdir):
|
||||
"""Ensure chained exceptions whose frames contain "__tracebackhide__" are not shown (#1904)."""
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def f1():
|
||||
__tracebackhide__ = True
|
||||
try:
|
||||
return f1.meh
|
||||
except AttributeError:
|
||||
pytest.fail("fail")
|
||||
|
||||
@pytest.fixture
|
||||
def fix():
|
||||
f1()
|
||||
|
||||
|
||||
def test(fix):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(str(p))
|
||||
assert "'function' object has no attribute 'meh'" not in result.stdout.str()
|
||||
assert result.ret == 1
|
|
@ -9,6 +9,7 @@ from typing import Optional
|
|||
from typing_extensions import assert_type
|
||||
|
||||
import pytest
|
||||
from pytest import MonkeyPatch
|
||||
|
||||
|
||||
# Issue #7488.
|
||||
|
@ -29,6 +30,19 @@ def check_parametrize_ids_callable(func) -> None:
|
|||
pass
|
||||
|
||||
|
||||
# Issue #10999.
|
||||
def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None:
|
||||
from typing import TypedDict
|
||||
|
||||
class Foo(TypedDict):
|
||||
x: int
|
||||
y: float
|
||||
|
||||
a: Foo = {"x": 1, "y": 3.14}
|
||||
monkeypatch.setitem(a, "x", 2)
|
||||
monkeypatch.delitem(a, "y")
|
||||
|
||||
|
||||
def check_raises_is_a_context_manager(val: bool) -> None:
|
||||
with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo:
|
||||
pass
|
||||
|
|
Loading…
Reference in New Issue