Compare commits

..

79 Commits

Author SHA1 Message Date
pre-commit-ci[bot]
3f12087fe0 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2022-02-11 15:56:37 +00:00
pytest bot
bc3021cdfd Prepare release version 7.0.1 2022-02-11 15:54:45 +00:00
Bruno Oliveira
591d476f14 Merge pull request #9673 from nicoddemus/backport-9511
[7.0.x] Enable testing with Python 3.11 (#9511)
2022-02-11 12:45:35 -03:00
Bruno Oliveira
6ca733e8f1 Enable testing with Python 3.11 (#9511) 2022-02-11 12:26:29 -03:00
Bruno Oliveira
ac37b1b113 Merge pull request #9671 from nicoddemus/backport-9668
[7.0.x] Merge pull request #9668 from hugovk/test-me-latest-3.10
2022-02-11 12:21:36 -03:00
Bruno Oliveira
c891e402ac Merge pull request #9672 from nicoddemus/backport-9669
[7.0.x] Merge pull request #9669 from hugovk/ci-only-update-plugin-list-for-upstream
2022-02-11 10:00:08 -03:00
Bruno Oliveira
e2753a2b8b Merge pull request #9669 from hugovk/ci-only-update-plugin-list-for-upstream 2022-02-11 09:34:30 -03:00
Bruno Oliveira
b5a154c1d9 Merge pull request #9668 from hugovk/test-me-latest-3.10 2022-02-11 09:28:15 -03:00
Bruno Oliveira
0fae45bb6e Merge pull request #9660 from pytest-dev/backport-9646-to-7.0.x 2022-02-10 14:30:27 -03:00
Bruno Oliveira
37d434f5fc [7.0.x] Delay warning about collector/item diamond inheritance 2022-02-10 17:12:02 +00:00
github-actions[bot]
6684110408 [7.0.x] unittest: restore UnitTestFunction.obj to return unbound rather than bound method (#9656)
Co-authored-by: Ran Benita <ran@unusedvar.com>
2022-02-09 23:20:31 +01:00
Ran Benita
ed8255c811 Merge pull request #9653 from pytest-dev/backport-9651-to-7.0.x
[7.0.x] Rename ``pythonpath`` plugin to ``python_path``
2022-02-09 13:54:07 +02:00
Ran Benita
453bcb1b83 [7.0.x] Rename `pythonpath plugin to python_path` 2022-02-09 11:20:53 +00:00
Bruno Oliveira
a24c78b7af Merge pull request #9649 from pytest-dev/backport-9642-to-7.0.x 2022-02-08 13:26:10 -03:00
Anthony Sottile
4bf8aff5b7 [7.0.x] allow running testids which contain :: in the parametrized portion 2022-02-08 16:09:06 +00:00
Ran Benita
462a6f0de6 Merge pull request #9647 from pytest-dev/backport-9609-to-7.0.x
[7.0.x] importlib.readers not valid until python 3.10
2022-02-08 17:54:38 +02:00
Ran Benita
c588a4720e [7.0.x] importlib.readers not valid until python 3.10 2022-02-08 17:36:38 +02:00
Ran Benita
048a10bd96 Merge pull request #9648 from pytest-dev/backport-9638-to-7.0.x
[7.0.x] work around test pollution caused by new setuptools mutating global logger level
2022-02-08 16:52:39 +02:00
Anthony Sottile
3c35477230 [7.0.x] work around test pollution caused by new setuptools mutating global logger level 2022-02-08 14:32:58 +00:00
github-actions[bot]
5eb4d6977c [7.0.x] doc: Hide done training (#9605)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-02-04 11:42:53 +00:00
github-actions[bot]
e37fbe5685 Prepare release 7.0.0 (#9598)
* Prepare release version 7.0.0

* Add note to changelog

Co-authored-by: pytest bot <pytestbot@gmail.com>
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-02-04 11:23:39 +01:00
github-actions[bot]
737b220516 [7.0.x] releasing: Add template for major releases (#9597)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-02-03 17:04:19 +01:00
github-actions[bot]
7fa3972963 [7.0.x] releasing: Always set doc_version (#9590)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-02-03 11:07:47 +00:00
github-actions[bot]
b304499925 [7.0.x] Make 'warnings' and 'deselected' in assert_outcomes optional (#9566)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2022-01-27 14:18:54 +00:00
github-actions[bot]
f17525df26 [7.0.x] doc: Add ellipsis to warning usecase list (#9562)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-01-27 10:36:58 -03:00
Florian Bruhin
0a7be971d2 ci: Bump up timeout (#9565)
macOS apparently can be slow, https://github.com/pytest-dev/pytest/runs/4965510831 for #9556 got cancelled at 91%

(cherry picked from commit 7bffcd0ac4)
2022-01-27 10:36:25 -03:00
github-actions[bot]
c17908cdb3 [7.0.x] doc: Recategorize 7.0.0 changelog items (#9564)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-01-27 10:36:15 -03:00
github-actions[bot]
ab549bba8a [7.0.x] Add missing cooperative constructor changelog (#9563)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-01-27 10:36:04 -03:00
github-actions[bot]
4b1707ff70 [7.0.x] Autouse linearization graph (#9557)
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2022-01-27 11:57:34 +00:00
github-actions[bot]
28e5b3b8b7 [7.0.x] Add additional docs for uncooperative ctor deprecation (#9552)
Co-authored-by: Florian Bruhin <me@the-compiler.org>
2022-01-27 11:54:34 +00:00
Bruno Oliveira
e854d05328 Merge pull request #9550 from pytest-dev/backport-9545-to-7.0.x 2022-01-27 07:54:44 -03:00
Ran Benita
48e64feac7 [7.0.x] doc/reference: don't document pytest.__version__ under "Marks" 2022-01-27 08:50:18 +00:00
Bruno Oliveira
b66899d322 Merge pull request #9530 from nicoddemus/backport-9522 2022-01-25 16:53:49 -03:00
Ran Benita
b1b1bd03a8 Merge pull request #9544 from bluetech/backport-9532
[7.0.x] config: avoid stat storm in _getconftestmodules
2022-01-25 21:53:12 +02:00
Ran Benita
6639d6c7d8 Merge pull request #9532 from bluetech/getdir-cache
config: avoid stat storm in _getconftestmodules

(cherry picked from commit 5c69eced6c)

Conflicts:
	src/_pytest/config/__init__.py
2022-01-25 18:46:16 +02:00
Bruno Oliveira
6a1b8e4b28 Merge pull request #9522 from holmanb/rewrite-test 2022-01-20 13:21:48 -03:00
Ran Benita
5c49dea989 Merge pull request #9513 from pytest-dev/backport-9512-to-7.0.x
[7.0.x] testing: avoid private pluggy attributes in test
2022-01-14 18:30:33 +02:00
Ran Benita
a1635ca49a [7.0.x] testing: avoid private pluggy attributes in test 2022-01-14 16:10:54 +00:00
Bruno Oliveira
46c06e7560 Merge pull request #9508 from pytest-dev/backport-9495-to-7.0.x 2022-01-13 17:11:22 -03:00
Olga Matoula
94bcd2ce0f [7.0.x] Add docs on pytest.warns(None) deprecation 2022-01-13 19:51:06 +00:00
Bruno Oliveira
4967a0d084 Merge pull request #9507 from nicoddemus/backport-9506
[7.0.x] Improve on configuration file docs section
2022-01-13 15:14:32 -03:00
Bruno Oliveira
efa02cffdd Merge pull request #9506 from eamanu/fix-9505 2022-01-13 13:55:08 -03:00
Ran Benita
b5a6e30dae Merge pull request #9499 from pytest-dev/backport-9494-to-7.0.x
[7.0.x] python: add back `instance` accessor to all python nodes, not just Function
2022-01-10 21:28:12 +02:00
Ran Benita
d0ae12ca76 [7.0.x] python: add back instance accessor to all python nodes, not just Function 2022-01-10 19:09:14 +00:00
Ran Benita
0c5fc61610 Merge pull request #9485 from pytest-dev/backport-9484-to-7.0.x
[7.0.x] config: fix incorrect cache hit check in _getconftestmodules
2022-01-07 13:44:58 +02:00
Ran Benita
41cb93b549 [7.0.x] config: fix incorrect cache hit check in _getconftestmodules 2022-01-07 11:28:12 +00:00
Bruno Oliveira
7381ce2d83 Merge pull request #9474 from pytest-dev/backport-9400-to-7.0.x 2022-01-04 11:28:51 -03:00
Bruno Oliveira
5d151c0e7c Merge pull request #9476 from pytest-dev/backport-9464-to-7.0.x 2022-01-04 11:27:48 -03:00
Dan Alvizu
f988e070c6 [7.0.x] Doc update: clarify -W syntax 2022-01-04 14:04:18 +00:00
Bruno Oliveira
1a04121f2f [7.0.x] Ensure Config.inifile is available during pytest_cmdline_main 2022-01-04 13:24:11 +00:00
Ran Benita
8542eb47c4 Merge pull request #9453 from bluetech/backport-9451
[7.0.x] Backport "doc: document {Code,TracebackEntry}.path changes as breaking"
2021-12-28 10:55:24 +02:00
Ran Benita
4f79cea72d Merge pull request #9451 from bluetech/code-path-changelog
doc: document {Code,TracebackEntry}.path changes as breaking
(cherry picked from commit 1131f23e04)
2021-12-27 17:01:04 +02:00
Ran Benita
c76989352f Merge pull request #9450 from bluetech/backport-9438
[7.0.x] Backport "pytest: bring back direct imports of TempdirFactory, Testdir"
2021-12-27 16:54:01 +02:00
Ran Benita
74f4aad708 Merge pull request #9449 from bluetech/backport-9447
[7.0.x] Backport "code: accept any `os.PathLike[str]` in `Traceback.cut`"
2021-12-27 15:55:37 +02:00
Ran Benita
5171327e3b Merge pull request #9438 from bluetech/pytest-legacypath-imports
pytest: bring back direct imports of TempdirFactory, Testdir
(cherry picked from commit 69da199f6e)
2021-12-27 15:11:45 +02:00
Ran Benita
9274fa5610 Merge pull request #9447 from bluetech/code-cut-pathlike
code: accept any `os.PathLike[str]` in `Traceback.cut`
(cherry picked from commit fcef7e49fd)
2021-12-27 15:10:13 +02:00
Bruno Oliveira
161841d38e Fix typos (#9424) (#9448)
Co-authored-by: Kian Meng, Ang <kianmeng.ang@gmail.com>
2021-12-27 10:02:26 -03:00
Kian Meng, Ang
e62daed8c4 Fix typos (#9424) 2021-12-27 09:26:25 -03:00
Ran Benita
764f90351a Merge pull request #9446 from bluetech/backport-9443
[7.0.x] Backport "doc/reference: add 4 missing hooks to reference"
2021-12-27 12:11:44 +02:00
Ran Benita
50bf3625c9 Merge pull request #9445 from bluetech/backport-9441
[7.0.x] Backport "python: skip nose setup/teardown fixtures if non-callable"
2021-12-27 11:41:02 +02:00
Ran Benita
0002597ddd Merge pull request #9443 from bluetech/undocumented-hooks
doc/reference: add 4 missing hooks to reference

(cherry picked from commit 7a42db2bf0)
2021-12-27 11:14:58 +02:00
Ran Benita
41e424b172 Merge pull request #9441 from bluetech/nose-setup-callable
python: skip nose setup/teardown fixtures if non-callable
(cherry picked from commit 7fc2cf51c2)
2021-12-27 11:13:12 +02:00
Ran Benita
839ca90c0e Merge pull request #9439 from bluetech/backport-9416
[7.0.x] Backport "doc: fix a reference in "Writing hook functions""
2021-12-25 13:00:34 +02:00
Ran Benita
8f1a8800c8 Merge pull request #9416 from bluetech/doc-stash-fix
doc: fix a reference in "Writing hook functions"
(cherry picked from commit 443aa0219c)
2021-12-25 10:50:36 +02:00
Bruno Oliveira
045713ac2e Merge pull request #9418 from nicoddemus/backport-9417
[7.0.x] Fix test_errors_in_xfail_skip_expressions for Python 3.10.1
2021-12-16 10:57:49 -03:00
Bruno Oliveira
f094355401 Merge pull request #9417 from nicoddemus/fix-py3.10.1-9413 2021-12-16 10:38:43 -03:00
Bruno Oliveira
378baab126 Merge pull request #9409 from bluetech/backport-9401 2021-12-12 13:30:54 -03:00
Ran Benita
74b9f46e40 Merge pull request #9401 from bluetech/doc-hooks-ref
doc: add a `hook` crossref type
(cherry picked from commit 8040cfd965)
2021-12-12 18:05:19 +02:00
Bruno Oliveira
bf913eb4e7 Merge pull request #9398 from The-Compiler/backport-9390
[7.0.x] fix python version in changelog message (#9390)
2021-12-08 07:50:19 -03:00
Bruno Oliveira
813e1e5b54 Merge pull request #9399 from The-Compiler/backport-9394
[7.0.x] Hide internal stack when using pytest.approx() in bool context (#9394)
2021-12-08 07:50:11 -03:00
Bruno Oliveira
3587d6b526 Merge pull request #9397 from The-Compiler/backport-9389
[7.0.x] fix typo
2021-12-08 07:50:00 -03:00
Bruno Oliveira
76e108d06c Hide internal stack when using pytest.approx() in bool context (#9394)
This makes the error traceback point directly to the offending usage, rather
than to the internal `Approx.__bool__` method.

(cherry picked from commit 3ba9c01f9b)
2021-12-08 10:54:47 +01:00
Anthony Sottile
5f70fcba2e fix python version in changelog message (#9390)
(cherry picked from commit 7ae23ff8ae)
2021-12-08 10:54:19 +01:00
Éric
39028fac00 fix typo
(cherry picked from commit b691d31897)
2021-12-08 10:53:30 +01:00
Florian Bruhin
ac0870ebad scripts: Use release branch for changelog URL (#9380) (#9383)
* scripts: Use release branch for changelog URL

With a prerelease, /stable won't show the correct changelog.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
(cherry picked from commit 21a186bbda)
2021-12-07 19:19:42 +01:00
Florian Bruhin
871533322b Fix changelog URL in 7.0.0rc1 announcement (#9379) (#9382)
The changelog does not exist at /stable because an rc isn't stable...

(cherry picked from commit 5cb50fa13c)
2021-12-07 11:44:19 +01:00
Bruno Oliveira
df2c59c07e Merge pull request #9381 from nicoddemus/backport-9359 2021-12-07 07:20:38 -03:00
Yuval Shimon
9bfa02ea07 Fixed error message prints function decorators when using assert in Python 3.9 and above. (#9359) 2021-12-07 07:08:48 -03:00
github-actions[bot]
85897eddc6 Prepare release version 7.0.0rc1 (#9375)
Co-authored-by: pytest bot <pytestbot@gmail.com>
2021-12-07 09:37:18 +01:00
68 changed files with 957 additions and 309 deletions

View File

@@ -23,7 +23,7 @@ permissions: {}
jobs:
build:
runs-on: ${{ matrix.os }}
timeout-minutes: 30
timeout-minutes: 45
permissions:
contents: read
@@ -37,6 +37,7 @@ jobs:
"windows-py38",
"windows-py39",
"windows-py310",
"windows-py311",
"ubuntu-py36",
"ubuntu-py37",
@@ -45,6 +46,7 @@ jobs:
"ubuntu-py38",
"ubuntu-py39",
"ubuntu-py310",
"ubuntu-py311",
"ubuntu-pypy3",
"macos-py37",
@@ -78,9 +80,13 @@ jobs:
os: windows-latest
tox_env: "py39-xdist"
- name: "windows-py310"
python: "3.10-dev"
python: "3.10"
os: windows-latest
tox_env: "py310-xdist"
- name: "windows-py311"
python: "3.11-dev"
os: windows-latest
tox_env: "py311"
- name: "ubuntu-py36"
python: "3.6"
@@ -108,9 +114,13 @@ jobs:
os: ubuntu-latest
tox_env: "py39-xdist"
- name: "ubuntu-py310"
python: "3.10-dev"
python: "3.10"
os: ubuntu-latest
tox_env: "py310-xdist"
- name: "ubuntu-py311"
python: "3.11-dev"
os: ubuntu-latest
tox_env: "py311"
- name: "ubuntu-pypy3"
python: "pypy-3.7"
os: ubuntu-latest

View File

@@ -12,6 +12,7 @@ permissions: {}
jobs:
createPullRequest:
if: github.repository_owner == 'pytest-dev'
runs-on: ubuntu-latest
permissions:
contents: write

View File

@@ -185,6 +185,7 @@ Katerina Koukiou
Keri Volans
Kevin Cox
Kevin J. Foley
Kian-Meng Ang
Kodi B. Arfer
Kostis Anagnostopoulos
Kristoffer Nordström
@@ -348,6 +349,7 @@ Xixi Zhao
Xuan Luong
Xuecong Liao
Yoav Caspi
Yuval Shimon
Zac Hatfield-Dodds
Zachary Kneupper
Zoltán Máté

View File

@@ -6,6 +6,8 @@ Release announcements
:maxdepth: 2
release-7.0.1
release-7.0.0
release-7.0.0rc1
release-6.2.5
release-6.2.4

View File

@@ -11,7 +11,7 @@ clear information about the circumstances and a simple example which
reproduces the problem.
The issue tracker is of course not empty now. We have many remaining
"enhacement" issues which we'll hopefully can tackle in 2014 with your
"enhancement" issues which we'll hopefully can tackle in 2014 with your
help.
For those who use older Python versions, please note that pytest is not

View File

@@ -0,0 +1,74 @@
pytest-7.0.0
=======================================
The pytest team is proud to announce the 7.0.0 release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
* Adam J. Stewart
* Alexander King
* Amin Alaee
* Andrew Neitsch
* Anthony Sottile
* Ben Davies
* Bernát Gábor
* Brian Okken
* Bruno Oliveira
* Cristian Vera
* Dan Alvizu
* David Szotten
* Eddie
* Emmanuel Arias
* Emmanuel Meric de Bellefon
* Eric Liu
* Florian Bruhin
* GergelyKalmar
* Graeme Smecher
* Harshna
* Hugo van Kemenade
* Jakub Kulík
* James Myatt
* Jeff Rasley
* Kale Kundert
* Kian Meng, Ang
* Miro Hrončok
* Naveen-Pratap
* Oleg Höfling
* Olga Matoula
* Ran Benita
* Ronny Pfannschmidt
* Simon K
* Srip
* Sören Wegener
* Taneli Hukkinen
* Terje Runde
* Thomas Grainger
* Thomas Hisch
* William Jamir Silva
* Yuval Shimon
* Zac Hatfield-Dodds
* andrewdotn
* denivyruck
* ericluoliu
* oleg.hoefling
* symonk
* ziebam
* Éloi Rivard
* Éric
Happy testing,
The pytest Development Team

View File

@@ -19,7 +19,7 @@ You can upgrade from PyPI via:
Users are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
https://docs.pytest.org/en/7.0.x/changelog.html
Thanks to all the contributors to this release:

View File

@@ -0,0 +1,20 @@
pytest-7.0.1
=======================================
pytest 7.0.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Anthony Sottile
* Bruno Oliveira
* Ran Benita
Happy testing,
The pytest Development Team

View File

@@ -117,10 +117,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
:issue:`7767` for details.
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:292
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:295
Return a :class:`pytest.TempdirFactory` instance for the test session.
tmpdir -- .../_pytest/legacypath.py:299
tmpdir -- .../_pytest/legacypath.py:302
Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.

View File

@@ -28,6 +28,75 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 7.0.1 (2022-02-11)
=========================
Bug Fixes
---------
- `#9608 <https://github.com/pytest-dev/pytest/issues/9608>`_: Fix invalid importing of ``importlib.readers`` in Python 3.9.
- `#9610 <https://github.com/pytest-dev/pytest/issues/9610>`_: Restore `UnitTestFunction.obj` to return unbound rather than bound method.
Fixes a crash during a failed teardown in unittest TestCases with non-default `__init__`.
Regressed in pytest 7.0.0.
- `#9636 <https://github.com/pytest-dev/pytest/issues/9636>`_: The ``pythonpath`` plugin was renamed to ``python_path``. This avoids a conflict with the ``pytest-pythonpath`` plugin.
- `#9642 <https://github.com/pytest-dev/pytest/issues/9642>`_: Fix running tests by id with ``::`` in the parametrize portion.
- `#9643 <https://github.com/pytest-dev/pytest/issues/9643>`_: Delay issuing a :class:`~pytest.PytestWarning` about diamond inheritance involving :class:`~pytest.Item` and
:class:`~pytest.Collector` so it can be filtered using :ref:`standard warning filters <warnings>`.
pytest 7.0.0 (2022-02-03)
=========================
(**Please see the full set of changes for this release also in the 7.0.0rc1 notes below**)
Deprecations
------------
- `#9488 <https://github.com/pytest-dev/pytest/issues/9488>`_: If custom subclasses of nodes like :class:`pytest.Item` override the
``__init__`` method, they should take ``**kwargs``. See
:ref:`uncooperative-constructors-deprecated` for details.
Note that a deprection warning is only emitted when there is a conflict in the
arguments pytest expected to pass. This deprecation was already part of pytest
7.0.0rc1 but wasn't documented.
Bug Fixes
---------
- `#9355 <https://github.com/pytest-dev/pytest/issues/9355>`_: Fixed error message prints function decorators when using assert in Python 3.8 and above.
- `#9396 <https://github.com/pytest-dev/pytest/issues/9396>`_: Ensure :attr:`pytest.Config.inifile` is available during the :func:`pytest_cmdline_main <_pytest.hookspec.pytest_cmdline_main>` hook (regression during ``7.0.0rc1``).
Improved Documentation
----------------------
- `#9404 <https://github.com/pytest-dev/pytest/issues/9404>`_: Added extra documentation on alternatives to common misuses of `pytest.warns(None)` ahead of its deprecation.
- `#9505 <https://github.com/pytest-dev/pytest/issues/9505>`_: Clarify where the configuration files are located. To avoid confusions documentation mentions
that configuration file is located in the root of the repository.
Trivial/Internal Changes
------------------------
- `#9521 <https://github.com/pytest-dev/pytest/issues/9521>`_: Add test coverage to assertion rewrite path.
pytest 7.0.0rc1 (2021-12-06)
============================
@@ -156,7 +225,7 @@ Deprecations
See :ref:`the deprecation note <diamond-inheritance-deprecated>` for full details.
- `#8592 <https://github.com/pytest-dev/pytest/issues/8592>`_: :func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` has been officially deprecated. It will be removed in a future release. Use :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` instead.
- `#8592 <https://github.com/pytest-dev/pytest/issues/8592>`_: :hook:`pytest_cmdline_preparse` has been officially deprecated. It will be removed in a future release. Use :hook:`pytest_load_initial_conftests` instead.
See :ref:`the deprecation note <cmdline-preparse-deprecated>` for full details.
@@ -173,6 +242,12 @@ Deprecations
This was changed for consistency with :func:`pytest.mark.skip <pytest.mark.skip>` and :func:`pytest.mark.xfail <pytest.mark.xfail>` which both accept
``reason`` as an argument.
- `#8174 <https://github.com/pytest-dev/pytest/issues/8174>`_: The following changes have been made to types reachable through :attr:`pytest.ExceptionInfo.traceback`:
- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
There was no deprecation period for this change (sorry!).
Features
@@ -203,11 +278,11 @@ Features
- ``pytest.Mark`` for :class:`marks <pytest.Mark>`.
- ``pytest.MarkDecorator`` for :class:`mark decorators <pytest.MarkDecorator>`.
- ``pytest.MarkGenerator`` for the :class:`pytest.mark <pytest.MarkGenerator>` singleton.
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :func:`pytest_generate_tests <pytest.hookspec.pytest_generate_tests>` hook.
- ``pytest.Metafunc`` for the :class:`metafunc <pytest.MarkGenerator>` argument to the :hook:`pytest_generate_tests` hook.
- ``pytest.CallInfo`` for the :class:`CallInfo <pytest.CallInfo>` type passed to various hooks.
- ``pytest.PytestPluginManager`` for :class:`PytestPluginManager <pytest.PytestPluginManager>`.
- ``pytest.ExceptionInfo`` for the :class:`ExceptionInfo <pytest.ExceptionInfo>` type returned from :func:`pytest.raises` and passed to various hooks.
- ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :func:`pytest_addoption <pytest.hookspec.pytest_addoption>` hook.
- ``pytest.Parser`` for the :class:`Parser <pytest.Parser>` type passed to the :hook:`pytest_addoption` hook.
- ``pytest.OptionGroup`` for the :class:`OptionGroup <pytest.OptionGroup>` type returned from the :func:`parser.addgroup <pytest.Parser.getgroup>` method.
- ``pytest.HookRecorder`` for the :class:`HookRecorder <pytest.HookRecorder>` type returned from :class:`~pytest.Pytester`.
- ``pytest.RecordedHookCall`` for the :class:`RecordedHookCall <pytest.HookRecorder>` type returned from :class:`~pytest.HookRecorder`.
@@ -228,11 +303,11 @@ Features
- `#8144 <https://github.com/pytest-dev/pytest/issues/8144>`_: The following hooks now receive an additional ``pathlib.Path`` argument, equivalent to an existing ``py.path.local`` argument:
- :func:`pytest_ignore_collect <_pytest.hookspec.pytest_ignore_collect>` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_collect_file <_pytest.hookspec.pytest_collect_file>` - The ``file_path`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_pycollect_makemodule <_pytest.hookspec.pytest_pycollect_makemodule>` - The ``module_path`` parameter (equivalent to existing ``path`` parameter).
- :func:`pytest_report_header <_pytest.hookspec.pytest_report_header>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
- :func:`pytest_report_collectionfinish <_pytest.hookspec.pytest_report_collectionfinish>` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
- :hook:`pytest_ignore_collect` - The ``collection_path`` parameter (equivalent to existing ``path`` parameter).
- :hook:`pytest_collect_file` - The ``file_path`` parameter (equivalent to existing ``path`` parameter).
- :hook:`pytest_pycollect_makemodule` - The ``module_path`` parameter (equivalent to existing ``path`` parameter).
- :hook:`pytest_report_header` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
- :hook:`pytest_report_collectionfinish` - The ``start_path`` parameter (equivalent to existing ``startdir`` parameter).
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
@@ -473,10 +548,9 @@ Trivial/Internal Changes
- `#8174 <https://github.com/pytest-dev/pytest/issues/8174>`_: The following changes have been made to internal pytest types/functions:
- The ``path`` property of ``_pytest.code.Code`` returns ``Path`` instead of ``py.path.local``.
- The ``path`` property of ``_pytest.code.TracebackEntry`` returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.code.getfslineno()`` function returns ``Path`` instead of ``py.path.local``.
- The ``_pytest.python.path_matches_patterns()`` function takes ``Path`` instead of ``py.path.local``.
- The ``_pytest._code.Traceback.cut()`` function accepts any ``os.PathLike[str]``, not just ``py.path.local``.
- `#8248 <https://github.com/pytest-dev/pytest/issues/8248>`_: Internal Restructure: let ``python.PyObjMixin`` inherit from ``nodes.Node`` to carry over typing information.
@@ -491,7 +565,7 @@ Trivial/Internal Changes
- `#8913 <https://github.com/pytest-dev/pytest/issues/8913>`_: The private ``CallSpec2._arg2scopenum`` attribute has been removed after an internal refactoring.
- `#8967 <https://github.com/pytest-dev/pytest/issues/8967>`_: :func:`pytest_assertion_pass <_pytest.hookspec.pytest_assertion_pass>` is no longer considered experimental and
- `#8967 <https://github.com/pytest-dev/pytest/issues/8967>`_: :hook:`pytest_assertion_pass` is no longer considered experimental and
future changes to it will be considered more carefully.
@@ -538,7 +612,7 @@ Bug Fixes
the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with
private permissions.
pytest used to silenty use a pre-existing ``/tmp/pytest-of-<username>`` directory,
pytest used to silently use a pre-existing ``/tmp/pytest-of-<username>`` directory,
even if owned by another user. This means another user could pre-create such a
directory and gain control of another user's temporary directory. Now such a
condition results in an error.
@@ -853,8 +927,8 @@ Deprecations
if you use this and want a replacement.
- :issue:`7255`: The :func:`pytest_warning_captured <_pytest.hookspec.pytest_warning_captured>` hook is deprecated in favor
of :func:`pytest_warning_recorded <_pytest.hookspec.pytest_warning_recorded>`, and will be removed in a future version.
- :issue:`7255`: The :hook:`pytest_warning_captured` hook is deprecated in favor
of :hook:`pytest_warning_recorded`, and will be removed in a future version.
- :issue:`7648`: The ``gethookproxy()`` and ``isinitpath()`` methods of ``FSCollector`` and ``Package`` are deprecated;
@@ -962,7 +1036,7 @@ Trivial/Internal Changes
process, pytest now ignores builtin attributes (like ``__class__``,
``__delattr__`` and ``__new__``) without consulting the :confval:`python_classes` and
:confval:`python_functions` configuration options and without passing them to plugins
using the :func:`pytest_pycollect_makeitem <_pytest.hookspec.pytest_pycollect_makeitem>` hook.
using the :hook:`pytest_pycollect_makeitem` hook.
pytest 6.0.2 (2020-09-04)
@@ -1304,7 +1378,7 @@ Improvements
is not displayed by default for passing tests. This change makes the mistake
visible during testing.
You may supress this behavior temporarily or permanently by setting
You may suppress this behavior temporarily or permanently by setting
``logging.raiseExceptions = False``.
@@ -1751,7 +1825,7 @@ Bug Fixes
- :issue:`6646`: Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc.
- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger.
- :issue:`6660`: :py:func:`pytest.exit` is handled when emitted from the :hook:`pytest_sessionfinish` hook. This includes quitting from a debugger.
- :issue:`6752`: When :py:func:`pytest.raises` is used as a function (as opposed to a context manager),
@@ -1759,7 +1833,7 @@ Bug Fixes
it was swallowed and ignored (regression in pytest 5.1.0).
- :issue:`6801`: Do not display empty lines inbetween traceback for unexpected exceptions with doctests.
- :issue:`6801`: Do not display empty lines in between traceback for unexpected exceptions with doctests.
- :issue:`6802`: The :fixture:`testdir fixture <testdir>` works within doctests now.
@@ -1863,7 +1937,7 @@ Improvements
- :issue:`6231`: Improve check for misspelling of :ref:`pytest.mark.parametrize ref`.
- :issue:`6257`: Handle :py:func:`pytest.exit` being used via :py:func:`~_pytest.hookspec.pytest_internalerror`, e.g. when quitting pdb from post mortem.
- :issue:`6257`: Handle :func:`pytest.exit` being used via :hook:`pytest_internalerror`, e.g. when quitting pdb from post mortem.
@@ -2497,7 +2571,7 @@ Deprecations
Features
--------
- :issue:`3457`: New :func:`~_pytest.hookspec.pytest_assertion_pass`
- :issue:`3457`: New :hook:`pytest_assertion_pass`
hook, called with context information when an assertion *passes*.
This hook is still **experimental** so use it with caution.
@@ -4628,7 +4702,7 @@ Bug Fixes
- Fixed a bug where stdout and stderr were logged twice by junitxml when a test
was marked xfail. (:issue:`3491`)
- Fix ``usefixtures`` mark applyed to unittest tests by correctly instantiating
- Fix ``usefixtures`` mark applied to unittest tests by correctly instantiating
``FixtureInfo``. (:issue:`3498`)
- Fix assertion rewriter compatibility with libraries that monkey patch
@@ -5017,9 +5091,9 @@ Features
- Console output falls back to "classic" mode when capturing is disabled (``-s``),
otherwise the output gets garbled to the point of being useless. (:issue:`3038`)
- New :func:`~_pytest.hookspec.pytest_runtest_logfinish`
- New :hook:`pytest_runtest_logfinish`
hook which is called when a test item has finished executing, analogous to
:func:`~_pytest.hookspec.pytest_runtest_logstart`.
:hook:`pytest_runtest_logstart`.
(:issue:`3101`)
- Improve performance when collecting tests using many fixtures. (:issue:`3107`)
@@ -8009,7 +8083,7 @@ Bug fixes:
or through plugin hooks. Also introduce a "--strict" option which
will treat unregistered markers as errors
allowing to avoid typos and maintain a well described set of markers
for your test suite. See exaples at http://pytest.org/en/stable/how-to/mark.html
for your test suite. See examples at http://pytest.org/en/stable/how-to/mark.html
and its links.
- issue50: introduce "-m marker" option to select tests based on markers
(this is a stricter and more predictable version of '-k' in that "-m"
@@ -8321,7 +8395,7 @@ Bug fixes:
- fix issue57 -f|--looponfail to work with xpassing tests (thanks Ronny)
- fix issue92 collectonly reporter and --pastebin (thanks Benjamin Peterson)
- fix py.code.compile(source) to generate unique filenames
- fix assertion re-interp problems on PyPy, by defering code
- fix assertion re-interp problems on PyPy, by deferring code
compilation to the (overridable) Frame.eval class. (thanks Amaury Forgeot)
- fix py.path.local.pyimport() to work with directories
- streamline py.path.local.mkdtemp implementation and usage
@@ -8395,7 +8469,7 @@ Bug fixes:
- improve support for raises and other dynamically compiled code by
manipulating python's linecache.cache instead of the previous
rather hacky way of creating custom code objects. This makes
it seemlessly work on Jython and PyPy where it previously didn't.
it seamlessly work on Jython and PyPy where it previously didn't.
- fix issue96: make capturing more resilient against Control-C
interruptions (involved somewhat substantial refactoring

View File

@@ -445,6 +445,13 @@ def setup(app: "sphinx.application.Sphinx") -> None:
indextemplate="pair: %s; global variable interpreted by pytest",
)
app.add_crossref_type(
directivename="hook",
rolename="hook",
objname="pytest hook",
indextemplate="pair: %s; hook",
)
configure_logging(app)
# Make Sphinx mark classes with "final" when decorated with @final.

View File

@@ -56,6 +56,10 @@ Plugins which implement custom items and collectors are encouraged to replace
``fspath`` parameters (``py.path.local``) with ``path`` parameters
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
If possible, plugins with custom items should use :ref:`cooperative
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
arguments they only pass on to the superclass.
.. note::
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
new attribute being ``path``) is **the opposite** of the situation for
@@ -81,11 +85,11 @@ no matter what argument was used in the constructor. We expect to deprecate the
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
* :func:`pytest_ignore_collect(collection_path: pathlib.Path) <_pytest.hookspec.pytest_ignore_collect>` as equivalent to ``path``
* :func:`pytest_collect_file(file_path: pathlib.Path) <_pytest.hookspec.pytest_collect_file>` as equivalent to ``path``
* :func:`pytest_pycollect_makemodule(module_path: pathlib.Path) <_pytest.hookspec.pytest_pycollect_makemodule>` as equivalent to ``path``
* :func:`pytest_report_header(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_header>` as equivalent to ``startdir``
* :func:`pytest_report_collectionfinish(start_path: pathlib.Path) <_pytest.hookspec.pytest_report_collectionfinish>` as equivalent to ``startdir``
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
@@ -127,7 +131,7 @@ Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit`
is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these
functions and the``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
.. code-block:: python
@@ -157,8 +161,8 @@ Implementing the ``pytest_cmdline_preparse`` hook
.. deprecated:: 7.0
Implementing the :func:`pytest_cmdline_preparse <_pytest.hookspec.pytest_cmdline_preparse>` hook has been officially deprecated.
Implement the :func:`pytest_load_initial_conftests <_pytest.hookspec.pytest_load_initial_conftests>` hook instead.
Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated.
Implement the :hook:`pytest_load_initial_conftests` hook instead.
.. code-block:: python
@@ -191,6 +195,40 @@ Instead, a separate collector node should be used, which collects the item. See
.. _example pr fixing inheritance: https://github.com/asmeurer/pytest-flakes/pull/40/files
.. _uncooperative-constructors-deprecated:
Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 7.0
If custom subclasses of nodes like :class:`pytest.Item` override the
``__init__`` method, they should take ``**kwargs``. Thus,
.. code-block:: python
class CustomItem(pytest.Item):
def __init__(self, name, parent, additional_arg):
super().__init__(name, parent)
self.additional_arg = additional_arg
should be turned into:
.. code-block:: python
class CustomItem(pytest.Item):
def __init__(self, *, additional_arg, **kwargs):
super().__init__(**kwargs)
self.additional_arg = additional_arg
to avoid hard-coding the arguments pytest can pass to the superclass.
See :ref:`non-python tests` for a full example.
For cases without conflicts, no deprecation warning is emitted. For cases with
conflicts (such as :class:`pytest.File` now taking ``path`` instead of
``fspath``, as :ref:`outlined above <node-ctor-fspath-deprecation>`), a
deprecation warning is now raised.
Backward compatibilities in ``Parser.addoption``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -221,11 +259,11 @@ Using ``pytest.warns(None)``
.. deprecated:: 7.0
:func:`pytest.warns(None) <pytest.warns>` is now deprecated because many people used
it to mean "this code does not emit warnings", but it actually had the effect of
checking that the code emits at least one warning of any type - like ``pytest.warns()``
:func:`pytest.warns(None) <pytest.warns>` is now deprecated because it was frequently misused.
Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()``
or ``pytest.warns(Warning)``.
See :ref:`warns use cases` for examples.
The ``--strict`` command-line option
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -324,8 +362,8 @@ at some point, depending on the plans for the plugins and number of users using
.. versionremoved:: 6.0
The ``pytest_collect_directory`` has not worked properly for years (it was called
but the results were ignored). Users may consider using :func:`pytest_collection_modifyitems <_pytest.hookspec.pytest_collection_modifyitems>` instead.
The ``pytest_collect_directory`` hook has not worked properly for years (it was called
but the results were ignored). Users may consider using :hook:`pytest_collection_modifyitems` instead.
TerminalReporter.writer
~~~~~~~~~~~~~~~~~~~~~~~

View File

@@ -0,0 +1,56 @@
<svg xmlns="http://www.w3.org/2000/svg" width="112" height="682">
<style>
text {
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
dominant-baseline: middle;
text-anchor: middle;
fill: #062886;
font-size: medium;
}
ellipse.fixture, rect.test {
fill: #eeffcc;
stroke: #007020;
stroke-width: 2;
}
text.fixture {
color: #06287e;
}
circle.class {
fill: #c3e0ec;
stroke: #0e84b5;
stroke-width: 2;
}
text.class {
fill: #0e84b5;
}
path, line {
stroke: black;
stroke-width: 2;
fill: none;
}
rect.autouse {
fill: #ca7f3d;
}
</style>
<line x1="56" x2="56" y1="681" y2="26" />
<ellipse class="fixture" rx="50" ry="25" cx="56" cy="26" />
<text x="56" y="26">order</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="96" />
<text x="56" y="96">a</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="166" />
<text x="56" y="166">b</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="236" />
<text x="56" y="236">c</text>
<rect class="autouse" width="112" height="40" x="0" y="286" />
<text x="56" y="306">autouse</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="376" />
<text x="56" y="376">d</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="446" />
<text x="56" y="446">e</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="516" />
<text x="56" y="516">f</text>
<ellipse class="fixture" rx="25" ry="25" cx="56" cy="586" />
<text x="56" y="586">g</text>
<rect class="test" width="110" height="50" x="1" y="631" />
<text x="56" y="656">test_order</text>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -18,8 +18,8 @@ class YamlFile(pytest.File):
class YamlItem(pytest.Item):
def __init__(self, name, parent, spec):
super().__init__(name, parent)
def __init__(self, *, spec, **kwargs):
super().__init__(**kwargs)
self.spec = spec
def runtest(self):

View File

@@ -22,7 +22,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
pytest 7.0.0rc1
pytest 7.0.1
.. _`simpletest`:

View File

@@ -139,7 +139,7 @@ project:
which adds ``pytest`` (rather than ``py.test``) as the recommended
command-line entry point
Due to this history, it's diffcult to answer the question when pytest was started.
Due to this history, it's difficult to answer the question when pytest was started.
It depends what point should really be seen as the start of it all. One
possible interpretation is to pick Europython 2004, i.e. around June/July
2004.

View File

@@ -42,8 +42,18 @@ Running pytest now produces this output:
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
======================= 1 passed, 1 warning in 0.12s =======================
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
them into errors:
Controlling warnings
--------------------
Similar to Python's `warning filter`_ and :option:`-W option <python:-W>` flag, pytest provides
its own ``-W`` flag to control which warnings are ignored, displayed, or turned into
errors. See the `warning filter`_ documentation for more
advanced use-cases.
.. _`warning filter`: https://docs.python.org/3/library/warnings.html#warning-filter
This code sample shows how to treat any ``UserWarning`` category class of warning
as an error:
.. code-block:: pytest
@@ -96,9 +106,6 @@ all other warnings into errors.
When a warning matches more than one option in the list, the action for the last matching option
is performed.
Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
:option:`-W option <python:-W>` and :func:`warnings.simplefilter`, so please refer to those sections in the Python
documentation for other examples and advanced usage.
.. _`filterwarnings`:
@@ -344,6 +351,37 @@ warnings, or index into it to get a particular recorded warning.
Full API: :class:`~_pytest.recwarn.WarningsRecorder`.
.. _`warns use cases`:
Additional use cases of warnings in tests
-----------------------------------------
Here are some use cases involving warnings that often come up in tests, and suggestions on how to deal with them:
- To ensure that **any** warning is emitted, use:
.. code-block:: python
with pytest.warns():
...
- To ensure that **no** warnings are emitted, use:
.. code-block:: python
with warnings.catch_warnings():
warnings.simplefilter("error")
...
- To suppress warnings, use:
.. code-block:: python
with warnings.catch_warnings():
warnings.simplefilter("ignore")
...
.. _custom_failure_messages:
Custom failure messages

View File

@@ -369,7 +369,7 @@ Here is a simple test file with the several usages:
Running it with the report-on-xfail option gives this output:
.. FIXME: Use $ instead of ! again to reenable regendoc once it's fixed:
.. FIXME: Use $ instead of ! again to re-enable regendoc once it's fixed:
https://github.com/pytest-dev/pytest/issues/8807
.. code-block:: pytest

View File

@@ -321,7 +321,7 @@ Plugins often need to store data on :class:`~pytest.Item`\s in one hook
implementation, and access it in another. One common solution is to just
assign some private attribute directly on the item, but type-checkers like
mypy frown upon this, and it may also cause conflicts with other plugins.
So pytest offers a better way to do this, :attr:`_pytest.nodes.Node.stash <item.stash>`.
So pytest offers a better way to do this, :attr:`item.stash <_pytest.nodes.Node.stash>`.
To use the "stash" in your plugins, first create "stash keys" somewhere at the
top level of your plugin:

View File

@@ -1,10 +1,11 @@
:orphan:
.. sidebar:: Next Open Trainings
..
.. sidebar:: Next Open Trainings
- `Professional Testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, February 1st to 3rd, 2022, Leipzig (Germany) and remote.
- `Professional Testing with Python <https://www.python-academy.com/courses/specialtopics/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, February 1st to 3rd, 2022, Leipzig (Germany) and remote.
Also see `previous talks and blogposts <talks.html>`_.
Also see `previous talks and blogposts <talks.html>`_.
.. _features:

View File

@@ -20,8 +20,7 @@ Configuration file formats
--------------------------
Many :ref:`pytest settings <ini options ref>` can be set in a *configuration file*, which
by convention resides on the root of your repository or in your
tests folder.
by convention resides in the root directory of your repository.
A quick example of the configuration files supported by pytest:

View File

@@ -208,7 +208,7 @@ the one defined in ``tests/test_top.py`` would be unavailable to it because it
would have to step down a level (step inside a circle) to find it.
The first fixture the test finds is the one that will be used, so
:ref:`fixtures can be overriden <override fixtures>` if you need to change or
:ref:`fixtures can be overridden <override fixtures>` if you need to change or
extend what one does for a particular scope.
You can also use the ``conftest.py`` file to implement
@@ -401,6 +401,9 @@ the graph would look like this:
Because ``c`` can now be put above ``d`` in the graph, pytest can once again
linearize the graph to this:
.. image:: /example/fixtures/test_fixtures_order_autouse_flat.*
:align: center
In this example, ``c`` makes ``b`` and ``a`` effectively autouse fixtures as
well.

View File

@@ -9,6 +9,39 @@ This page contains the full reference to pytest's API.
:depth: 3
:local:
Constants
---------
pytest.__version__
~~~~~~~~~~~~~~~~~~
The current pytest version, as a string::
>>> import pytest
>>> pytest.__version__
'7.0.0'
.. _`version-tuple`:
pytest.version_tuple
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 7.0
The current pytest version, as a tuple::
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)
For pre-releases, the last component will be a string with the prerelease version::
>>> import pytest
>>> pytest.version_tuple
(7, 0, '0rc1')
Functions
---------
@@ -226,37 +259,6 @@ Marks a test function as *expected to fail*.
a new release of a library fixes a known bug).
pytest.__version__
~~~~~~~~~~~~~~~~~~
The current pytest version, as a string::
>>> import pytest
>>> pytest.__version__
'7.0.0'
.. _`version-tuple`:
pytest.version_tuple
~~~~~~~~~~~~~~~~~~~~
.. versionadded:: 7.0
The current pytest version, as a tuple::
>>> import pytest
>>> pytest.version_tuple
(7, 0, 0)
For pre-releases, the last component will be a string with the prerelease version::
>>> import pytest
>>> pytest.version_tuple
(7, 0, '0rc1')
Custom marks
~~~~~~~~~~~~
@@ -671,9 +673,13 @@ Bootstrapping hooks
Bootstrapping hooks called for plugins registered early enough (internal and setuptools plugins).
.. hook:: pytest_load_initial_conftests
.. autofunction:: pytest_load_initial_conftests
.. hook:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_preparse
.. hook:: pytest_cmdline_parse
.. autofunction:: pytest_cmdline_parse
.. hook:: pytest_cmdline_main
.. autofunction:: pytest_cmdline_main
.. _`initialization-hooks`:
@@ -683,13 +689,20 @@ Initialization hooks
Initialization hooks called for plugins and ``conftest.py`` files.
.. hook:: pytest_addoption
.. autofunction:: pytest_addoption
.. hook:: pytest_addhooks
.. autofunction:: pytest_addhooks
.. hook:: pytest_configure
.. autofunction:: pytest_configure
.. hook:: pytest_unconfigure
.. autofunction:: pytest_unconfigure
.. hook:: pytest_sessionstart
.. autofunction:: pytest_sessionstart
.. hook:: pytest_sessionfinish
.. autofunction:: pytest_sessionfinish
.. hook:: pytest_plugin_registered
.. autofunction:: pytest_plugin_registered
Collection hooks
@@ -697,21 +710,34 @@ Collection hooks
``pytest`` calls the following hooks for collecting files and directories:
.. hook:: pytest_collection
.. autofunction:: pytest_collection
.. hook:: pytest_ignore_collect
.. autofunction:: pytest_ignore_collect
.. hook:: pytest_collect_file
.. autofunction:: pytest_collect_file
.. hook:: pytest_pycollect_makemodule
.. autofunction:: pytest_pycollect_makemodule
For influencing the collection of objects in Python modules
you can use the following hook:
.. hook:: pytest_pycollect_makeitem
.. autofunction:: pytest_pycollect_makeitem
.. hook:: pytest_generate_tests
.. autofunction:: pytest_generate_tests
.. hook:: pytest_make_parametrize_id
.. autofunction:: pytest_make_parametrize_id
Hooks for influencing test skipping:
.. hook:: pytest_markeval_namespace
.. autofunction:: pytest_markeval_namespace
After collection is complete, you can modify the order of
items, delete or otherwise amend the test items:
.. hook:: pytest_collection_modifyitems
.. autofunction:: pytest_collection_modifyitems
.. note::
@@ -725,13 +751,21 @@ Test running (runtest) hooks
All runtest related hooks receive a :py:class:`pytest.Item <pytest.Item>` object.
.. hook:: pytest_runtestloop
.. autofunction:: pytest_runtestloop
.. hook:: pytest_runtest_protocol
.. autofunction:: pytest_runtest_protocol
.. hook:: pytest_runtest_logstart
.. autofunction:: pytest_runtest_logstart
.. hook:: pytest_runtest_logfinish
.. autofunction:: pytest_runtest_logfinish
.. hook:: pytest_runtest_setup
.. autofunction:: pytest_runtest_setup
.. hook:: pytest_runtest_call
.. autofunction:: pytest_runtest_call
.. hook:: pytest_runtest_teardown
.. autofunction:: pytest_runtest_teardown
.. hook:: pytest_runtest_makereport
.. autofunction:: pytest_runtest_makereport
For deeper understanding you may look at the default implementation of
@@ -740,6 +774,7 @@ in ``_pytest.pdb`` which interacts with ``_pytest.capture``
and its input/output capturing in order to immediately drop
into interactive debugging when a test failure occurs.
.. hook:: pytest_pyfunc_call
.. autofunction:: pytest_pyfunc_call
Reporting hooks
@@ -747,27 +782,47 @@ Reporting hooks
Session related reporting hooks:
.. hook:: pytest_collectstart
.. autofunction:: pytest_collectstart
.. hook:: pytest_make_collect_report
.. autofunction:: pytest_make_collect_report
.. hook:: pytest_itemcollected
.. autofunction:: pytest_itemcollected
.. hook:: pytest_collectreport
.. autofunction:: pytest_collectreport
.. hook:: pytest_deselected
.. autofunction:: pytest_deselected
.. hook:: pytest_report_header
.. autofunction:: pytest_report_header
.. hook:: pytest_report_collectionfinish
.. autofunction:: pytest_report_collectionfinish
.. hook:: pytest_report_teststatus
.. autofunction:: pytest_report_teststatus
.. hook:: pytest_report_to_serializable
.. autofunction:: pytest_report_to_serializable
.. hook:: pytest_report_from_serializable
.. autofunction:: pytest_report_from_serializable
.. hook:: pytest_terminal_summary
.. autofunction:: pytest_terminal_summary
.. hook:: pytest_fixture_setup
.. autofunction:: pytest_fixture_setup
.. hook:: pytest_fixture_post_finalizer
.. autofunction:: pytest_fixture_post_finalizer
.. hook:: pytest_warning_captured
.. autofunction:: pytest_warning_captured
.. hook:: pytest_warning_recorded
.. autofunction:: pytest_warning_recorded
Central hook for reporting about test execution:
.. hook:: pytest_runtest_logreport
.. autofunction:: pytest_runtest_logreport
Assertion related hooks:
.. hook:: pytest_assertrepr_compare
.. autofunction:: pytest_assertrepr_compare
.. hook:: pytest_assertion_pass
.. autofunction:: pytest_assertion_pass
@@ -777,10 +832,16 @@ Debugging/Interaction hooks
There are few hooks which can be used for special
reporting or interaction with exceptions:
.. hook:: pytest_internalerror
.. autofunction:: pytest_internalerror
.. hook:: pytest_keyboard_interrupt
.. autofunction:: pytest_keyboard_interrupt
.. hook:: pytest_exception_interact
.. autofunction:: pytest_exception_interact
.. hook:: pytest_enter_pdb
.. autofunction:: pytest_enter_pdb
.. hook:: pytest_leave_pdb
.. autofunction:: pytest_leave_pdb
Objects

View File

@@ -88,7 +88,9 @@ def prepare_release_pr(
print(f"Branch {Fore.CYAN}{release_branch}{Fore.RESET} created.")
if prerelease:
if is_major:
template_name = "release.major.rst"
elif prerelease:
template_name = "release.pre.rst"
elif is_feature_release:
template_name = "release.minor.rst"
@@ -104,6 +106,7 @@ def prepare_release_pr(
"--",
version,
template_name,
release_branch, # doc_version
"--skip-check-links",
]
print("Running", " ".join(cmdline))

24
scripts/release.major.rst Normal file
View File

@@ -0,0 +1,24 @@
pytest-{version}
=======================================
The pytest team is proud to announce the {version} release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/stable/
As usual, you can upgrade from PyPI via:
pip install -U pytest
Thanks to all of the contributors to this release:
{contributors}
Happy testing,
The pytest Development Team

View File

@@ -3,8 +3,8 @@ pytest-{version}
The pytest team is proud to announce the {version} release!
This release contains new features, improvements, bug fixes, and breaking changes, so users
are encouraged to take a look at the CHANGELOG carefully:
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

View File

@@ -19,7 +19,7 @@ You can upgrade from PyPI via:
Users are encouraged to take a look at the CHANGELOG carefully:
https://docs.pytest.org/en/stable/changelog.html
https://docs.pytest.org/en/{doc_version}/changelog.html
Thanks to all the contributors to this release:

View File

@@ -10,7 +10,7 @@ from colorama import Fore
from colorama import init
def announce(version, template_name):
def announce(version, template_name, doc_version):
"""Generates a new release announcement entry in the docs."""
# Get our list of authors
stdout = check_output(["git", "describe", "--abbrev=0", "--tags"])
@@ -31,7 +31,9 @@ def announce(version, template_name):
)
contributors_text = "\n".join(f"* {name}" for name in sorted(contributors)) + "\n"
text = template_text.format(version=version, contributors=contributors_text)
text = template_text.format(
version=version, contributors=contributors_text, doc_version=doc_version
)
target = Path(__file__).parent.joinpath(f"../doc/en/announce/release-{version}.rst")
target.write_text(text, encoding="UTF-8")
@@ -82,9 +84,9 @@ def check_links():
check_call(["tox", "-e", "docs-checklinks"])
def pre_release(version, template_name, *, skip_check_links):
def pre_release(version, template_name, doc_version, *, skip_check_links):
"""Generates new docs, release announcements and creates a local tag."""
announce(version, template_name)
announce(version, template_name, doc_version)
regen(version)
changelog(version, write_out=True)
fix_formatting()
@@ -112,11 +114,15 @@ def main():
parser.add_argument(
"template_name", help="Name of template file to use for release announcement"
)
parser.add_argument(
"doc_version", help="For prereleases, the version to link to in the docs"
)
parser.add_argument("--skip-check-links", action="store_true", default=False)
options = parser.parse_args()
pre_release(
options.version,
options.template_name,
options.doc_version,
skip_check_links=options.skip_check_links,
)

View File

@@ -1,5 +1,6 @@
import ast
import inspect
import os
import re
import sys
import traceback
@@ -343,10 +344,10 @@ class Traceback(List[TracebackEntry]):
def cut(
self,
path: Optional[Union[Path, str]] = None,
path: Optional[Union["os.PathLike[str]", str]] = None,
lineno: Optional[int] = None,
firstlineno: Optional[int] = None,
excludepath: Optional[Path] = None,
excludepath: Optional["os.PathLike[str]"] = None,
) -> "Traceback":
"""Return a Traceback instance wrapping part of this Traceback.
@@ -357,15 +358,17 @@ class Traceback(List[TracebackEntry]):
for formatting reasons (removing some uninteresting bits that deal
with handling of the exception/traceback).
"""
path_ = None if path is None else os.fspath(path)
excludepath_ = None if excludepath is None else os.fspath(excludepath)
for x in self:
code = x.frame.code
codepath = code.path
if path is not None and codepath != path:
if path is not None and str(codepath) != path_:
continue
if (
excludepath is not None
and isinstance(codepath, Path)
and excludepath in codepath.parents
and excludepath_ in (str(p) for p in codepath.parents) # type: ignore[operator]
):
continue
if lineno is not None and x.lineno != lineno:
@@ -895,7 +898,7 @@ class FormattedExcinfo:
max_frames=max_frames,
total=len(traceback),
)
# Type ignored because adding two instaces of a List subtype
# Type ignored because adding two instances of a List subtype
# currently incorrectly has type List instead of the subtype.
traceback = traceback[:max_frames] + traceback[-max_frames:] # type: ignore
else:

View File

@@ -149,6 +149,11 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i
values: List[int] = []
for x in ast.walk(node):
if isinstance(x, (ast.stmt, ast.ExceptHandler)):
# Before Python 3.8, the lineno of a decorated class or function pointed at the decorator.
# Since Python 3.8, the lineno points to the class/def, so need to include the decorators.
if isinstance(x, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
for d in x.decorator_list:
values.append(d.lineno - 1)
values.append(x.lineno - 1)
for name in ("finalbody", "orelse"):
val: Optional[List[ast.stmt]] = getattr(x, name, None)

View File

@@ -276,13 +276,15 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
with open(pathname, "rb") as f:
return f.read()
if sys.version_info >= (3, 9):
if sys.version_info >= (3, 10):
def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore
from types import SimpleNamespace
from importlib.readers import FileReader
if sys.version_info < (3, 11):
from importlib.readers import FileReader
else:
from importlib.resources.readers import FileReader
return FileReader(SimpleNamespace(path=self._rewritten_names[name]))
return FileReader(types.SimpleNamespace(path=self._rewritten_names[name]))
def _write_pyc_fp(
@@ -514,7 +516,7 @@ def _call_assertion_pass(lineno: int, orig: str, expl: str) -> None:
def _check_if_assertion_pass_impl() -> bool:
"""Check if any plugins implement the pytest_assertion_pass hook
in order not to generate explanation unecessarily (might be expensive)."""
in order not to generate explanation unnecessarily (might be expensive)."""
return True if util._assertion_pass else False

View File

@@ -255,7 +255,7 @@ default_plugins = essential_plugins + (
"warnings",
"logging",
"reports",
"pythonpath",
"python_path",
*(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []),
"faulthandler",
)
@@ -331,6 +331,14 @@ def _prepareconfig(
raise
def _get_directory(path: Path) -> Path:
"""Get the directory of a path - itself if already a directory."""
if path.is_file():
return path.parent
else:
return path
@final
class PytestPluginManager(PluginManager):
"""A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with
@@ -353,6 +361,12 @@ class PytestPluginManager(PluginManager):
self._conftestpath2mod: Dict[Path, types.ModuleType] = {}
self._confcutdir: Optional[Path] = None
self._noconftest = False
# _getconftestmodules()'s call to _get_directory() causes a stat
# storm when it's called potentially thousands of times in a test
# session (#9478), often with the same path, so cache it.
self._get_directory = lru_cache(256)(_get_directory)
self._duplicatepaths: Set[Path] = set()
# plugins that were explicitly skipped with pytest.skip
@@ -530,15 +544,12 @@ class PytestPluginManager(PluginManager):
if self._noconftest:
return []
if path.is_file():
directory = path.parent
else:
directory = path
directory = self._get_directory(path)
# Optimization: avoid repeated searches in the same directory.
# Assumes always called with same importmode and rootpath.
existing_clist = self._dirpath2confmods.get(directory)
if existing_clist:
if existing_clist is not None:
return existing_clist
# XXX these days we may rather want to use config.rootpath
@@ -970,7 +981,7 @@ class Config:
def add_cleanup(self, func: Callable[[], None]) -> None:
"""Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure)."""
use (usually coinciding with pytest_unconfigure)."""
self._cleanup.append(func)
def _do_configure(self) -> None:

View File

@@ -23,7 +23,7 @@ class PathAwareHookProxy:
this helper wraps around hook callers
until pluggy supports fixingcalls, this one will do
it currently doesnt return full hook caller proxies for fixed hooks,
it currently doesn't return full hook caller proxies for fixed hooks,
this may have to be changed later depending on bugs
"""

View File

@@ -115,8 +115,10 @@ NODE_CTOR_FSPATH_ARG = UnformattedWarning(
)
WARNS_NONE_ARG = PytestRemovedIn8Warning(
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n"
" Replace pytest.warns(None) by simply pytest.warns()."
"Passing None has been deprecated.\n"
"See https://docs.pytest.org/en/latest/how-to/capture-warnings.html"
"#additional-use-cases-of-warnings-in-tests"
" for alternatives in common use cases."
)
KEYWORD_MSG_ARG = UnformattedWarning(

View File

@@ -163,7 +163,7 @@ def pytest_cmdline_preparse(config: "Config", args: List[str]) -> None:
"""(**Deprecated**) modify command line arguments before option parsing.
This hook is considered deprecated and will be removed in a future pytest version. Consider
using :func:`pytest_load_initial_conftests` instead.
using :hook:`pytest_load_initial_conftests` instead.
.. note::
This hook will not be called for ``conftest.py`` files, only for setuptools plugins.
@@ -467,7 +467,7 @@ def pytest_runtest_logstart(
) -> None:
"""Called at the start of running the runtest protocol for a single item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
:param str nodeid: Full node ID of the item.
:param location: A tuple of ``(filename, lineno, testname)``.
@@ -479,7 +479,7 @@ def pytest_runtest_logfinish(
) -> None:
"""Called at the end of running the runtest protocol for a single item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
:param str nodeid: Full node ID of the item.
:param location: A tuple of ``(filename, lineno, testname)``.
@@ -526,7 +526,7 @@ def pytest_runtest_makereport(
"""Called to create a :class:`~pytest.TestReport` for each of
the setup, call and teardown runtest phases of a test item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
:param call: The :class:`~pytest.CallInfo` for the phase.
@@ -538,7 +538,7 @@ def pytest_runtest_logreport(report: "TestReport") -> None:
"""Process the :class:`~pytest.TestReport` produced for each
of the setup, call and teardown runtest phases of an item.
See :func:`pytest_runtest_protocol` for a description of the runtest protocol.
See :hook:`pytest_runtest_protocol` for a description of the runtest protocol.
"""
@@ -557,7 +557,7 @@ def pytest_report_from_serializable(
data: Dict[str, Any],
) -> Optional[Union["CollectReport", "TestReport"]]:
"""Restore a report object previously serialized with
:func:`pytest_report_to_serializable`."""
:hook:`pytest_report_to_serializable`."""
# -------------------------------------------------------------------------
@@ -895,10 +895,10 @@ def pytest_exception_interact(
"""Called when an exception was raised which can potentially be
interactively handled.
May be called during collection (see :py:func:`pytest_make_collect_report`),
May be called during collection (see :hook:`pytest_make_collect_report`),
in which case ``report`` is a :class:`CollectReport`.
May be called during runtest of an item (see :py:func:`pytest_runtest_protocol`),
May be called during runtest of an item (see :hook:`pytest_runtest_protocol`),
in which case ``report`` is a :class:`TestReport`.
This hook is not called if the exception that was raised is an internal

View File

@@ -256,7 +256,7 @@ class _NodeReporter:
def finalize(self) -> None:
data = self.to_xml()
self.__dict__.clear()
# Type ignored becuase mypy doesn't like overriding a method.
# Type ignored because mypy doesn't like overriding a method.
# Also the return value doesn't match...
self.to_xml = lambda: data # type: ignore[assignment]

View File

@@ -10,13 +10,26 @@ from typing import Union
import attr
from iniconfig import SectionWrapper
import pytest
from _pytest.cacheprovider import Cache
from _pytest.compat import final
from _pytest.compat import LEGACY_PATH
from _pytest.compat import legacy_path
from _pytest.config import Config
from _pytest.config import hookimpl
from _pytest.config import PytestPluginManager
from _pytest.deprecated import check_ispytest
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.main import Session
from _pytest.monkeypatch import MonkeyPatch
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
from _pytest.pytester import HookRecorder
from _pytest.pytester import Pytester
from _pytest.pytester import RunResult
from _pytest.terminal import TerminalReporter
from _pytest.tmpdir import TempPathFactory
if TYPE_CHECKING:
from typing_extensions import Final
@@ -35,10 +48,10 @@ class Testdir:
__test__ = False
CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
TimeoutExpired: "Final" = Pytester.TimeoutExpired
def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
check_ispytest(_ispytest)
self._pytester = pytester
@@ -64,10 +77,10 @@ class Testdir:
self._pytester.plugins = plugins
@property
def monkeypatch(self) -> pytest.MonkeyPatch:
def monkeypatch(self) -> MonkeyPatch:
return self._pytester._monkeypatch
def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
def make_hook_recorder(self, pluginmanager) -> HookRecorder:
"""See :meth:`Pytester.make_hook_recorder`."""
return self._pytester.make_hook_recorder(pluginmanager)
@@ -131,9 +144,7 @@ class Testdir:
"""See :meth:`Pytester.copy_example`."""
return legacy_path(self._pytester.copy_example(name))
def getnode(
self, config: pytest.Config, arg
) -> Optional[Union[pytest.Item, pytest.Collector]]:
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
"""See :meth:`Pytester.getnode`."""
return self._pytester.getnode(config, arg)
@@ -141,9 +152,7 @@ class Testdir:
"""See :meth:`Pytester.getpathnode`."""
return self._pytester.getpathnode(path)
def genitems(
self, colitems: List[Union[pytest.Item, pytest.Collector]]
) -> List[pytest.Item]:
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
"""See :meth:`Pytester.genitems`."""
return self._pytester.genitems(colitems)
@@ -165,19 +174,19 @@ class Testdir:
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
)
def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
"""See :meth:`Pytester.runpytest_inprocess`."""
return self._pytester.runpytest_inprocess(*args, **kwargs)
def runpytest(self, *args, **kwargs) -> pytest.RunResult:
def runpytest(self, *args, **kwargs) -> RunResult:
"""See :meth:`Pytester.runpytest`."""
return self._pytester.runpytest(*args, **kwargs)
def parseconfig(self, *args) -> pytest.Config:
def parseconfig(self, *args) -> Config:
"""See :meth:`Pytester.parseconfig`."""
return self._pytester.parseconfig(*args)
def parseconfigure(self, *args) -> pytest.Config:
def parseconfigure(self, *args) -> Config:
"""See :meth:`Pytester.parseconfigure`."""
return self._pytester.parseconfigure(*args)
@@ -196,8 +205,8 @@ class Testdir:
)
def collect_by_name(
self, modcol: pytest.Collector, name: str
) -> Optional[Union[pytest.Item, pytest.Collector]]:
self, modcol: Collector, name: str
) -> Optional[Union[Item, Collector]]:
"""See :meth:`Pytester.collect_by_name`."""
return self._pytester.collect_by_name(modcol, name)
@@ -212,11 +221,11 @@ class Testdir:
"""See :meth:`Pytester.popen`."""
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
"""See :meth:`Pytester.run`."""
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
def runpython(self, script) -> pytest.RunResult:
def runpython(self, script) -> RunResult:
"""See :meth:`Pytester.runpython`."""
return self._pytester.runpython(script)
@@ -224,7 +233,7 @@ class Testdir:
"""See :meth:`Pytester.runpython_c`."""
return self._pytester.runpython_c(command)
def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
"""See :meth:`Pytester.runpytest_subprocess`."""
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
@@ -245,13 +254,10 @@ class Testdir:
return str(self.tmpdir)
pytest.Testdir = Testdir # type: ignore[attr-defined]
class LegacyTestdirPlugin:
@staticmethod
@pytest.fixture
def testdir(pytester: pytest.Pytester) -> Testdir:
@fixture
def testdir(pytester: Pytester) -> Testdir:
"""
Identical to :fixture:`pytester`, and provides an instance whose methods return
legacy ``LEGACY_PATH`` objects instead when applicable.
@@ -267,10 +273,10 @@ class TempdirFactory:
"""Backward compatibility wrapper that implements :class:``_pytest.compat.LEGACY_PATH``
for :class:``TempPathFactory``."""
_tmppath_factory: pytest.TempPathFactory
_tmppath_factory: TempPathFactory
def __init__(
self, tmppath_factory: pytest.TempPathFactory, *, _ispytest: bool = False
self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
) -> None:
check_ispytest(_ispytest)
self._tmppath_factory = tmppath_factory
@@ -284,19 +290,16 @@ class TempdirFactory:
return legacy_path(self._tmppath_factory.getbasetemp().resolve())
pytest.TempdirFactory = TempdirFactory # type: ignore[attr-defined]
class LegacyTmpdirPlugin:
@staticmethod
@pytest.fixture(scope="session")
def tmpdir_factory(request: pytest.FixtureRequest) -> TempdirFactory:
@fixture(scope="session")
def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
"""Return a :class:`pytest.TempdirFactory` instance for the test session."""
# Set dynamically by pytest_configure().
return request.config._tmpdirhandler # type: ignore
@staticmethod
@pytest.fixture
@fixture
def tmpdir(tmp_path: Path) -> LEGACY_PATH:
"""Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
@@ -314,7 +317,7 @@ class LegacyTmpdirPlugin:
return legacy_path(tmp_path)
def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH:
"""Return a directory path object with the given name.
Same as :func:`mkdir`, but returns a legacy py path instance.
@@ -322,7 +325,7 @@ def Cache_makedir(self: pytest.Cache, name: str) -> LEGACY_PATH:
return legacy_path(self.mkdir(name))
def FixtureRequest_fspath(self: pytest.FixtureRequest) -> LEGACY_PATH:
def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH:
"""(deprecated) The file system path of the test module which collected this test."""
return legacy_path(self.path)
@@ -337,7 +340,7 @@ def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH:
return legacy_path(self.startpath)
def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
def Config_invocation_dir(self: Config) -> LEGACY_PATH:
"""The directory from which pytest was invoked.
Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
@@ -348,7 +351,7 @@ def Config_invocation_dir(self: pytest.Config) -> LEGACY_PATH:
return legacy_path(str(self.invocation_params.dir))
def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
def Config_rootdir(self: Config) -> LEGACY_PATH:
"""The path to the :ref:`rootdir <rootdir>`.
Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
@@ -358,7 +361,7 @@ def Config_rootdir(self: pytest.Config) -> LEGACY_PATH:
return legacy_path(str(self.rootpath))
def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
def Config_inifile(self: Config) -> Optional[LEGACY_PATH]:
"""The path to the :ref:`configfile <configfiles>`.
Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
@@ -368,7 +371,7 @@ def Config_inifile(self: pytest.Config) -> Optional[LEGACY_PATH]:
return legacy_path(str(self.inipath)) if self.inipath else None
def Session_stardir(self: pytest.Session) -> LEGACY_PATH:
def Session_stardir(self: Session) -> LEGACY_PATH:
"""The path from which pytest was invoked.
Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
@@ -400,12 +403,44 @@ def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:
self.path = Path(value)
@pytest.hookimpl
def pytest_configure(config: pytest.Config) -> None:
mp = pytest.MonkeyPatch()
config.add_cleanup(mp.undo)
@hookimpl(tryfirst=True)
def pytest_load_initial_conftests(early_config: Config) -> None:
"""Monkeypatch legacy path attributes in several classes, as early as possible."""
mp = MonkeyPatch()
early_config.add_cleanup(mp.undo)
# Add Cache.makedir().
mp.setattr(Cache, "makedir", Cache_makedir, raising=False)
# Add FixtureRequest.fspath property.
mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False)
# Add TerminalReporter.startdir property.
mp.setattr(
TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
)
# Add Config.{invocation_dir,rootdir,inifile} properties.
mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False)
mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False)
mp.setattr(Config, "inifile", property(Config_inifile), raising=False)
# Add Session.startdir property.
mp.setattr(Session, "startdir", property(Session_stardir), raising=False)
# Add pathlist configuration type.
mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type)
# Add Node.fspath property.
mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
@hookimpl
def pytest_configure(config: Config) -> None:
"""Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed."""
if config.pluginmanager.has_plugin("tmpdir"):
mp = MonkeyPatch()
config.add_cleanup(mp.undo)
# Create TmpdirFactory and attach it to the config object.
#
# This is to comply with existing plugins which expect the handler to be
@@ -422,40 +457,9 @@ def pytest_configure(config: pytest.Config) -> None:
config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir")
# Add Cache.makedir().
mp.setattr(pytest.Cache, "makedir", Cache_makedir, raising=False)
# Add FixtureRequest.fspath property.
mp.setattr(
pytest.FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False
)
# Add TerminalReporter.startdir property.
mp.setattr(
TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
)
# Add Config.{invocation_dir,rootdir,inifile} properties.
mp.setattr(
pytest.Config, "invocation_dir", property(Config_invocation_dir), raising=False
)
mp.setattr(pytest.Config, "rootdir", property(Config_rootdir), raising=False)
mp.setattr(pytest.Config, "inifile", property(Config_inifile), raising=False)
# Add Session.startdir property.
mp.setattr(pytest.Session, "startdir", property(Session_stardir), raising=False)
# Add pathlist configuration type.
mp.setattr(pytest.Config, "_getini_unknown_type", Config__getini_unknown_type)
# Add Node.fspath property.
mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
@pytest.hookimpl
def pytest_plugin_registered(
plugin: object, manager: pytest.PytestPluginManager
) -> None:
@hookimpl
def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None:
# pytester is not loaded by default and is commonly loaded from a conftest,
# so checking for it in `pytest_configure` is not enough.
is_pytester = plugin is manager.get_plugin("pytester")

View File

@@ -605,8 +605,7 @@ class Session(nodes.FSCollector):
) -> Sequence[Union[nodes.Item, nodes.Collector]]:
"""Perform the collection phase for this session.
This is called by the default
:func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook
This is called by the default :hook:`pytest_collection` hook
implementation; see the documentation of this hook for more details.
For testing purposes, it may also be called directly on a fresh
``Session``.
@@ -872,7 +871,10 @@ def resolve_collection_argument(
If the path doesn't exist, raise UsageError.
If the path is a directory and selection parts are present, raise UsageError.
"""
strpath, *parts = str(arg).split("::")
base, squacket, rest = str(arg).partition("[")
strpath, *parts = base.split("::")
if parts:
parts[-1] = f"{parts[-1]}{squacket}{rest}"
if as_pypath:
strpath = search_pypath(strpath)
fspath = invocation_path / strpath

View File

@@ -190,7 +190,7 @@ class MatcherAdapter(Mapping[str, bool]):
class Expression:
"""A compiled match expression as used by -k and -m.
The expression can be evaulated against different matchers.
The expression can be evaluated against different matchers.
"""
__slots__ = ("code",)

View File

@@ -145,7 +145,10 @@ class NodeMeta(type):
warnings.warn(
PytestDeprecationWarning(
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}"
f"{self} is not using a cooperative constructor and only takes {set(known_kw)}.\n"
"See https://docs.pytest.org/en/stable/deprecations.html"
"#constructors-of-custom-pytest-node-subclasses-should-take-kwargs "
"for more details."
)
)
@@ -653,20 +656,6 @@ class Item(Node):
nextitem = None
def __init_subclass__(cls) -> None:
problems = ", ".join(
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
)
if problems:
warnings.warn(
f"{cls.__name__} is an Item subclass and should not be a collector, "
f"however its bases {problems} are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n"
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
PytestWarning,
)
def __init__(
self,
name,
@@ -694,6 +683,37 @@ class Item(Node):
#: for this test.
self.user_properties: List[Tuple[str, object]] = []
self._check_item_and_collector_diamond_inheritance()
def _check_item_and_collector_diamond_inheritance(self) -> None:
"""
Check if the current type inherits from both File and Collector
at the same time, emitting a warning accordingly (#8447).
"""
cls = type(self)
# We inject an attribute in the type to avoid issuing this warning
# for the same class more than once, which is not helpful.
# It is a hack, but was deemed acceptable in order to avoid
# flooding the user in the common case.
attr_name = "_pytest_diamond_inheritance_warning_shown"
if getattr(cls, attr_name, False):
return
setattr(cls, attr_name, True)
problems = ", ".join(
base.__name__ for base in cls.__bases__ if issubclass(base, Collector)
)
if problems:
warnings.warn(
f"{cls.__name__} is an Item subclass and should not be a collector, "
f"however its bases {problems} are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n"
"Pytest Doc example: https://docs.pytest.org/en/latest/example/nonpython.html\n"
"example pull request on a plugin: https://github.com/asmeurer/pytest-flakes/pull/40/",
PytestWarning,
)
def runtest(self) -> None:
"""Run the test case for this item.

View File

@@ -596,11 +596,15 @@ class RunResult:
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
warnings: int = 0,
deselected: int = 0,
warnings: Optional[int] = None,
deselected: Optional[int] = None,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
"""
Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run.
``warnings`` and ``deselected`` are only checked if not None.
"""
__tracebackhide__ = True
from _pytest.pytester_assertions import assert_outcomes
@@ -1288,7 +1292,7 @@ class Pytester:
) -> Optional[Union[Item, Collector]]:
"""Return the collection node for name from the module collection.
Searchs a module collection node for a collection node matching the
Searches a module collection node for a collection node matching the
given name.
:param modcol: A module collection node; see :py:meth:`getmodulecol`.

View File

@@ -4,6 +4,7 @@
# hence cannot be subject to assertion rewriting, which requires a
# module to not be already imported.
from typing import Dict
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Union
@@ -42,8 +43,8 @@ def assert_outcomes(
errors: int = 0,
xpassed: int = 0,
xfailed: int = 0,
warnings: int = 0,
deselected: int = 0,
warnings: Optional[int] = None,
deselected: Optional[int] = None,
) -> None:
"""Assert that the specified outcomes appear with the respective
numbers (0 means it didn't occur) in the text output from a test run."""
@@ -56,8 +57,6 @@ def assert_outcomes(
"errors": outcomes.get("errors", 0),
"xpassed": outcomes.get("xpassed", 0),
"xfailed": outcomes.get("xfailed", 0),
"warnings": outcomes.get("warnings", 0),
"deselected": outcomes.get("deselected", 0),
}
expected = {
"passed": passed,
@@ -66,7 +65,11 @@ def assert_outcomes(
"errors": errors,
"xpassed": xpassed,
"xfailed": xfailed,
"warnings": warnings,
"deselected": deselected,
}
if warnings is not None:
obtained["warnings"] = outcomes.get("warnings", 0)
expected["warnings"] = warnings
if deselected is not None:
obtained["deselected"] = outcomes.get("deselected", 0)
expected["deselected"] = deselected
assert obtained == expected

View File

@@ -278,6 +278,16 @@ class PyobjMixin(nodes.Node):
node = self.getparent(Class)
return node.obj if node is not None else None
@property
def instance(self):
"""Python instance object the function is bound to.
Returns None if not a test method, e.g. for a standalone test function,
a staticmethod, a class or a module.
"""
node = self.getparent(Function)
return getattr(node.obj, "__self__", None) if node is not None else None
@property
def obj(self):
"""Underlying Python object."""
@@ -517,12 +527,18 @@ class Module(nodes.File, PyCollector):
self.obj, ("setUpModule", "setup_module")
)
if setup_module is None and has_nose:
# The name "setup" is too common - only treat as fixture if callable.
setup_module = _get_first_non_fixture_func(self.obj, ("setup",))
if not callable(setup_module):
setup_module = None
teardown_module = _get_first_non_fixture_func(
self.obj, ("tearDownModule", "teardown_module")
)
if teardown_module is None and has_nose:
teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",))
# Same as "setup" above - only treat as fixture if callable.
if not callable(teardown_module):
teardown_module = None
if setup_module is None and teardown_module is None:
return
@@ -980,7 +996,7 @@ class CallSpec2:
@final
class Metafunc:
"""Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
"""Objects passed to the :hook:`pytest_generate_tests` hook.
They help to inspect a test function and to generate tests according to
test configuration or values specified in the class or module where a
@@ -1644,7 +1660,7 @@ class Function(PyobjMixin, nodes.Item):
# this will be redeemed later
for mark in callspec.marks:
# feel free to cry, this was broken for years before
# and keywords cant fix it per design
# and keywords can't fix it per design
self.keywords[mark.name] = mark
self.own_markers.extend(normalize_mark_list(callspec.marks))
if keywords:
@@ -1683,15 +1699,6 @@ class Function(PyobjMixin, nodes.Item):
"""Underlying python 'function' object."""
return getimfunc(self.obj)
@property
def instance(self):
"""Python instance object the function is bound to.
Returns None if not a test method, e.g. for a standalone test function
or a staticmethod.
"""
return getattr(self.obj, "__self__", None)
def _getobj(self):
assert self.parent is not None
if isinstance(self.parent, Class):

View File

@@ -101,6 +101,7 @@ class ApproxBase:
)
def __bool__(self):
__tracebackhide__ = True
raise AssertionError(
"approx() is not supported in a boolean context.\nDid you mean: `assert a == approx(b)`?"
)

View File

@@ -185,6 +185,15 @@ class TestCaseFunction(Function):
_excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None
_testcase: Optional["unittest.TestCase"] = None
def _getobj(self):
assert self.parent is not None
# Unlike a regular Function in a Class, where `item.obj` returns
# a *bound* method (attached to an instance), TestCaseFunction's
# `obj` returns an *unbound* method (not attached to an instance).
# This inconsistency is probably not desirable, but needs some
# consideration before changing.
return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined]
def setup(self) -> None:
# A bound method to be called during teardown() if set (see 'runtest()').
self._explicit_tearDown: Optional[Callable[[], None]] = None

View File

@@ -25,6 +25,8 @@ from _pytest.fixtures import FixtureLookupError
from _pytest.fixtures import FixtureRequest
from _pytest.fixtures import yield_fixture
from _pytest.freeze_support import freeze_includes
from _pytest.legacypath import TempdirFactory
from _pytest.legacypath import Testdir
from _pytest.logging import LogCaptureFixture
from _pytest.main import Session
from _pytest.mark import Mark
@@ -148,7 +150,9 @@ __all__ = [
"Stash",
"StashKey",
"version_tuple",
"TempdirFactory",
"TempPathFactory",
"Testdir",
"TestReport",
"UsageError",
"WarningsRecorder",

View File

@@ -1281,7 +1281,7 @@ def test_tee_stdio_captures_and_live_prints(pytester: Pytester) -> None:
reason="Windows raises `OSError: [Errno 22] Invalid argument` instead",
)
def test_no_brokenpipeerror_message(pytester: Pytester) -> None:
"""Ensure that the broken pipe error message is supressed.
"""Ensure that the broken pipe error message is suppressed.
In some Python versions, it reaches sys.unraisablehook, in others
a BrokenPipeError exception is propagated, but either way it prints

View File

@@ -484,7 +484,7 @@ def test_source_with_decorator() -> None:
src = inspect.getsource(deco_fixture)
assert src == " @pytest.fixture\n def deco_fixture():\n assert False\n"
# currenly Source does not unwrap decorators, testing the
# currently Source does not unwrap decorators, testing the
# existing behavior here for explicitness, but perhaps we should revisit/change this
# in the future
assert str(Source(deco_fixture)).startswith("@functools.wraps(function)")
@@ -618,6 +618,19 @@ def something():
assert str(source) == "def func(): raise ValueError(42)"
def test_decorator() -> None:
s = """\
def foo(f):
pass
@foo
def bar():
pass
"""
source = getstatement(3, s)
assert "@foo" in str(source)
def XXX_test_expression_multiline() -> None:
source = """\
something

View File

@@ -194,8 +194,10 @@ def test_warns_none_is_deprecated():
with pytest.warns(
PytestDeprecationWarning,
match=re.escape(
"Passing None to catch any warning has been deprecated, pass no arguments instead:\n "
"Replace pytest.warns(None) by simply pytest.warns()."
"Passing None has been deprecated.\n"
"See https://docs.pytest.org/en/latest/how-to/capture-warnings.html"
"#additional-use-cases-of-warnings-in-tests"
" for alternatives in common use cases."
),
):
with pytest.warns(None): # type: ignore[call-overload]

View File

@@ -1616,7 +1616,7 @@ def test_raise_unprintable_assertion_error(pytester: Pytester) -> None:
)
def test_raise_assertion_error_raisin_repr(pytester: Pytester) -> None:
def test_raise_assertion_error_raising_repr(pytester: Pytester) -> None:
pytester.makepyfile(
"""
class RaisingRepr(object):
@@ -1627,9 +1627,15 @@ def test_raise_assertion_error_raisin_repr(pytester: Pytester) -> None:
"""
)
result = pytester.runpytest()
result.stdout.fnmatch_lines(
["E AssertionError: <unprintable AssertionError object>"]
)
if sys.version_info >= (3, 11):
# python 3.11 has native support for un-str-able exceptions
result.stdout.fnmatch_lines(
["E AssertionError: <exception str() failed>"]
)
else:
result.stdout.fnmatch_lines(
["E AssertionError: <unprintable AssertionError object>"]
)
def test_issue_1944(pytester: Pytester) -> None:

View File

@@ -1123,6 +1123,28 @@ class TestAssertionRewriteHookDetails:
assert _read_pyc(source, pyc) is None # no error
def test_read_pyc_success(self, tmp_path: Path, pytester: Pytester) -> None:
"""
Ensure that the _rewrite_test() -> _write_pyc() produces a pyc file
that can be properly read with _read_pyc()
"""
from _pytest.assertion import AssertionState
from _pytest.assertion.rewrite import _read_pyc
from _pytest.assertion.rewrite import _rewrite_test
from _pytest.assertion.rewrite import _write_pyc
config = pytester.parseconfig()
state = AssertionState(config, "rewrite")
fn = tmp_path / "source.py"
pyc = Path(str(fn) + "c")
fn.write_text("def test(): assert True")
source_stat, co = _rewrite_test(fn, config)
_write_pyc(state, co, source_stat, pyc)
assert _read_pyc(fn, pyc, state.trace) is not None
@pytest.mark.skipif(
sys.version_info < (3, 7), reason="Only the Python 3.7 format for simplicity"
)

View File

@@ -64,7 +64,7 @@ class TestCollector:
assert pytester.collect_by_name(modcol, "doesnotexist") is None
def test_getparent(self, pytester: Pytester) -> None:
def test_getparent_and_accessors(self, pytester: Pytester) -> None:
modcol = pytester.getmodulecol(
"""
class TestClass:
@@ -77,14 +77,21 @@ class TestCollector:
fn = pytester.collect_by_name(cls, "test_foo")
assert isinstance(fn, pytest.Function)
module_parent = fn.getparent(pytest.Module)
assert module_parent is modcol
assert fn.getparent(pytest.Module) is modcol
assert modcol.module is not None
assert modcol.cls is None
assert modcol.instance is None
function_parent = fn.getparent(pytest.Function)
assert function_parent is fn
assert fn.getparent(pytest.Class) is cls
assert cls.module is not None
assert cls.cls is not None
assert cls.instance is None
class_parent = fn.getparent(pytest.Class)
assert class_parent is cls
assert fn.getparent(pytest.Function) is fn
assert fn.module is not None
assert fn.cls is not None
assert fn.instance is not None
assert fn.function is not None
def test_getcustomfile_roundtrip(self, pytester: Pytester) -> None:
hello = pytester.makefile(".xxx", hello="world")

View File

@@ -1,4 +1,5 @@
import enum
import sys
from functools import partial
from functools import wraps
from typing import TYPE_CHECKING
@@ -91,6 +92,7 @@ def test_get_real_func_partial() -> None:
assert get_real_func(partial(foo)) is foo
@pytest.mark.skipif(sys.version_info >= (3, 11), reason="couroutine removed")
def test_is_generator_asyncio(pytester: Pytester) -> None:
pytester.makepyfile(
"""
@@ -156,11 +158,11 @@ class ErrorsHelper:
@property
def raise_exception(self):
raise Exception("exception should be catched")
raise Exception("exception should be caught")
@property
def raise_fail_outcome(self):
pytest.fail("fail should be catched")
pytest.fail("fail should be caught")
def test_helper_failures() -> None:

View File

@@ -386,7 +386,7 @@ class TestParseIni:
pytest.param(
"""
[some_other_header]
required_plugins = wont be triggered
required_plugins = won't be triggered
[pytest]
""",
"1.5",
@@ -807,7 +807,7 @@ class TestConfigAPI:
with pytest.raises(pytest.UsageError, match=exp_match):
pytester.parseconfig("--confcutdir", pytester.path.joinpath("file"))
with pytest.raises(pytest.UsageError, match=exp_match):
pytester.parseconfig("--confcutdir", pytester.path.joinpath("inexistant"))
pytester.parseconfig("--confcutdir", pytester.path.joinpath("nonexistent"))
p = pytester.mkdir("dir")
config = pytester.parseconfig("--confcutdir", p)
@@ -1264,15 +1264,21 @@ def test_load_initial_conftest_last_ordering(_config_for_test):
m = My()
pm.register(m)
hc = pm.hook.pytest_load_initial_conftests
values = hc._nonwrappers + hc._wrappers
expected = [
"_pytest.config",
m.__module__,
"_pytest.pythonpath",
"_pytest.capture",
"_pytest.warnings",
hookimpls = [
(
hookimpl.function.__module__,
"wrapper" if hookimpl.hookwrapper else "nonwrapper",
)
for hookimpl in hc.get_hookimpls()
]
assert hookimpls == [
("_pytest.config", "nonwrapper"),
(m.__module__, "nonwrapper"),
("_pytest.legacypath", "nonwrapper"),
("_pytest.python_path", "nonwrapper"),
("_pytest.capture", "wrapper"),
("_pytest.warnings", "wrapper"),
]
assert [x.function.__module__ for x in values] == expected
def test_get_plugin_specs_as_list() -> None:

View File

@@ -1,4 +1,5 @@
import inspect
import sys
import textwrap
from pathlib import Path
from typing import Callable
@@ -80,7 +81,7 @@ class TestDoctests:
'# Empty'
def my_func():
">>> magic = 42 "
def unuseful():
def useless():
'''
# This is a function
# >>> # it doesn't have any doctest
@@ -200,6 +201,7 @@ class TestDoctests:
"Traceback (most recent call last):",
' File "*/doctest.py", line *, in __run',
" *",
*((" *^^^^*",) if sys.version_info >= (3, 11) else ()),
' File "<doctest test_doctest_unexpected_exception.txt[1]>", line 1, in <module>',
"ZeroDivisionError: division by zero",
"*/test_doctest_unexpected_exception.txt:2: UnexpectedException",
@@ -564,7 +566,7 @@ class TestDoctests:
>>> magic - 42
0
'''
def unuseful():
def useless():
pass
def another():
'''
@@ -802,11 +804,12 @@ class TestDoctests:
p = pytester.makepyfile(
setup="""
from setuptools import setup, find_packages
setup(name='sample',
version='0.0',
description='description',
packages=find_packages()
)
if __name__ == '__main__':
setup(name='sample',
version='0.0',
description='description',
packages=find_packages()
)
"""
)
result = pytester.runpytest(p, "--doctest-modules")

View File

@@ -161,3 +161,20 @@ def test_override_ini_paths(pytester: pytest.Pytester) -> None:
)
result = pytester.runpytest("--override-ini", "paths=foo/bar1.py foo/bar2.py", "-s")
result.stdout.fnmatch_lines(["user_path:bar1.py", "user_path:bar2.py"])
def test_inifile_from_cmdline_main_hook(pytester: pytest.Pytester) -> None:
"""Ensure Config.inifile is available during pytest_cmdline_main (#9396)."""
p = pytester.makeini(
"""
[pytest]
"""
)
pytester.makeconftest(
"""
def pytest_cmdline_main(config):
print("pytest_cmdline_main inifile =", config.inifile)
"""
)
result = pytester.runpytest_subprocess("-s")
result.stdout.fnmatch_lines(f"*pytest_cmdline_main inifile = {p}")

View File

@@ -1,6 +1,7 @@
import argparse
import os
import re
import sys
from pathlib import Path
from typing import Optional
@@ -44,16 +45,32 @@ def test_wrap_session_notify_exception(ret_exc, pytester: Pytester) -> None:
assert result.ret == ExitCode.INTERNAL_ERROR
assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):"
end_lines = (
result.stdout.lines[-4:]
if sys.version_info >= (3, 11)
else result.stdout.lines[-3:]
)
if exc == SystemExit:
assert result.stdout.lines[-3:] == [
assert end_lines == [
f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart',
'INTERNALERROR> raise SystemExit("boom")',
*(
("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",)
if sys.version_info >= (3, 11)
else ()
),
"INTERNALERROR> SystemExit: boom",
]
else:
assert result.stdout.lines[-3:] == [
assert end_lines == [
f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart',
'INTERNALERROR> raise ValueError("boom")',
*(
("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",)
if sys.version_info >= (3, 11)
else ()
),
"INTERNALERROR> ValueError: boom",
]
if returncode is False:
@@ -171,6 +188,12 @@ class TestResolveCollectionArgument:
invocation_path, "pkg::foo::bar", as_pypath=True
)
def test_parametrized_name_with_colons(self, invocation_path: Path) -> None:
ret = resolve_collection_argument(
invocation_path, "src/pkg/test.py::test[a::b]"
)
assert ret == (invocation_path / "src/pkg/test.py", ["test[a::b]"])
def test_does_not_exist(self, invocation_path: Path) -> None:
"""Given a file/module that does not exist raises UsageError."""
with pytest.raises(

View File

@@ -1,3 +1,5 @@
import re
import warnings
from pathlib import Path
from typing import cast
from typing import List
@@ -58,30 +60,31 @@ def test_subclassing_both_item_and_collector_deprecated(
request, tmp_path: Path
) -> None:
"""
Verifies we warn on diamond inheritance
as well as correctly managing legacy inheritance ctors with missing args
as found in plugins
Verifies we warn on diamond inheritance as well as correctly managing legacy
inheritance constructors with missing args as found in plugins.
"""
with pytest.warns(
PytestWarning,
match=(
"(?m)SoWrong is an Item subclass and should not be a collector, however its bases File are collectors.\n"
"Please split the Collectors and the Item into separate node types.\n.*"
),
):
# We do not expect any warnings messages to issued during class definition.
with warnings.catch_warnings():
warnings.simplefilter("error")
class SoWrong(nodes.Item, nodes.File):
def __init__(self, fspath, parent):
"""Legacy ctor with legacy call # don't wana see"""
super().__init__(fspath, parent)
with pytest.warns(
PytestWarning, match=".*SoWrong.* not using a cooperative constructor.*"
):
with pytest.warns(PytestWarning) as rec:
SoWrong.from_parent(
request.session, fspath=legacy_path(tmp_path / "broken.txt")
)
messages = [str(x.message) for x in rec]
assert any(
re.search(".*SoWrong.* not using a cooperative constructor.*", x)
for x in messages
)
assert any(
re.search("(?m)SoWrong .* should not be a collector", x) for x in messages
)
@pytest.mark.parametrize(

View File

@@ -477,3 +477,22 @@ def test_raises(pytester: Pytester) -> None:
"* 1 failed, 2 passed *",
]
)
def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None:
"""Regression test for #9391."""
p = pytester.makepyfile(
__init__="",
setup="""
""",
teardown="""
""",
test_it="""
from . import setup, teardown
def test_it():
pass
""",
)
result = pytester.runpytest(p, "-p", "nose")
assert result.ret == 0

View File

@@ -743,8 +743,8 @@ def test_run_result_repr() -> None:
# known exit code
r = pytester_mod.RunResult(1, outlines, errlines, duration=0.5)
assert (
repr(r) == "<RunResult ret=ExitCode.TESTS_FAILED len(stdout.lines)=3"
assert repr(r) == (
f"<RunResult ret={str(pytest.ExitCode.TESTS_FAILED)} len(stdout.lines)=3"
" len(stderr.lines)=4 duration=0.50s>"
)
@@ -835,6 +835,8 @@ def test_pytester_assert_outcomes_warnings(pytester: Pytester) -> None:
)
result = pytester.runpytest()
result.assert_outcomes(passed=1, warnings=1)
# If warnings is not passed, it is not checked at all.
result.assert_outcomes(passed=1)
def test_pytester_outcomes_deselected(pytester: Pytester) -> None:
@@ -849,3 +851,5 @@ def test_pytester_outcomes_deselected(pytester: Pytester) -> None:
)
result = pytester.runpytest("-k", "test_one")
result.assert_outcomes(passed=1, deselected=1)
# If deselected is not passed, it is not checked at all.
result.assert_outcomes(passed=1)

View File

@@ -81,7 +81,7 @@ def test_no_ini(pytester: Pytester, file_structure) -> None:
def test_clean_up(pytester: Pytester) -> None:
"""Test that the pythonpath plugin cleans up after itself."""
"""Test that the plugin cleans up after itself."""
# This is tough to test behaviorly because the cleanup really runs last.
# So the test make several implementation assumptions:
# - Cleanup is done in pytest_unconfigure().

View File

@@ -287,7 +287,7 @@ class BaseFunctionalTests:
def test_func():
import sys
# on python2 exc_info is keept till a function exits
# on python2 exc_info is kept till a function exits
# so we would end up calling test functions while
# sys.exc_info would return the indexerror
# from guessing the lastitem

View File

@@ -1143,8 +1143,6 @@ def test_errors_in_xfail_skip_expressions(pytester: Pytester) -> None:
pypy_version_info = getattr(sys, "pypy_version_info", None)
if pypy_version_info is not None and pypy_version_info < (6,):
markline = markline[5:]
elif sys.version_info[:2] >= (3, 10):
markline = markline[11:]
elif sys.version_info >= (3, 8) or hasattr(sys, "pypy_version_info"):
markline = markline[4:]

View File

@@ -56,7 +56,7 @@ def test_stash() -> None:
with pytest.raises(AttributeError):
stash.foo = "nope" # type: ignore[attr-defined]
# No interaction with anoter stash.
# No interaction with another stash.
stash2 = Stash()
key3 = StashKey[int]()
assert key2 not in stash2

View File

@@ -1472,3 +1472,29 @@ def test_do_cleanups_on_teardown_failure(pytester: Pytester) -> None:
passed, skipped, failed = reprec.countoutcomes()
assert failed == 2
assert passed == 1
def test_traceback_pruning(pytester: Pytester) -> None:
"""Regression test for #9610 - doesn't crash during traceback pruning."""
pytester.makepyfile(
"""
import unittest
class MyTestCase(unittest.TestCase):
def __init__(self, test_method):
unittest.TestCase.__init__(self, test_method)
class TestIt(MyTestCase):
@classmethod
def tearDownClass(cls) -> None:
assert False
def test_it(self):
pass
"""
)
reprec = pytester.inline_run()
passed, skipped, failed = reprec.countoutcomes()
assert passed == 1
assert failed == 1
assert reprec.ret == 1

View File

@@ -289,7 +289,7 @@ def test_warning_captured_hook(pytester: Pytester) -> None:
assert collected_result[2] == expected_result[2], str(collected)
# NOTE: collected_result[3] is location, which differs based on the platform you are on
# thus, the best we can do here is assert the types of the paremeters match what we expect
# thus, the best we can do here is assert the types of the parameters match what we expect
# and not try and preload it in the expected array
if collected_result[3] is not None:
assert type(collected_result[3][0]) is str, str(collected)
@@ -707,7 +707,7 @@ class TestStackLevel:
pytester.parseconfig("--help")
# with stacklevel=2 the warning should originate from config._preparse and is
# thrown by an errorneous conftest.py
# thrown by an erroneous conftest.py
assert len(capwarn.captured) == 1
warning, location = capwarn.captured.pop()
file, _, func = location

View File

@@ -9,6 +9,7 @@ envlist =
py38
py39
py310
py311
pypy3
py37-{pexpect,xdist,unittestextras,numpy,pluggymain}
doctesting
@@ -130,7 +131,7 @@ commands =
{envpython} tox_run.py
[testenv:release]
decription = do a release, required posarg of the version number
description = do a release, required posarg of the version number
basepython = python3
usedevelop = True
passenv = *
@@ -144,7 +145,7 @@ deps =
commands = python scripts/release.py {posargs}
[testenv:prepare-release-pr]
decription = prepare a release PR from a manual trigger in GitHub actions
description = prepare a release PR from a manual trigger in GitHub actions
usedevelop = {[testenv:release]usedevelop}
passenv = {[testenv:release]passenv}
deps = {[testenv:release]deps}