Compare commits

...

41 Commits

Author SHA1 Message Date
Ran Benita
ec8e23951d Merge pull request #10883 from bluetech/cherry-pick-release
Cherry pick 7.3.0 release notes
2023-04-09 00:53:52 +03:00
Ran Benita
bf47357511 Merge pull request #10881 from pytest-dev/release-7.3.0
Prepare release 7.3.0

(cherry picked from commit cec5bfe058)
2023-04-09 00:50:37 +03:00
Kodi Arfer
3683722bcb FormattedExcinfo.get_source: avoid crash when line number is out-of-bounds/negative
pytest could crash given pathological AST position attributes, which shouldn't happen when testing real Python code, but could happen when testing AST produced by e.g. Hylang.

Another example of the failure is in the nightly CI for the JAX project: https://github.com/google/jax/actions/runs/4607513902/jobs/8142126075

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
Co-authored-by: Jake VanderPlas <jakevdp@google.com>
2023-04-05 22:48:24 -03:00
github-actions[bot]
31d0b51039 [automated] Update plugin list (#10857)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2023-04-04 13:17:43 -03:00
Pierre Sassoulas
2d2f69dab5 Merge pull request #10862 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2023-04-04 09:04:47 +02:00
pre-commit-ci[bot]
2a39ed3461 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 23.1.0 → 23.3.0](https://github.com/psf/black/compare/23.1.0...23.3.0)
2023-04-04 06:34:42 +00:00
github-actions[bot]
a3b39069bc [automated] Update plugin list (#10838)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2023-03-26 08:32:13 -03:00
github-actions[bot]
172c832cbd [automated] Update plugin list (#10823)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2023-03-24 11:41:07 -03:00
dependabot[bot]
839b90db45 build(deps): Bump peter-evans/create-pull-request from 4.2.3 to 4.2.4 (#10828)
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 4.2.3 to 4.2.4.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](2b011faafd...38e0b6e68b)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 11:40:34 -03:00
dependabot[bot]
549cc512f7 build(deps): Bump pytest-asyncio in /testing/plugins_integration (#10827)
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.20.2 to 0.21.0.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.20.2...v0.21.0)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 11:40:06 -03:00
Ronny Pfannschmidt
2369bed1db Merge pull request #10727 from RonnyPfannschmidt/ronny/split-report-header
split up report header lines for config, rootdir and testpaths
2023-03-18 22:06:46 +01:00
Ronny Pfannschmidt
54864f0c9b bugfix: fix imports for simple example 2023-03-17 21:58:26 +01:00
Ronny Pfannschmidt
ba969d2ae7 run regendoc 2023-03-17 21:58:26 +01:00
Ronny Pfannschmidt
407b330fe1 split up report header lines
i found it painful to read crammed in a single line
thus rootdir, config file and testpaths now have own lines
2023-03-17 21:58:26 +01:00
Felix Hofstätter
431ec6d34e Correctly handle tracebackhide for chained exceptions (#10772) 2023-03-15 08:10:25 -03:00
pre-commit-ci[bot]
eada68b2b3 [pre-commit.ci] pre-commit autoupdate (#10814)
updates:
- [github.com/PyCQA/autoflake: v2.0.1 → v2.0.2](https://github.com/PyCQA/autoflake/compare/v2.0.1...v2.0.2)
- [github.com/pre-commit/mirrors-mypy: v1.0.1 → v1.1.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.0.1...v1.1.1)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2023-03-14 08:17:13 -03:00
dependabot[bot]
ab069247cd build(deps): Bump pytest-rerunfailures in /testing/plugins_integration (#10812)
Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 11.1.1 to 11.1.2.
- [Release notes](https://github.com/pytest-dev/pytest-rerunfailures/releases)
- [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/11.1.1...11.1.2)

---
updated-dependencies:
- dependency-name: pytest-rerunfailures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-13 08:00:07 -03:00
github-actions[bot]
7af1e4e4ed [automated] Update plugin list (#10810)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2023-03-12 13:04:50 -03:00
Stefanie Molin
0ae04ae629 Include pyproject.toml in help section that lists out config files with ini-options (#10807) 2023-03-11 10:59:02 -03:00
Florian Bruhin
723035be7f doc: Remove done training (#10805) 2023-03-10 20:47:03 +00:00
Alessio Izzo
6e478b0947 Fix walrus operator support in assertion rewriting (#10758)
Closes #10743
2023-03-10 07:32:36 -03:00
Paul Kehrer
a869141b3d New option to allow a progress report even when capture=no (#10755) 2023-03-07 17:49:37 -03:00
Bruno Oliveira
5e98aefc92 Merge pull request #10794 from pytest-dev/update-plugin-list/patch-d5dda84ef
[automated] Update plugin list
2023-03-06 08:16:06 -03:00
Bruno Oliveira
5f47e423b2 Merge pull request #10795 from bluthej/fix-10782
Fix example in the documentation (#10782)
2023-03-05 13:49:35 -03:00
bluthej
5a61ec3d4a Fix example in the documentation (#10782) 2023-03-05 17:01:21 +01:00
Zac Hatfield-Dodds
b3b44ea814 Merge pull request #10766 from rdb/fix-10765 2023-03-04 23:34:35 -08:00
pytest bot
1d48b3021d [automated] Update plugin list 2023-03-05 00:25:29 +00:00
Bruno Oliveira
d5dda84ef3 Merge pull request #10793 from nicoddemus/cherry-pick-release
Merge pull request #10792 from pytest-dev/release-7.2.2
2023-03-03 16:22:47 -03:00
Bruno Oliveira
517e02e59e Merge pull request #10792 from pytest-dev/release-7.2.2
Prepare release 7.2.2

(cherry picked from commit 3ce6030f0c)
2023-03-03 16:14:11 -03:00
Bruno Oliveira
4e259590c9 Normalize how changelog entries are written (#10779)
Went over all changelog entries making sure they follow our guidelines as written at:

88c9e92258/.github/PULL_REQUEST_TEMPLATE.md (L18-L21)
2023-03-03 12:53:38 -03:00
Bruno Oliveira
97a2761d72 Fix test_cmdline_python_namespace_package (#10788)
pgk_resources.declare_namespace has been deprecated, so added an ignore warnings option
to the test.
2023-03-03 12:25:33 -03:00
Billy
88c9e92258 Minor updates to fixtures docs (#10724)
Updated the c fixture to be a little more consistent with other fixtures in the corresponding image. for example both e and g both have edges connected with the fixtures that they explicitly depend on.
2023-02-28 12:42:33 -03:00
Ronny Pfannschmidt
72ad32411f Docs: be more explicit about module level skip preventing collection (#10753) 2023-02-28 12:41:31 -03:00
Bruno Oliveira
cb9e8be301 Move logic to get_user_id in compat 2023-02-28 11:19:34 -03:00
Bruno Oliveira
d72da480c4 Apply suggestions from code review 2023-02-28 11:02:17 -03:00
Bruno Oliveira
07e7deb4a7 Update changelog/10765.bugfix.rst 2023-02-28 10:59:23 -03:00
Bruno Oliveira
572b5657d7 Merge pull request #10767 from alexhad6/fix-typo-in-python_api.py
Fix typo in python_api.py
2023-02-27 08:59:13 -03:00
dependabot[bot]
44afed9b13 build(deps): Bump pytest-rerunfailures in /testing/plugins_integration (#10754)
Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 11.1 to 11.1.1.
- [Release notes](https://github.com/pytest-dev/pytest-rerunfailures/releases)
- [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/11.1...11.1.1)

---
updated-dependencies:
- dependency-name: pytest-rerunfailures
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-27 08:56:25 -03:00
github-actions[bot]
13ea4780b8 [automated] Update plugin list (#10752)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2023-02-27 08:55:33 -03:00
Alex Hadley
135600fca3 Fix typo in python_api.py 2023-02-24 15:04:42 -08:00
rdb
c237297b3d Fix OSError in tmpdir on emscripten due to missing getuid()
Fixes #10765
2023-02-24 23:23:44 +01:00
52 changed files with 2270 additions and 1505 deletions

View File

@@ -38,7 +38,7 @@ jobs:
run: python scripts/update-plugin-list.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54
with:
commit-message: '[automated] Update plugin list'
author: 'pytest bot <pytestbot@users.noreply.github.com>'

View File

@@ -2,7 +2,7 @@ default_language_version:
python: "3.10"
repos:
- repo: https://github.com/psf/black
rev: 23.1.0
rev: 23.3.0
hooks:
- id: black
args: [--safe, --quiet]
@@ -23,7 +23,7 @@ repos:
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://github.com/PyCQA/autoflake
rev: v2.0.1
rev: v2.0.2
hooks:
- id: autoflake
name: autoflake
@@ -58,7 +58,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.0.1
rev: v1.1.1
hooks:
- id: mypy
files: ^(src/|testing/)

View File

@@ -12,6 +12,7 @@ Adam Uhlir
Ahn Ki-Wook
Akiomi Kamakura
Alan Velasco
Alessio Izzo
Alexander Johnson
Alexander King
Alexei Kozlenok
@@ -127,6 +128,7 @@ Erik M. Bray
Evan Kepner
Fabien Zarifian
Fabio Zadrozny
Felix Hofstätter
Felix Nieuwenhuizen
Feng Ma
Florian Bruhin
@@ -161,6 +163,7 @@ Ionuț Turturică
Itxaso Aizpurua
Iwan Briquemont
Jaap Broekhuizen
Jake VanderPlas
Jakob van Santen
Jakub Mitoraj
James Bourbeau
@@ -331,6 +334,7 @@ Srinivas Reddy Thatiparthy
Stefan Farmbauer
Stefan Scherfke
Stefan Zimmermann
Stefanie Molin
Stefano Taschini
Steffen Allner
Stephan Obermann

View File

@@ -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.

View File

@@ -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.

View File

@@ -1 +0,0 @@
Fix :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0` in class ApproxMapping.

View File

@@ -1 +0,0 @@
Fixed crash if `--cache-show` and `--help` are passed at the same time.

View File

@@ -1 +0,0 @@
Fix bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage.

View File

@@ -1 +0,0 @@
Fix crash if ``--fixtures`` and ``--help`` are passed at the same time.

View File

@@ -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.

View File

@@ -1,2 +0,0 @@
Fix :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow
:code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'.

View File

@@ -1 +0,0 @@
pytest no longer depends on the `attrs` package (don't worry, nice diffs for attrs classes are still supported).

View File

@@ -1 +0,0 @@
Added `CI` and `BUILD_NUMBER` environment variables to the documentation.

View File

@@ -1 +0,0 @@
Added ``start`` and ``stop`` timestamps to ``TestReport`` objects.

View File

@@ -1 +0,0 @@
Fixed entry-points declaration in the documentation example using Hatch.

View File

@@ -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.

View File

@@ -1 +0,0 @@
``--log-disable`` CLI option added to disable individual loggers.

View File

@@ -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.

View File

@@ -6,6 +6,8 @@ Release announcements
:maxdepth: 2
release-7.3.0
release-7.2.2
release-7.2.1
release-7.2.0
release-7.1.3

View File

@@ -0,0 +1,25 @@
pytest-7.2.2
=======================================
pytest 7.2.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Bruno Oliveira
* Garvit Shubham
* Mahesh Vashishtha
* Ramsey
* Ronny Pfannschmidt
* Teejay
* q0w
* vin01
Happy testing,
The pytest Development Team

View File

@@ -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

View File

@@ -33,25 +33,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
Values can be any object handled by the json stdlib module.
capsys -- .../_pytest/capture.py:905
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_output(capsys):
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
capsysbinary -- .../_pytest/capture.py:933
capsysbinary -- .../_pytest/capture.py:1001
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsysbinary.readouterr()``
@@ -69,7 +51,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capsysbinary.readouterr()
assert captured.out == b"hello\n"
capfd -- .../_pytest/capture.py:961
capfd -- .../_pytest/capture.py:1029
Enable text capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
@@ -87,7 +69,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capfd.readouterr()
assert captured.out == "hello\n"
capfdbinary -- .../_pytest/capture.py:989
capfdbinary -- .../_pytest/capture.py:1057
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
The captured output is made available via ``capfd.readouterr()`` method
@@ -105,7 +87,25 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
captured = capfdbinary.readouterr()
assert captured.out == b"hello\n"
doctest_namespace [session scope] -- .../_pytest/doctest.py:738
capsys -- .../_pytest/capture.py:973
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
The captured output is made available via ``capsys.readouterr()`` method
calls, which return a ``(out, err)`` namedtuple.
``out`` and ``err`` will be ``text`` objects.
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
Example:
.. code-block:: python
def test_output(capsys):
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
doctest_namespace [session scope] -- .../_pytest/doctest.py:737
Fixture that returns a :py:class:`dict` that will be injected into the
namespace of doctests.
@@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
For more details: :ref:`doctest_namespace`.
pytestconfig [session scope] -- .../_pytest/fixtures.py:1356
pytestconfig [session scope] -- .../_pytest/fixtures.py:1360
Session-scoped fixture that returns the session's :class:`pytest.Config`
object.
@@ -196,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
caplog -- .../_pytest/logging.py:491
caplog -- .../_pytest/logging.py:498
Access and control log capturing.
Captured logs are available through the following properties/methods::
@@ -237,17 +237,19 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
on warning categories.
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:188
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:245
Return a :class:`pytest.TempPathFactory` instance for the test session.
tmp_path -- .../_pytest/tmpdir.py:203
tmp_path -- .../_pytest/tmpdir.py:260
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
By default, a new base temporary directory is created each test session,
and old bases are removed after 3 sessions, to aid in debugging. If
``--basetemp`` is used then it is cleared each session. See :ref:`base
and old bases are removed after 3 sessions, to aid in debugging.
This behavior can be configured with :confval:`tmp_path_retention_count` and
:confval:`tmp_path_retention_policy`.
If ``--basetemp`` is used then it is cleared each session. See :ref:`base
temporary directory`.
The returned object is a :class:`pathlib.Path` object.

View File

@@ -28,6 +28,112 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
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.
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 depends on the `attrs` package (don't worry, nice diffs for attrs classes are still supported).
pytest 7.2.2 (2023-03-03)
=========================
Bug Fixes
---------
- `#10533 <https://github.com/pytest-dev/pytest/issues/10533>`_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`.
- `#10592 <https://github.com/pytest-dev/pytest/issues/10592>`_: Fixed crash if `--cache-show` and `--help` are passed at the same time.
- `#10597 <https://github.com/pytest-dev/pytest/issues/10597>`_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage.
- `#10626 <https://github.com/pytest-dev/pytest/issues/10626>`_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time.
- `#10660 <https://github.com/pytest-dev/pytest/issues/10660>`_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow
:code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'.
Improved Documentation
----------------------
- `#10690 <https://github.com/pytest-dev/pytest/issues/10690>`_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation.
- `#10721 <https://github.com/pytest-dev/pytest/issues/10721>`_: Fixed entry-points declaration in the documentation example using Hatch.
- `#10753 <https://github.com/pytest-dev/pytest/issues/10753>`_: Changed wording of the module level skip to be very explicit
about not collecting tests and not executing the rest of the module.
pytest 7.2.1 (2023-01-13)
=========================

View File

@@ -17,7 +17,7 @@ def b(a, order):
@pytest.fixture
def c(a, b, order):
def c(b, order):
order.append("c")

View File

@@ -148,7 +148,8 @@ The test collection would look like this:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project, configfile: pytest.ini
rootdir: /home/sweet/project
configfile: pytest.ini
collected 2 items
<Module check_myapp.py>
@@ -209,7 +210,8 @@ You can always peek at the collection tree without running tests like this:
. $ pytest --collect-only pythoncollection.py
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project, configfile: pytest.ini
rootdir: /home/sweet/project
configfile: pytest.ini
collected 3 items
<Module CWD/pythoncollection.py>
@@ -290,7 +292,8 @@ file will be left out:
$ pytest --collect-only
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project, configfile: pytest.ini
rootdir: /home/sweet/project
configfile: pytest.ini
collected 0 items
======================= no tests collected in 0.12s ========================

View File

@@ -144,7 +144,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E 1
E 1...
E
E ...Full output truncated (7 lines hidden), use '-vv' to show
E ...Full output truncated (6 lines hidden), use '-vv' to show
failure_demo.py:60: AssertionError
_________________ TestSpecialisedExplanations.test_eq_list _________________
@@ -184,9 +184,8 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E Left contains 1 more item:
E {'c': 0}
E Right contains 1 more item:
E {'d': 0}...
E
E ...Full output truncated (2 lines hidden), use '-vv' to show
E {'d': 0}
E Use -v to get more diff
failure_demo.py:71: AssertionError
_________________ TestSpecialisedExplanations.test_eq_set __________________
@@ -195,16 +194,15 @@ Here is a nice run of several failures and how ``pytest`` presents things:
def test_eq_set(self):
> assert {0, 10, 11, 12} == {0, 20, 21}
E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21}
E assert {0, 10, 11, 12} == {0, 20, 21}
E Extra items in the left set:
E 10
E 11
E 12
E Extra items in the right set:
E 20
E 21...
E
E ...Full output truncated (2 lines hidden), use '-vv' to show
E 21
E Use -v to get more diff
failure_demo.py:74: AssertionError
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
@@ -241,9 +239,8 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E which
E includes foo
E ? +++
E and a...
E
E ...Full output truncated (2 lines hidden), use '-vv' to show
E and a
E tail
failure_demo.py:84: AssertionError
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
@@ -307,9 +304,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E ['b']
E
E Drill down into differing attribute b:
E b: 'b' != 'c'...
E
E ...Full output truncated (3 lines hidden), use '-vv' to show
E b: 'b' != 'c'
E - c
E + b
failure_demo.py:108: AssertionError
________________ TestSpecialisedExplanations.test_eq_attrs _________________
@@ -334,9 +331,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
E ['b']
E
E Drill down into differing attribute b:
E b: 'b' != 'c'...
E
E ...Full output truncated (3 lines hidden), use '-vv' to show
E b: 'b' != 'c'
E - c
E + b
failure_demo.py:120: AssertionError
______________________________ test_attribute ______________________________
@@ -673,7 +670,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list - asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list_long - ...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dict - Asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - Assert...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - assert...
FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list
FAILED failure_demo.py::TestSpecialisedExplanations::test_in_list - asser...
FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline

View File

@@ -892,8 +892,9 @@ here is a little example implemented via a local plugin:
.. code-block:: python
# content of conftest.py
from typing import Dict
import pytest
from pytest import StashKey, CollectReport
phase_report_key = StashKey[Dict[str, CollectReport]]()
@@ -956,8 +957,8 @@ and run it:
rootdir: /home/sweet/project
collected 3 items
test_module.py Esetting up a test failed! test_module.py::test_setup_fails
Fexecuting test failed test_module.py::test_call_fails
test_module.py Esetting up a test failed or skipped test_module.py::test_setup_fails
Fexecuting test failed or skipped test_module.py::test_call_fails
F
================================== ERRORS ==================================

View File

@@ -24,8 +24,9 @@ The first few lines should look like this:
[project]
name = "PACKAGENAME"
version = "PACKAGEVERSION"
where ``PACKAGENAME`` is the name of your package.
where ``PACKAGENAME`` and ``PACKAGEVERSION`` are the name and version of your package respectively.
You can then install your package in "editable" mode by running from the same directory:

View File

@@ -22,7 +22,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
pytest 7.2.1
pytest 7.3.0
.. _`simpletest`:

View File

@@ -167,9 +167,9 @@ Now we can increase pytest's verbosity:
E Right contains 4 more items:
E {'10': 10, '20': 20, '30': 30, '40': 40}
E Full diff:
E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}...
E
E ...Full output truncated (3 lines hidden), use '-vv' to show
E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
E ? - - - - - - - -
E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}
test_verbosity_example.py:14: AssertionError
___________________________ test_long_text_fail ____________________________

View File

@@ -449,7 +449,8 @@ in our ``pytest.ini`` to tell pytest where to look for example files.
$ pytest
=========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
rootdir: /home/sweet/project, configfile: pytest.ini
rootdir: /home/sweet/project
configfile: pytest.ini
collected 2 items
test_example.py .. [100%]

View File

@@ -1,10 +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
- `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
Also see :doc:`previous talks and blogposts <talks>`.
Also see :doc:`previous talks and blogposts <talks>`.
.. _features:

View File

@@ -335,7 +335,7 @@ For example:
.. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py
If we map out what depends on what, we get something that look like this:
If we map out what depends on what, we get something that looks like this:
.. image:: /example/fixtures/test_fixtures_order_dependencies.*
:align: center

File diff suppressed because it is too large Load Diff

View File

@@ -1220,6 +1220,7 @@ passed multiple times. The expected format is ``name=value``. For example::
* ``classic``: classic pytest output.
* ``progress``: like classic pytest output, but with a progress indicator.
* ``progress-even-when-capture-no``: allows the use of the progress indicator even when ``capture=no``.
* ``count``: like progress, but shows progress as the number of tests completed instead of a percent.
The default is ``progress``, but you can fallback to ``classic`` if you prefer or
@@ -1994,8 +1995,11 @@ All the command-line flags can be obtained by running ``pytest --help``::
--log-auto-indent=LOG_AUTO_INDENT
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
times.
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
markers (linelist): Markers for test functions
empty_parameter_set_mark (string):
@@ -2023,9 +2027,18 @@ All the command-line flags can be obtained by running ``pytest --help``::
console_output_style (string):
Console output: "classic", or with additional
progress information ("progress" (percentage) |
"count")
"count" | "progress-even-when-capture-no" (forces
progress even when capture=no)
xfail_strict (bool): Default for the strict parameter of xfail markers
when not given explicitly (default: False)
tmp_path_retention_count (string):
How many sessions should we keep the `tmp_path`
directories, according to
`tmp_path_retention_policy`.
tmp_path_retention_policy (string):
Controls which directories created by the `tmp_path`
fixture are kept around, based on test outcome.
(all/failed/none)
enable_assertion_pass_hook (bool):
Enables the pytest_assertion_pass hook. Make sure to
delete any previously generated pyc cache files.

View File

@@ -411,13 +411,13 @@ class Traceback(List[TracebackEntry]):
"""
return Traceback(filter(fn, self), self._excinfo)
def getcrashentry(self) -> TracebackEntry:
def getcrashentry(self) -> Optional[TracebackEntry]:
"""Return last non-hidden traceback entry that lead to the exception of a traceback."""
for i in range(-1, -len(self) - 1, -1):
entry = self[i]
if not entry.ishidden():
return entry
return self[-1]
return None
def recursionindex(self) -> Optional[int]:
"""Return the index of the frame/TracebackEntry where recursion originates if
@@ -602,11 +602,13 @@ class ExceptionInfo(Generic[E]):
"""
return isinstance(self.value, exc)
def _getreprcrash(self) -> "ReprFileLocation":
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
exconly = self.exconly(tryshort=True)
entry = self.traceback.getcrashentry()
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
return ReprFileLocation(path, lineno + 1, exconly)
if entry:
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
return ReprFileLocation(path, lineno + 1, exconly)
return None
def getrepr(
self,
@@ -741,11 +743,13 @@ class FormattedExcinfo:
) -> List[str]:
"""Return formatted and marked up source lines."""
lines = []
if source is None or line_index >= len(source.lines):
if source is not None and line_index < 0:
line_index += len(source)
if source is None or line_index >= len(source.lines) or line_index < 0:
# `line_index` could still be outside `range(len(source.lines))` if
# we're processing AST with pathological position attributes.
source = Source("???")
line_index = 0
if line_index < 0:
line_index += len(source)
space_prefix = " "
if short:
lines.append(space_prefix + source.lines[line_index].strip())
@@ -942,9 +946,14 @@ class FormattedExcinfo:
)
else:
reprtraceback = self.repr_traceback(excinfo_)
reprcrash: Optional[ReprFileLocation] = (
excinfo_._getreprcrash() if self.style != "value" else None
)
# will be None if all traceback entries are hidden
reprcrash: Optional[ReprFileLocation] = excinfo_._getreprcrash()
if reprcrash:
if self.style == "value":
repr_chain += [(reprtraceback, None, descr)]
else:
repr_chain += [(reprtraceback, reprcrash, descr)]
else:
# Fallback to native repr if the exception doesn't have a traceback:
# ExceptionInfo objects require a full traceback to work.
@@ -952,8 +961,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_ = (
@@ -1044,7 +1053,7 @@ class ExceptionChainRepr(ExceptionRepr):
@dataclasses.dataclass(eq=False)
class ReprExceptionInfo(ExceptionRepr):
reprtraceback: "ReprTraceback"
reprcrash: "ReprFileLocation"
reprcrash: Optional["ReprFileLocation"]
def toterminal(self, tw: TerminalWriter) -> None:
self.reprtraceback.toterminal(tw)

View File

@@ -44,10 +44,14 @@ from _pytest.stash import StashKey
if TYPE_CHECKING:
from _pytest.assertion import AssertionState
if sys.version_info >= (3, 8):
namedExpr = ast.NamedExpr
else:
namedExpr = ast.Expr
assertstate_key = StashKey["AssertionState"]()
# pytest caches rewritten pycs in pycache dirs
PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}"
PYC_EXT = ".py" + (__debug__ and "c" or "o")
@@ -635,8 +639,12 @@ class AssertionRewriter(ast.NodeVisitor):
.push_format_context() and .pop_format_context() which allows
to build another %-formatted string while already building one.
This state is reset on every new assert statement visited and used
by the other visitors.
:variables_overwrite: A dict filled with references to variables
that change value within an assert. This happens when a variable is
reassigned with the walrus operator
This state, except the variables_overwrite, is reset on every new assert
statement visited and used by the other visitors.
"""
def __init__(
@@ -652,6 +660,7 @@ class AssertionRewriter(ast.NodeVisitor):
else:
self.enable_assertion_pass_hook = False
self.source = source
self.variables_overwrite: Dict[str, str] = {}
def run(self, mod: ast.Module) -> None:
"""Find all assert statements in *mod* and rewrite them."""
@@ -666,7 +675,7 @@ class AssertionRewriter(ast.NodeVisitor):
if doc is not None and self.is_rewrite_disabled(doc):
return
pos = 0
lineno = 1
item = None
for item in mod.body:
if (
expect_docstring
@@ -937,6 +946,18 @@ class AssertionRewriter(ast.NodeVisitor):
ast.copy_location(node, assert_)
return self.statements
def visit_NamedExpr(self, name: namedExpr) -> Tuple[namedExpr, str]:
# This method handles the 'walrus operator' repr of the target
# name if it's a local variable or _should_repr_global_name()
# thinks it's acceptable.
locs = ast.Call(self.builtin("locals"), [], [])
target_id = name.target.id # type: ignore[attr-defined]
inlocs = ast.Compare(ast.Str(target_id), [ast.In()], [locs])
dorepr = self.helper("_should_repr_global_name", name)
test = ast.BoolOp(ast.Or(), [inlocs, dorepr])
expr = ast.IfExp(test, self.display(name), ast.Str(target_id))
return name, self.explanation_param(expr)
def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]:
# Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable.
@@ -963,6 +984,20 @@ class AssertionRewriter(ast.NodeVisitor):
# cond is set in a prior loop iteration below
self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa
self.expl_stmts = fail_inner
# Check if the left operand is a namedExpr and the value has already been visited
if (
isinstance(v, ast.Compare)
and isinstance(v.left, namedExpr)
and v.left.target.id
in [
ast_expr.id
for ast_expr in boolop.values[:i]
if hasattr(ast_expr, "id")
]
):
pytest_temp = self.variable()
self.variables_overwrite[v.left.target.id] = pytest_temp
v.left.target.id = pytest_temp
self.push_format_context()
res, expl = self.visit(v)
body.append(ast.Assign([ast.Name(res_var, ast.Store())], res))
@@ -1038,6 +1073,9 @@ class AssertionRewriter(ast.NodeVisitor):
def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
self.push_format_context()
# We first check if we have overwritten a variable in the previous assert
if isinstance(comp.left, ast.Name) and comp.left.id in self.variables_overwrite:
comp.left.id = self.variables_overwrite[comp.left.id]
left_res, left_expl = self.visit(comp.left)
if isinstance(comp.left, (ast.Compare, ast.BoolOp)):
left_expl = f"({left_expl})"
@@ -1049,6 +1087,13 @@ class AssertionRewriter(ast.NodeVisitor):
syms = []
results = [left_res]
for i, op, next_operand in it:
if (
isinstance(next_operand, namedExpr)
and isinstance(left_res, ast.Name)
and next_operand.target.id == left_res.id
):
next_operand.target.id = self.variable()
self.variables_overwrite[left_res.id] = next_operand.target.id
next_res, next_expl = self.visit(next_operand)
if isinstance(next_operand, (ast.Compare, ast.BoolOp)):
next_expl = f"({next_expl})"
@@ -1072,6 +1117,7 @@ class AssertionRewriter(ast.NodeVisitor):
res: ast.expr = ast.BoolOp(ast.And(), load_names)
else:
res = load_names[0]
return res, self.explanation_param(self.pop_format_context(expl_call))

View File

@@ -1,4 +1,6 @@
"""Python version compatibility code."""
from __future__ import annotations
import dataclasses
import enum
import functools
@@ -12,11 +14,8 @@ from typing import Any
from typing import Callable
from typing import Generic
from typing import NoReturn
from typing import Optional
from typing import Tuple
from typing import TYPE_CHECKING
from typing import TypeVar
from typing import Union
import py
@@ -46,7 +45,7 @@ LEGACY_PATH = py.path. local
# fmt: on
def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH:
"""Internal wrapper to prepare lazy proxies for legacy_path instances"""
return LEGACY_PATH(path)
@@ -56,7 +55,7 @@ def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH:
# https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions
class NotSetType(enum.Enum):
token = 0
NOTSET: "Final" = NotSetType.token # noqa: E305
NOTSET: Final = NotSetType.token # noqa: E305
# fmt: on
if sys.version_info >= (3, 8):
@@ -94,7 +93,7 @@ def is_async_function(func: object) -> bool:
return iscoroutinefunction(func) or inspect.isasyncgenfunction(func)
def getlocation(function, curdir: Optional[str] = None) -> str:
def getlocation(function, curdir: str | None = None) -> str:
function = get_real_func(function)
fn = Path(inspect.getfile(function))
lineno = function.__code__.co_firstlineno
@@ -132,8 +131,8 @@ def getfuncargnames(
*,
name: str = "",
is_method: bool = False,
cls: Optional[type] = None,
) -> Tuple[str, ...]:
cls: type | None = None,
) -> tuple[str, ...]:
"""Return the names of a function's mandatory arguments.
Should return the names of all function arguments that:
@@ -197,7 +196,7 @@ def getfuncargnames(
return arg_names
def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]:
def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]:
# Note: this code intentionally mirrors the code at the beginning of
# getfuncargnames, to get the arguments which were excluded from its result
# because they had default values.
@@ -228,7 +227,7 @@ def _bytes_to_ascii(val: bytes) -> str:
return val.decode("ascii", "backslashreplace")
def ascii_escaped(val: Union[bytes, str]) -> str:
def ascii_escaped(val: bytes | str) -> str:
r"""If val is pure ASCII, return it as an str, otherwise, escape
bytes objects into a sequence of escaped bytes:
@@ -355,7 +354,6 @@ else:
if sys.version_info >= (3, 8):
from functools import cached_property as cached_property
else:
from typing import Type
class cached_property(Generic[_S, _T]):
__slots__ = ("func", "__doc__")
@@ -366,12 +364,12 @@ else:
@overload
def __get__(
self, instance: None, owner: Optional[Type[_S]] = ...
) -> "cached_property[_S, _T]":
self, instance: None, owner: type[_S] | None = ...
) -> cached_property[_S, _T]:
...
@overload
def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T:
def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T:
...
def __get__(self, instance, owner=None):
@@ -381,6 +379,18 @@ else:
return value
def get_user_id() -> int | None:
"""Return the current user id, or None if we cannot get it reliably on the current platform."""
# win32 does not have a getuid() function.
# On Emscripten, getuid() is a stub that always returns 0.
if sys.platform in ("win32", "emscripten"):
return None
# getuid shouldn't fail, but cpython defines such a case.
# Let's hope for the best.
uid = os.getuid()
return uid if uid != -1 else None
# Perform exhaustiveness checking.
#
# Consider this example:

View File

@@ -164,7 +164,8 @@ def showhelp(config: Config) -> None:
tw.write(config._parser.optparser.format_help())
tw.line()
tw.line(
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
"[pytest] ini-options in the first "
"pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:"
)
tw.line()

View File

@@ -157,8 +157,12 @@ def skip(
The message to show the user as reason for the skip.
:param allow_module_level:
Allows this function to be called at module level, skipping the rest
of the module. Defaults to False.
Allows this function to be called at module level.
Raising the skip exception at module level will stop
the execution of the module and prevent the collection of all tests in the module,
even those defined before the `skip` call.
Defaults to False.
:param msg:
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.

View File

@@ -808,7 +808,7 @@ def raises( # noqa: F811
:param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception:
The expected exception type, or a tuple if one of multiple possible
exception types are excepted.
exception types are expected.
:kwparam str | typing.Pattern[str] | None match:
If specified, a string containing a regular expression,
or a regular expression object, that is tested against the string
@@ -924,10 +924,10 @@ def raises( # noqa: F811
f"any special code to say 'this should never raise an exception'."
)
if isinstance(expected_exception, type):
excepted_exceptions: Tuple[Type[E], ...] = (expected_exception,)
expected_exceptions: Tuple[Type[E], ...] = (expected_exception,)
else:
excepted_exceptions = expected_exception
for exc in excepted_exceptions:
expected_exceptions = expected_exception
for exc in expected_exceptions:
if not isinstance(exc, type) or not issubclass(exc, BaseException):
msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable]
not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__

View File

@@ -347,6 +347,10 @@ 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

View File

@@ -229,7 +229,8 @@ def pytest_addoption(parser: Parser) -> None:
parser.addini(
"console_output_style",
help='Console output: "classic", or with additional progress information '
'("progress" (percentage) | "count")',
'("progress" (percentage) | "count" | "progress-even-when-capture-no" (forces '
"progress even when capture=no)",
default="progress",
)
@@ -346,14 +347,19 @@ class TerminalReporter:
def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]":
"""Return whether we should display progress information based on the current config."""
# do not show progress if we are not capturing output (#3038)
if self.config.getoption("capture", "no") == "no":
# do not show progress if we are not capturing output (#3038) unless explicitly
# overridden by progress-even-when-capture-no
if (
self.config.getoption("capture", "no") == "no"
and self.config.getini("console_output_style")
!= "progress-even-when-capture-no"
):
return False
# do not show progress if we are showing fixture setup/teardown
if self.config.getoption("setupshow", False):
return False
cfg: str = self.config.getini("console_output_style")
if cfg == "progress":
if cfg == "progress" or cfg == "progress-even-when-capture-no":
return "progress"
elif cfg == "count":
return "count"
@@ -733,16 +739,14 @@ class TerminalReporter:
self.write_line(line)
def pytest_report_header(self, config: Config) -> List[str]:
line = "rootdir: %s" % config.rootpath
result = [f"rootdir: {config.rootpath}"]
if config.inipath:
line += ", configfile: " + bestrelpath(config.rootpath, config.inipath)
result.append("configfile: " + bestrelpath(config.rootpath, config.inipath))
if config.args_source == Config.ArgsSource.TESTPATHS:
testpaths: List[str] = config.getini("testpaths")
line += ", testpaths: {}".format(", ".join(testpaths))
result = [line]
result.append("testpaths: {}".format(", ".join(testpaths)))
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:

View File

@@ -2,7 +2,6 @@
import dataclasses
import os
import re
import sys
import tempfile
from pathlib import Path
from shutil import rmtree
@@ -30,7 +29,7 @@ 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 _pytest.compat import final
from _pytest.compat import final, get_user_id
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
@@ -176,19 +175,16 @@ class TempPathFactory:
# Also, to keep things private, fixup any world-readable temp
# rootdir's permissions. Historically 0o755 was used, so we can't
# just error out on this, at least for a while.
if sys.platform != "win32":
uid = os.getuid()
uid = get_user_id()
if uid is not None:
rootdir_stat = rootdir.stat()
# getuid shouldn't fail, but cpython defines such a case.
# Let's hope for the best.
if uid != -1:
if rootdir_stat.st_uid != uid:
raise OSError(
f"The temporary directory {rootdir} is not owned by the current user. "
"Fix this and try again."
)
if (rootdir_stat.st_mode & 0o077) != 0:
os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
if rootdir_stat.st_uid != uid:
raise OSError(
f"The temporary directory {rootdir} is not owned by the current user. "
"Fix this and try again."
)
if (rootdir_stat.st_mode & 0o077) != 0:
os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
keep = self._retention_count
if self._retention_policy == "none":
keep = 0

View File

@@ -693,7 +693,14 @@ class TestInvocationVariants:
# mixed module and filenames:
monkeypatch.chdir("world")
result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
# pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages.
# While we could change the test to use implicit namespace packages, seems better
# to still ensure the old declaration via declare_namespace still works.
ignore_w = r"-Wignore:Deprecated call to `pkg_resources.declare_namespace"
result = pytester.runpytest(
"--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w
)
assert result.ret == 0
result.stdout.fnmatch_lines(
[

View File

@@ -294,6 +294,7 @@ 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
@@ -311,10 +312,7 @@ class TestTraceback_f_g_h:
excinfo = pytest.raises(ValueError, f)
tb = excinfo.traceback
entry = tb.getcrashentry()
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"
assert entry is None
def test_excinfo_exconly():
@@ -463,6 +461,24 @@ class TestFormattedExcinfo:
assert lines[0] == "| def f(x):"
assert lines[1] == " pass"
def test_repr_source_out_of_bounds(self):
pr = FormattedExcinfo()
source = _pytest._code.Source(
"""\
def f(x):
pass
"""
).strip()
pr.flow_marker = "|" # type: ignore[misc]
lines = pr.get_source(source, 100)
assert len(lines) == 1
assert lines[0] == "| ???"
lines = pr.get_source(source, -100)
assert len(lines) == 1
assert lines[0] == "| ???"
def test_repr_source_excinfo(self) -> None:
"""Check if indentation is right."""
try:

View File

@@ -1,13 +1,13 @@
anyio[curio,trio]==3.6.2
django==4.1.7
pytest-asyncio==0.20.2
pytest-asyncio==0.21.0
pytest-bdd==6.1.1
pytest-cov==4.0.0
pytest-django==4.5.2
pytest-flakes==4.0.5
pytest-html==3.2.0
pytest-mock==3.10.0
pytest-rerunfailures==11.1
pytest-rerunfailures==11.1.2
pytest-sugar==0.9.5
pytest-trio==0.7.0
pytest-twisted==1.14.0

View File

@@ -1265,6 +1265,177 @@ class TestIssue2121:
result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"])
@pytest.mark.skipif(
sys.version_info < (3, 8), reason="walrus operator not available in py<38"
)
class TestIssue10743:
def test_assertion_walrus_operator(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
def my_func(before, after):
return before == after
def change_value(value):
return value.lower()
def test_walrus_conversion():
a = "Hello"
assert not my_func(a, a := change_value(a))
assert a == "hello"
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_assertion_walrus_operator_dont_rewrite(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
'PYTEST_DONT_REWRITE'
def my_func(before, after):
return before == after
def change_value(value):
return value.lower()
def test_walrus_conversion_dont_rewrite():
a = "Hello"
assert not my_func(a, a := change_value(a))
assert a == "hello"
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_assertion_inline_walrus_operator(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
def my_func(before, after):
return before == after
def test_walrus_conversion_inline():
a = "Hello"
assert not my_func(a, a := a.lower())
assert a == "hello"
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_assertion_inline_walrus_operator_reverse(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
def my_func(before, after):
return before == after
def test_walrus_conversion_reverse():
a = "Hello"
assert my_func(a := a.lower(), a)
assert a == 'hello'
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_assertion_walrus_no_variable_name_conflict(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
def test_walrus_conversion_no_conflict():
a = "Hello"
assert a == (b := a.lower())
"""
)
result = pytester.runpytest()
assert result.ret == 1
result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"])
def test_assertion_walrus_operator_true_assertion_and_changes_variable_value(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
def test_walrus_conversion_succeed():
a = "Hello"
assert a != (a := a.lower())
assert a == 'hello'
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_assertion_walrus_operator_fail_assertion(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
def test_walrus_conversion_fails():
a = "Hello"
assert a == (a := a.lower())
"""
)
result = pytester.runpytest()
assert result.ret == 1
result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"])
def test_assertion_walrus_operator_boolean_composite(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
def test_walrus_operator_change_boolean_value():
a = True
assert a and True and ((a := False) is False) and (a is False) and ((a := None) is None)
assert a is None
"""
)
result = pytester.runpytest()
assert result.ret == 0
def test_assertion_walrus_operator_compare_boolean_fails(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
def test_walrus_operator_change_boolean_value():
a = True
assert not (a and ((a := False) is False))
"""
)
result = pytester.runpytest()
assert result.ret == 1
result.stdout.fnmatch_lines(["*assert not (True and False is False)"])
def test_assertion_walrus_operator_boolean_none_fails(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
def test_walrus_operator_change_boolean_value():
a = True
assert not (a and ((a := None) is None))
"""
)
result = pytester.runpytest()
assert result.ret == 1
result.stdout.fnmatch_lines(["*assert not (True and None is None)"])
def test_assertion_walrus_operator_value_changes_cleared_after_each_test(
self, pytester: Pytester
) -> None:
pytester.makepyfile(
"""
def test_walrus_operator_change_value():
a = True
assert (a := None) is None
def test_walrus_operator_not_override_value():
a = True
assert a is True
"""
)
result = pytester.runpytest()
assert result.ret == 0
@pytest.mark.skipif(
sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
)

View File

@@ -74,7 +74,7 @@ class TestParseIni:
% p1.name,
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"])
result.stdout.fnmatch_lines(["configfile: setup.cfg", "* 1 passed in *"])
assert result.ret == 0
def test_append_parse_args(

View File

@@ -425,6 +425,9 @@ def test_context_classmethod() -> None:
assert A.x == 1
@pytest.mark.filterwarnings(
"ignore:Deprecated call to `pkg_resources.declare_namespace"
)
def test_syspath_prepend_with_namespace_packages(
pytester: Pytester, monkeypatch: MonkeyPatch
) -> None:

View File

@@ -909,7 +909,7 @@ class TestTerminalFunctional:
# with configfile
pytester.makeini("""[pytest]""")
result = pytester.runpytest()
result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"])
result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"])
# with testpaths option, and not passing anything in the command-line
pytester.makeini(
@@ -920,12 +920,12 @@ class TestTerminalFunctional:
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
["rootdir: *test_header0, configfile: tox.ini, testpaths: tests, gui"]
["rootdir: *test_header0", "configfile: tox.ini", "testpaths: tests, gui"]
)
# with testpaths option, passing directory in command-line: do not show testpaths then
result = pytester.runpytest("tests")
result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"])
result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"])
def test_header_absolute_testpath(
self, pytester: Pytester, monkeypatch: MonkeyPatch
@@ -944,9 +944,9 @@ class TestTerminalFunctional:
result = pytester.runpytest()
result.stdout.fnmatch_lines(
[
"rootdir: *absolute_testpath0, configfile: pyproject.toml, testpaths: {}".format(
tests
)
"rootdir: *absolute_testpath0",
"configfile: pyproject.toml",
f"testpaths: {tests}",
]
)
@@ -2213,6 +2213,24 @@ class TestProgressOutputStyle:
output = pytester.runpytest("--capture=no")
output.stdout.no_fnmatch_line("*%]*")
def test_capture_no_progress_enabled(
self, many_tests_files, pytester: Pytester
) -> None:
pytester.makeini(
"""
[pytest]
console_output_style = progress-even-when-capture-no
"""
)
output = pytester.runpytest("-s")
output.stdout.re_match_lines(
[
r"test_bar.py \.{10} \s+ \[ 50%\]",
r"test_foo.py \.{5} \s+ \[ 75%\]",
r"test_foobar.py \.{5} \s+ \[100%\]",
]
)
class TestProgressWithTeardown:
"""Ensure we show the correct percentages for tests that fail during teardown (#3088)"""

View File

@@ -0,0 +1,25 @@
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