Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bf3bcb2bf1 |
@@ -22,7 +22,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
|
||||
@@ -1,44 +1,35 @@
|
||||
name: deploy
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: 'Release version'
|
||||
required: true
|
||||
default: '1.2.3'
|
||||
push:
|
||||
tags:
|
||||
# These tags are protected, see:
|
||||
# https://github.com/pytest-dev/pytest/settings/tag_protection
|
||||
- "[0-9]+.[0-9]+.[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||
|
||||
|
||||
# Set permissions at the job level.
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
package:
|
||||
|
||||
deploy:
|
||||
if: github.repository == 'pytest-dev/pytest'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }}
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Build and Check Package
|
||||
uses: hynek/build-and-inspect-python-package@v1.5.3
|
||||
|
||||
deploy:
|
||||
if: github.repository == 'pytest-dev/pytest'
|
||||
needs: [package]
|
||||
runs-on: ubuntu-latest
|
||||
environment: deploy
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
uses: hynek/build-and-inspect-python-package@v1.5
|
||||
|
||||
- name: Download Package
|
||||
uses: actions/download-artifact@v3
|
||||
@@ -47,35 +38,14 @@ jobs:
|
||||
path: dist
|
||||
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.10
|
||||
|
||||
- name: Push tag
|
||||
run: |
|
||||
git config user.name "pytest bot"
|
||||
git config user.email "pytestbot@gmail.com"
|
||||
git tag --annotate --message=v${{ github.event.inputs.version }} ${{ github.event.inputs.version }} ${{ github.sha }}
|
||||
git push origin ${{ github.event.inputs.version }}
|
||||
|
||||
release-notes:
|
||||
|
||||
# todo: generate the content in the build job
|
||||
# the goal being of using a github action script to push the release data
|
||||
# after success instead of creating a complete python/tox env
|
||||
needs: [deploy]
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
password: ${{ secrets.pypi_token }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Install tox
|
||||
run: |
|
||||
|
||||
@@ -27,7 +27,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
+48
-46
@@ -27,19 +27,7 @@ concurrency:
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
package:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Build and Check Package
|
||||
uses: hynek/build-and-inspect-python-package@v1.5.3
|
||||
|
||||
build:
|
||||
needs: [package]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
@@ -49,41 +37,48 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
name: [
|
||||
"windows-py37",
|
||||
"windows-py37-pluggy",
|
||||
"windows-py38",
|
||||
"windows-py38-pluggy",
|
||||
"windows-py39",
|
||||
"windows-py310",
|
||||
"windows-py311",
|
||||
"windows-py312",
|
||||
|
||||
"ubuntu-py37",
|
||||
"ubuntu-py37-pluggy",
|
||||
"ubuntu-py37-freeze",
|
||||
"ubuntu-py38",
|
||||
"ubuntu-py38-pluggy",
|
||||
"ubuntu-py38-freeze",
|
||||
"ubuntu-py39",
|
||||
"ubuntu-py310",
|
||||
"ubuntu-py311",
|
||||
"ubuntu-py312",
|
||||
"ubuntu-pypy3",
|
||||
|
||||
"macos-py38",
|
||||
"macos-py37",
|
||||
"macos-py39",
|
||||
"macos-py310",
|
||||
"macos-py312",
|
||||
|
||||
"docs",
|
||||
"doctesting",
|
||||
"plugins",
|
||||
]
|
||||
|
||||
include:
|
||||
- name: "windows-py37"
|
||||
python: "3.7"
|
||||
os: windows-latest
|
||||
tox_env: "py37-numpy"
|
||||
- name: "windows-py37-pluggy"
|
||||
python: "3.7"
|
||||
os: windows-latest
|
||||
tox_env: "py37-pluggymain-pylib-xdist"
|
||||
- name: "windows-py38"
|
||||
python: "3.8"
|
||||
os: windows-latest
|
||||
tox_env: "py38-unittestextras"
|
||||
use_coverage: true
|
||||
- name: "windows-py38-pluggy"
|
||||
python: "3.8"
|
||||
os: windows-latest
|
||||
tox_env: "py38-pluggymain-pylib-xdist"
|
||||
- name: "windows-py39"
|
||||
python: "3.9"
|
||||
os: windows-latest
|
||||
@@ -101,19 +96,23 @@ jobs:
|
||||
os: windows-latest
|
||||
tox_env: "py312"
|
||||
|
||||
- name: "ubuntu-py37"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py37-lsof-numpy-pexpect"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-py37-pluggy"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py37-pluggymain-pylib-xdist"
|
||||
- name: "ubuntu-py37-freeze"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py37-freeze"
|
||||
- name: "ubuntu-py38"
|
||||
python: "3.8"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py38-lsof-numpy-pexpect"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-py38-pluggy"
|
||||
python: "3.8"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py38-pluggymain-pylib-xdist"
|
||||
- name: "ubuntu-py38-freeze"
|
||||
python: "3.8"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py38-freeze"
|
||||
tox_env: "py38-xdist"
|
||||
- name: "ubuntu-py39"
|
||||
python: "3.9"
|
||||
os: ubuntu-latest
|
||||
@@ -133,14 +132,14 @@ jobs:
|
||||
tox_env: "py312"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-pypy3"
|
||||
python: "pypy-3.8"
|
||||
python: "pypy-3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "pypy3-xdist"
|
||||
|
||||
- name: "macos-py38"
|
||||
python: "3.8"
|
||||
- name: "macos-py37"
|
||||
python: "3.7"
|
||||
os: macos-latest
|
||||
tox_env: "py38-xdist"
|
||||
tox_env: "py37-xdist"
|
||||
- name: "macos-py39"
|
||||
python: "3.9"
|
||||
os: macos-latest
|
||||
@@ -160,24 +159,22 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
tox_env: "plugins"
|
||||
|
||||
- name: "docs"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "docs"
|
||||
- name: "doctesting"
|
||||
python: "3.8"
|
||||
python: "3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "doctesting"
|
||||
use_coverage: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Download Package
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
|
||||
- name: Set up Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
@@ -191,13 +188,11 @@ jobs:
|
||||
|
||||
- name: Test without coverage
|
||||
if: "! matrix.use_coverage"
|
||||
shell: bash
|
||||
run: tox run -e ${{ matrix.tox_env }} --installpkg `find dist/*.tar.gz`
|
||||
run: "tox -e ${{ matrix.tox_env }}"
|
||||
|
||||
- name: Test with coverage
|
||||
if: "matrix.use_coverage"
|
||||
shell: bash
|
||||
run: tox run -e ${{ matrix.tox_env }}-coverage --installpkg `find dist/*.tar.gz`
|
||||
run: "tox -e ${{ matrix.tox_env }}-coverage"
|
||||
|
||||
- name: Generate coverage report
|
||||
if: "matrix.use_coverage"
|
||||
@@ -211,3 +206,10 @@ jobs:
|
||||
fail_ci_if_error: true
|
||||
files: ./coverage.xml
|
||||
verbose: true
|
||||
|
||||
check-package:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Build and Check Package
|
||||
uses: hynek/build-and-inspect-python-package@v1.5
|
||||
|
||||
@@ -20,33 +20,25 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
- name: requests-cache
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/.cache/pytest-plugin-list/
|
||||
key: plugins-http-cache-${{ github.run_id }} # Can use time based key as well
|
||||
restore-keys: plugins-http-cache-
|
||||
python-version: 3.8
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install packaging requests tabulate[widechars] tqdm requests-cache platformdirs
|
||||
|
||||
pip install packaging requests tabulate[widechars] tqdm
|
||||
|
||||
- name: Update Plugin List
|
||||
run: python scripts/update-plugin-list.py
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@153407881ec5c347639a548ade7d8ad1d6740e38
|
||||
uses: peter-evans/create-pull-request@5b4a9f6a9e2af26e5f02351490b90d01eb8ec1e5
|
||||
with:
|
||||
commit-message: '[automated] Update plugin list'
|
||||
author: 'pytest bot <pytestbot@users.noreply.github.com>'
|
||||
|
||||
+12
-12
@@ -1,16 +1,16 @@
|
||||
repos:
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.10.1
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: 1.16.0
|
||||
rev: 1.13.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==23.7.0]
|
||||
additional_dependencies: [black==23.1.0]
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.5.0
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: end-of-file-fixer
|
||||
@@ -21,7 +21,7 @@ repos:
|
||||
exclude: _pytest/(debugging|hookspec).py
|
||||
language_version: python3
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.2.1
|
||||
rev: v2.1.1
|
||||
hooks:
|
||||
- id: autoflake
|
||||
name: autoflake
|
||||
@@ -29,7 +29,7 @@ repos:
|
||||
language: python
|
||||
files: \.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.1.0
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
@@ -37,17 +37,17 @@ repos:
|
||||
- flake8-typing-imports==1.12.0
|
||||
- flake8-docstrings==1.5.0
|
||||
- repo: https://github.com/asottile/reorder-python-imports
|
||||
rev: v3.12.0
|
||||
rev: v3.9.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src', --py38-plus]
|
||||
args: ['--application-directories=.:src', --py37-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.15.0
|
||||
rev: v3.4.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
args: [--py37-plus]
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.5.0
|
||||
rev: v2.3.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: ["--max-py-version=3.12", "--include-version-classifiers"]
|
||||
@@ -56,7 +56,7 @@ repos:
|
||||
hooks:
|
||||
- id: python-use-type-annotations
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.6.1
|
||||
rev: v1.3.0
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^(src/|testing/)
|
||||
|
||||
@@ -9,10 +9,6 @@ python:
|
||||
path: .
|
||||
- requirements: doc/en/requirements.txt
|
||||
|
||||
sphinx:
|
||||
configuration: doc/en/conf.py
|
||||
fail_on_warning: true
|
||||
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
|
||||
@@ -11,7 +11,6 @@ Adam Johnson
|
||||
Adam Stewart
|
||||
Adam Uhlir
|
||||
Ahn Ki-Wook
|
||||
Akhilesh Ramakrishnan
|
||||
Akiomi Kamakura
|
||||
Alan Velasco
|
||||
Alessio Izzo
|
||||
@@ -56,9 +55,7 @@ Barney Gale
|
||||
Ben Gartner
|
||||
Ben Webb
|
||||
Benjamin Peterson
|
||||
Benjamin Schubert
|
||||
Bernard Pratz
|
||||
Bo Wu
|
||||
Bob Ippolito
|
||||
Brian Dorsey
|
||||
Brian Larsen
|
||||
@@ -132,7 +129,6 @@ Eric Hunsberger
|
||||
Eric Liu
|
||||
Eric Siegerman
|
||||
Erik Aronesty
|
||||
Erik Hasse
|
||||
Erik M. Bray
|
||||
Evan Kepner
|
||||
Evgeny Seliverstov
|
||||
@@ -144,7 +140,6 @@ Feng Ma
|
||||
Florian Bruhin
|
||||
Florian Dahlitz
|
||||
Floris Bruynooghe
|
||||
Fraser Stark
|
||||
Gabriel Landau
|
||||
Gabriel Reis
|
||||
Garvit Shubham
|
||||
@@ -171,8 +166,6 @@ Ian Bicking
|
||||
Ian Lesperance
|
||||
Ilya Konstantinov
|
||||
Ionuț Turturică
|
||||
Isaac Virshup
|
||||
Israel Fruchter
|
||||
Itxaso Aizpurua
|
||||
Iwan Briquemont
|
||||
Jaap Broekhuizen
|
||||
@@ -236,7 +229,6 @@ Maho
|
||||
Maik Figura
|
||||
Mandeep Bhutani
|
||||
Manuel Krebber
|
||||
Marc Mueller
|
||||
Marc Schlaich
|
||||
Marcelo Duarte Trevisani
|
||||
Marcin Bachry
|
||||
@@ -267,10 +259,8 @@ Michal Wajszczuk
|
||||
Michał Zięba
|
||||
Mickey Pashov
|
||||
Mihai Capotă
|
||||
Mihail Milushev
|
||||
Mike Hoyle (hoylemd)
|
||||
Mike Lundy
|
||||
Milan Lesnek
|
||||
Miro Hrončok
|
||||
Nathaniel Compton
|
||||
Nathaniel Waisbrot
|
||||
@@ -320,7 +310,6 @@ Raphael Pierzina
|
||||
Rafal Semik
|
||||
Raquel Alegre
|
||||
Ravi Chandra
|
||||
Reagan Lee
|
||||
Robert Holt
|
||||
Roberto Aldera
|
||||
Roberto Polli
|
||||
@@ -331,9 +320,7 @@ Ronny Pfannschmidt
|
||||
Ross Lawley
|
||||
Ruaridh Williamson
|
||||
Russel Winder
|
||||
Ryan Puddephatt
|
||||
Ryan Wooden
|
||||
Sadra Barikbin
|
||||
Saiprasad Kale
|
||||
Samuel Colvin
|
||||
Samuel Dion-Girardeau
|
||||
@@ -342,20 +329,16 @@ Samuele Pedroni
|
||||
Sanket Duthade
|
||||
Sankt Petersbug
|
||||
Saravanan Padmanaban
|
||||
Sean Malloy
|
||||
Segev Finer
|
||||
Serhii Mozghovyi
|
||||
Seth Junot
|
||||
Shantanu Jain
|
||||
Sharad Nair
|
||||
Shubham Adep
|
||||
Simon Blanchard
|
||||
Simon Gomizelj
|
||||
Simon Holesch
|
||||
Simon Kerr
|
||||
Skylar Downes
|
||||
Srinivas Reddy Thatiparthy
|
||||
Stefaan Lippens
|
||||
Stefan Farmbauer
|
||||
Stefan Scherfke
|
||||
Stefan Zimmermann
|
||||
@@ -369,7 +352,6 @@ Tadek Teleżyński
|
||||
Takafumi Arakaki
|
||||
Taneli Hukkinen
|
||||
Tanvi Mehta
|
||||
Tanya Agarwal
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
Tatiana Ovary
|
||||
@@ -388,16 +370,13 @@ Tomer Keren
|
||||
Tony Narlock
|
||||
Tor Colvin
|
||||
Trevor Bekolay
|
||||
Tushar Sadhwani
|
||||
Tyler Goodlet
|
||||
Tyler Smart
|
||||
Tzu-ping Chung
|
||||
Vasily Kuznetsov
|
||||
Victor Maryama
|
||||
Victor Rodriguez
|
||||
Victor Uriarte
|
||||
Vidar T. Fauske
|
||||
Vijay Arora
|
||||
Virgil Dupras
|
||||
Vitaly Lashmanov
|
||||
Vivaan Verma
|
||||
|
||||
+9
-11
@@ -50,7 +50,7 @@ Fix bugs
|
||||
--------
|
||||
|
||||
Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
|
||||
See also the `"good first issue" issues <https://github.com/pytest-dev/pytest/labels/good%20first%20issue>`_
|
||||
See also the `"status: easy" issues <https://github.com/pytest-dev/pytest/labels/status%3A%20easy>`_
|
||||
that are friendly to new contributors.
|
||||
|
||||
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going
|
||||
@@ -197,12 +197,11 @@ Short version
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
#. Fork the repository.
|
||||
#. Fetch tags from upstream if necessary (if you cloned only main `git fetch --tags https://github.com/pytest-dev/pytest`).
|
||||
#. Enable and install `pre-commit <https://pre-commit.com>`_ to ensure style-guides and code checks are followed.
|
||||
#. Follow **PEP-8** for naming and `black <https://github.com/psf/black>`_ for formatting.
|
||||
#. Tests are run using ``tox``::
|
||||
|
||||
tox -e linting,py39
|
||||
tox -e linting,py37
|
||||
|
||||
The test environments above are usually enough to cover most cases locally.
|
||||
|
||||
@@ -237,7 +236,6 @@ Here is a simple overview, with pytest-specific bits:
|
||||
|
||||
$ git clone git@github.com:YOUR_GITHUB_USERNAME/pytest.git
|
||||
$ cd pytest
|
||||
$ git fetch --tags https://github.com/pytest-dev/pytest
|
||||
# now, create your own branch off "main":
|
||||
|
||||
$ git checkout -b your-bugfix-branch-name main
|
||||
@@ -274,24 +272,24 @@ Here is a simple overview, with pytest-specific bits:
|
||||
|
||||
#. Run all the tests
|
||||
|
||||
You need to have Python 3.8 or later available in your system. Now
|
||||
You need to have Python 3.7 available in your system. Now
|
||||
running tests is as simple as issuing this command::
|
||||
|
||||
$ tox -e linting,py39
|
||||
$ tox -e linting,py37
|
||||
|
||||
This command will run tests via the "tox" tool against Python 3.9
|
||||
This command will run tests via the "tox" tool against Python 3.7
|
||||
and also perform "lint" coding-style checks.
|
||||
|
||||
#. You can now edit your local working copy and run the tests again as necessary. Please follow PEP-8 for naming.
|
||||
|
||||
You can pass different options to ``tox``. For example, to run tests on Python 3.9 and pass options to pytest
|
||||
You can pass different options to ``tox``. For example, to run tests on Python 3.7 and pass options to pytest
|
||||
(e.g. enter pdb on failure) to pytest you can do::
|
||||
|
||||
$ tox -e py39 -- --pdb
|
||||
$ tox -e py37 -- --pdb
|
||||
|
||||
Or to only run tests in a particular test module on Python 3.9::
|
||||
Or to only run tests in a particular test module on Python 3.7::
|
||||
|
||||
$ tox -e py39 -- testing/test_config.py
|
||||
$ tox -e py37 -- testing/test_config.py
|
||||
|
||||
|
||||
When committing, ``pre-commit`` will re-format the files if necessary.
|
||||
|
||||
+1
-1
@@ -100,7 +100,7 @@ Features
|
||||
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial),
|
||||
`nose <https://docs.pytest.org/en/stable/how-to/nose.html>`_ test suites out of the box
|
||||
|
||||
- Python 3.8+ or PyPy3
|
||||
- Python 3.7+ or PyPy3
|
||||
|
||||
- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
|
||||
|
||||
|
||||
+7
-5
@@ -133,12 +133,14 @@ Releasing
|
||||
|
||||
Both automatic and manual processes described above follow the same steps from this point onward.
|
||||
|
||||
#. After all tests pass and the PR has been approved, trigger the ``deploy`` job
|
||||
in https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml, using the ``release-MAJOR.MINOR.PATCH`` branch
|
||||
as source.
|
||||
#. After all tests pass and the PR has been approved, tag the release commit
|
||||
in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI::
|
||||
|
||||
This job will require approval from ``pytest-dev/core``, after which it will publish to PyPI
|
||||
and tag the repository.
|
||||
git fetch upstream
|
||||
git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH
|
||||
git push upstream MAJOR.MINOR.PATCH
|
||||
|
||||
Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
|
||||
|
||||
#. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch.
|
||||
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
Added :func:`ExceptionInfo.group_contains() <pytest.ExceptionInfo.group_contains>`, an assertion
|
||||
helper that tests if an `ExceptionGroup` contains a matching exception.
|
||||
@@ -1 +0,0 @@
|
||||
Test functions returning a value other than None will now issue a :class:`pytest.PytestWarning` instead of :class:`pytest.PytestRemovedIn8Warning`, meaning this will stay a warning instead of becoming an error in the future.
|
||||
@@ -1,2 +0,0 @@
|
||||
Added more comprehensive set assertion rewrites for comparisons other than equality ``==``, with
|
||||
the following operations now providing better failure messages: ``!=``, ``<=``, ``>=``, ``<``, and ``>``.
|
||||
@@ -1,2 +0,0 @@
|
||||
:meth:`pytest.WarningsRecorder.pop` will return the most-closely-matched warning in the list,
|
||||
rather than the first warning which is an instance of the requested type.
|
||||
@@ -0,0 +1 @@
|
||||
Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.
|
||||
@@ -0,0 +1 @@
|
||||
Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.
|
||||
@@ -0,0 +1,2 @@
|
||||
Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
|
||||
This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
|
||||
@@ -0,0 +1,5 @@
|
||||
When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
|
||||
|
||||
"All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
|
||||
|
||||
Previously, the last frame of the traceback was shown, even though it was hidden.
|
||||
@@ -0,0 +1,3 @@
|
||||
Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
|
||||
|
||||
Added :func:`TerminalReporter.wrap_write() <pytest.TerminalReporter.wrap_write>` as a helper for that.
|
||||
@@ -0,0 +1 @@
|
||||
Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.
|
||||
@@ -0,0 +1 @@
|
||||
Added underlying exception to cache provider path creation and write warning messages.
|
||||
@@ -1 +0,0 @@
|
||||
Added a warning about modifying the root logger during tests when using ``caplog``.
|
||||
@@ -0,0 +1 @@
|
||||
Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.
|
||||
@@ -0,0 +1 @@
|
||||
Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.
|
||||
@@ -0,0 +1,3 @@
|
||||
When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir`.
|
||||
Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
|
||||
If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.
|
||||
@@ -1,3 +0,0 @@
|
||||
Use pytestconfig instead of request.config in cache example
|
||||
|
||||
to be consistent with the API documentation.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
|
||||
@@ -0,0 +1,7 @@
|
||||
The :confval:`norecursedir` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it.
|
||||
|
||||
If after updating to this version you see that your `norecursedir` setting is not being respected,
|
||||
it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation.
|
||||
Most likely, your hook returns `False` for paths it does not want to ignore,
|
||||
which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path.
|
||||
The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore.
|
||||
@@ -1 +0,0 @@
|
||||
Updated documentation and tests to refer to hyphonated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``.
|
||||
@@ -1,6 +0,0 @@
|
||||
``pluggy>=1.2.0`` is now required.
|
||||
|
||||
pytest now uses "new-style" hook wrappers internally, available since pluggy 1.2.0.
|
||||
See `pluggy's 1.2.0 changelog <https://pluggy.readthedocs.io/en/latest/changelog.html#pluggy-1-2-0-2023-06-21>`_ and the :ref:`updated docs <hookwrapper>` for details.
|
||||
|
||||
Plugins which want to use new-style wrappers can do so if they require this version of pytest or later.
|
||||
@@ -1,11 +0,0 @@
|
||||
:class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`.
|
||||
|
||||
The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file.
|
||||
Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module),
|
||||
the module being the `__init__.py` file.
|
||||
This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details).
|
||||
|
||||
The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file.
|
||||
|
||||
Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist,
|
||||
if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files).
|
||||
@@ -1 +0,0 @@
|
||||
- Prevent constants at the top of file from being detected as docstrings.
|
||||
@@ -1 +0,0 @@
|
||||
Dropped support for Python 3.7, which `reached end-of-life on 2023-06-27 <https://devguide.python.org/versions/>`__.
|
||||
@@ -1,2 +0,0 @@
|
||||
The (internal) ``FixtureDef.cached_result`` type has changed.
|
||||
Now the third item ``cached_result[2]``, when set, is an exception instance instead of an exception triplet.
|
||||
@@ -1 +0,0 @@
|
||||
If a test is skipped from inside an :ref:`xunit setup fixture <classic xunit>`, the test summary now shows the test location instead of the fixture location.
|
||||
@@ -1,5 +0,0 @@
|
||||
(This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.)
|
||||
|
||||
:class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly.
|
||||
A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions,
|
||||
as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions.
|
||||
@@ -1 +0,0 @@
|
||||
Allow :func:`pytest.raises` ``match`` argument to match against `PEP-678 <https://peps.python.org/pep-0678/>` ``__notes__``.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed crash on `parametrize(..., scope="package")` without a package present.
|
||||
@@ -1,2 +0,0 @@
|
||||
Fixed a bug that when there are multiple fixtures for an indirect parameter,
|
||||
the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.
|
||||
@@ -1,2 +0,0 @@
|
||||
Logging to a file using the ``--log-file`` option will use ``--log-level``, ``--log-format`` and ``--log-date-format`` as fallback
|
||||
if ``--log-file-level``, ``--log-file-format`` and ``--log-file-date-format`` are not provided respectively.
|
||||
@@ -1,3 +0,0 @@
|
||||
The :fixture:`pytester` fixture now uses the :fixture:`monkeypatch` fixture to manage the current working directory.
|
||||
If you use ``pytester`` in combination with :func:`monkeypatch.undo() <pytest.MonkeyPatch.undo>`, the CWD might get restored.
|
||||
Use :func:`monkeypatch.context() <pytest.MonkeyPatch.context>` instead.
|
||||
@@ -1,2 +0,0 @@
|
||||
Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``.
|
||||
The previous spelling ``INCOVATION_DIR`` remains as an alias.
|
||||
@@ -1 +0,0 @@
|
||||
pluggy>=1.3.0 is now required. This adds typing to :class:`~pytest.PytestPluginManager`.
|
||||
@@ -1 +0,0 @@
|
||||
:func:`pytest.deprecated_call` now also considers warnings of type :class:`FutureWarning`.
|
||||
@@ -1,4 +0,0 @@
|
||||
Parametrized tests now *really do* ensure that the ids given to each input are unique - for
|
||||
example, ``a, a, a0`` now results in ``a1, a2, a0`` instead of the previous (buggy) ``a0, a1, a0``.
|
||||
This necessarily means changing nodeids where these were previously colliding, and for
|
||||
readability adds an underscore when non-unique ids end in a number.
|
||||
@@ -1 +0,0 @@
|
||||
Improved very verbose diff output to color it as a diff instead of only red.
|
||||
@@ -1 +0,0 @@
|
||||
Fixed crash when using an empty string for the same parametrized value more than once.
|
||||
@@ -0,0 +1 @@
|
||||
Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
|
||||
@@ -1,3 +0,0 @@
|
||||
Applying a mark to a fixture function now issues a warning: marks in fixtures never had any effect, but it is a common user error to apply a mark to a fixture (for example ``usefixtures``) and expect it to work.
|
||||
|
||||
This will become an error in the future.
|
||||
@@ -1,22 +0,0 @@
|
||||
**PytestRemovedIn8Warning deprecation warnings are now errors by default.**
|
||||
|
||||
Following our plan to remove deprecated features with as little disruption as
|
||||
possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors
|
||||
instead of warning messages by default.
|
||||
|
||||
**The affected features will be effectively removed in pytest 8.1**, so please consult the
|
||||
:ref:`deprecations` section in the docs for directions on how to update existing code.
|
||||
|
||||
In the pytest ``8.0.X`` series, it is possible to change the errors back into warnings as a
|
||||
stopgap measure by adding this to your ``pytest.ini`` file:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
ignore::pytest.PytestRemovedIn8Warning
|
||||
|
||||
But this will stop working when pytest ``8.1`` is released.
|
||||
|
||||
**If you have concerns** about the removal of a specific feature, please add a
|
||||
comment to :issue:`7363`.
|
||||
@@ -1 +0,0 @@
|
||||
:class:`~pytest.FixtureDef` is now exported as ``pytest.FixtureDef`` for typing purposes.
|
||||
@@ -1 +0,0 @@
|
||||
Removes unhelpful error message from assertion rewrite mechanism when exceptions raised in __iter__ methods, and instead treats them as un-iterable.
|
||||
@@ -0,0 +1,3 @@
|
||||
:func:`_pytest.logging.LogCaptureFixture.set_level` and :func:`_pytest.logging.LogCaptureFixture.at_level`
|
||||
will temporarily enable the requested ``level`` if ``level`` was disabled globally via
|
||||
``logging.disable(LEVEL)``.
|
||||
@@ -1,5 +0,0 @@
|
||||
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
|
||||
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
|
||||
(unless :confval:`python_files` was changed to allow `__init__.py` file).
|
||||
|
||||
To collect the entire package, specify just the directory: `pytest pkg`.
|
||||
@@ -1 +0,0 @@
|
||||
``pytest.warns`` and similar functions now capture warnings when an exception is raised inside a ``with`` block.
|
||||
@@ -1,7 +0,0 @@
|
||||
:func:`pytest.warns <warns>` now re-emits unmatched warnings when the context
|
||||
closes -- previously it would consume all warnings, hiding those that were not
|
||||
matched by the function.
|
||||
|
||||
While this is a new feature, we decided to announce this as a breaking change
|
||||
because many test suites are configured to error-out on warnings, and will
|
||||
therefore fail on the newly-re-emitted warnings.
|
||||
@@ -14,7 +14,7 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
|
||||
``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
|
||||
|
||||
* ``feature``: new user facing features, like new command-line options and new behavior.
|
||||
* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junit-xml``, improved colors in terminal, etc).
|
||||
* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
|
||||
* ``bugfix``: fixes a bug.
|
||||
* ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
|
||||
* ``deprecation``: feature deprecation.
|
||||
|
||||
@@ -6,10 +6,6 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-7.4.3
|
||||
release-7.4.2
|
||||
release-7.4.1
|
||||
release-7.4.0
|
||||
release-7.3.2
|
||||
release-7.3.1
|
||||
release-7.3.0
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
pytest-7.4.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 7.4.0 release!
|
||||
|
||||
This release contains new features, improvements, and bug fixes,
|
||||
the full list of changes is available in the changelog:
|
||||
|
||||
https://docs.pytest.org/en/stable/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/stable/
|
||||
|
||||
As usual, you can upgrade from PyPI via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Adam J. Stewart
|
||||
* Alessio Izzo
|
||||
* Alex
|
||||
* Alex Lambson
|
||||
* Brian Larsen
|
||||
* Bruno Oliveira
|
||||
* Bryan Ricker
|
||||
* Chris Mahoney
|
||||
* Facundo Batista
|
||||
* Florian Bruhin
|
||||
* Jarrett Keifer
|
||||
* Kenny Y
|
||||
* Miro Hrončok
|
||||
* Ran Benita
|
||||
* Roberto Aldera
|
||||
* Ronny Pfannschmidt
|
||||
* Sergey Kim
|
||||
* Stefanie Molin
|
||||
* Vijay Arora
|
||||
* Ville Skyttä
|
||||
* Zac Hatfield-Dodds
|
||||
* bzoracler
|
||||
* leeyueh
|
||||
* nondescryptid
|
||||
* theirix
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,20 +0,0 @@
|
||||
pytest-7.4.1
|
||||
=======================================
|
||||
|
||||
pytest 7.4.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:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Florian Bruhin
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,18 +0,0 @@
|
||||
pytest-7.4.2
|
||||
=======================================
|
||||
|
||||
pytest 7.4.2 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Bruno Oliveira
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,19 +0,0 @@
|
||||
pytest-7.4.3
|
||||
=======================================
|
||||
|
||||
pytest 7.4.3 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Marc Mueller
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -87,7 +87,6 @@ Released pytest versions support all Python versions that are actively maintaine
|
||||
============== ===================
|
||||
pytest version min. Python version
|
||||
============== ===================
|
||||
8.0+ 3.8+
|
||||
7.1+ 3.7+
|
||||
6.2 - 7.0 3.6+
|
||||
5.0 - 6.1 3.5+
|
||||
|
||||
+4
-4
@@ -22,7 +22,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collected 0 items
|
||||
cache -- .../_pytest/cacheprovider.py:532
|
||||
cache -- .../_pytest/cacheprovider.py:510
|
||||
Return a cache object that can persist state between testing sessions.
|
||||
|
||||
cache.get(key, default)
|
||||
@@ -105,7 +105,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
captured = capsys.readouterr()
|
||||
assert captured.out == "hello\n"
|
||||
|
||||
doctest_namespace [session scope] -- .../_pytest/doctest.py:757
|
||||
doctest_namespace [session scope] -- .../_pytest/doctest.py:737
|
||||
Fixture that returns a :py:class:`dict` that will be injected into the
|
||||
namespace of doctests.
|
||||
|
||||
@@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
For more details: :ref:`doctest_namespace`.
|
||||
|
||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1353
|
||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1360
|
||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||
object.
|
||||
|
||||
@@ -196,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
.. _legacy_path: https://py.readthedocs.io/en/latest/path.html
|
||||
|
||||
caplog -- .../_pytest/logging.py:570
|
||||
caplog -- .../_pytest/logging.py:498
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following properties/methods::
|
||||
|
||||
@@ -28,155 +28,6 @@ with advance notice in the **Deprecations** section of releases.
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 7.4.3 (2023-10-24)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10447 <https://github.com/pytest-dev/pytest/issues/10447>`_: Markers are now considered in the reverse mro order to ensure base class markers are considered first -- this resolves a regression.
|
||||
|
||||
|
||||
- `#11239 <https://github.com/pytest-dev/pytest/issues/11239>`_: Fixed ``:=`` in asserts impacting unrelated test cases.
|
||||
|
||||
|
||||
- `#11439 <https://github.com/pytest-dev/pytest/issues/11439>`_: Handled an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
|
||||
|
||||
|
||||
pytest 7.4.2 (2023-09-07)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#11237 <https://github.com/pytest-dev/pytest/issues/11237>`_: Fix doctest collection of `functools.cached_property` objects.
|
||||
|
||||
|
||||
- `#11306 <https://github.com/pytest-dev/pytest/issues/11306>`_: Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
|
||||
|
||||
|
||||
- `#11367 <https://github.com/pytest-dev/pytest/issues/11367>`_: Fixed bug where `user_properties` where not being saved in the JUnit XML file if a fixture failed during teardown.
|
||||
|
||||
|
||||
- `#11394 <https://github.com/pytest-dev/pytest/issues/11394>`_: Fixed crash when parsing long command line arguments that might be interpreted as files.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#11391 <https://github.com/pytest-dev/pytest/issues/11391>`_: Improved disclaimer on pytest plugin reference page to better indicate this is an automated, non-curated listing.
|
||||
|
||||
|
||||
pytest 7.4.1 (2023-09-02)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10337 <https://github.com/pytest-dev/pytest/issues/10337>`_: Fixed bug where fake intermediate modules generated by ``--import-mode=importlib`` would not include the
|
||||
child modules as attributes of the parent modules.
|
||||
|
||||
|
||||
- `#10702 <https://github.com/pytest-dev/pytest/issues/10702>`_: Fixed error assertion handling in :func:`pytest.approx` when ``None`` is an expected or received value when comparing dictionaries.
|
||||
|
||||
|
||||
- `#10811 <https://github.com/pytest-dev/pytest/issues/10811>`_: Fixed issue when using ``--import-mode=importlib`` together with ``--doctest-modules`` that caused modules
|
||||
to be imported more than once, causing problems with modules that have import side effects.
|
||||
|
||||
|
||||
pytest 7.4.0 (2023-06-23)
|
||||
=========================
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
- `#10901 <https://github.com/pytest-dev/pytest/issues/10901>`_: Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
|
||||
This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
|
||||
|
||||
|
||||
|
||||
Improvements
|
||||
------------
|
||||
|
||||
- `#10872 <https://github.com/pytest-dev/pytest/issues/10872>`_: Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.
|
||||
|
||||
|
||||
- `#10907 <https://github.com/pytest-dev/pytest/issues/10907>`_: When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
|
||||
|
||||
"All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
|
||||
|
||||
Previously, the last frame of the traceback was shown, even though it was hidden.
|
||||
|
||||
|
||||
- `#10940 <https://github.com/pytest-dev/pytest/issues/10940>`_: Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
|
||||
|
||||
Added ``TerminalReporter.wrap_write()`` as a helper for that.
|
||||
|
||||
|
||||
- `#10991 <https://github.com/pytest-dev/pytest/issues/10991>`_: Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.
|
||||
|
||||
|
||||
- `#11005 <https://github.com/pytest-dev/pytest/issues/11005>`_: Added the underlying exception to the cache provider's path creation and write warning messages.
|
||||
|
||||
|
||||
- `#11013 <https://github.com/pytest-dev/pytest/issues/11013>`_: Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.
|
||||
|
||||
|
||||
- `#11043 <https://github.com/pytest-dev/pytest/issues/11043>`_: When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir <rootdir>`.
|
||||
Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
|
||||
If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.
|
||||
|
||||
|
||||
- `#11081 <https://github.com/pytest-dev/pytest/issues/11081>`_: The :confval:`norecursedirs` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it.
|
||||
|
||||
If after updating to this version you see that your `norecursedirs` setting is not being respected,
|
||||
it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation.
|
||||
Most likely, your hook returns `False` for paths it does not want to ignore,
|
||||
which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path.
|
||||
The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore.
|
||||
|
||||
|
||||
- `#8711 <https://github.com/pytest-dev/pytest/issues/8711>`_: :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` and :func:`caplog.at_level() <pytest.LogCaptureFixture.at_level>`
|
||||
will temporarily enable the requested ``level`` if ``level`` was disabled globally via
|
||||
``logging.disable(LEVEL)``.
|
||||
|
||||
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#10831 <https://github.com/pytest-dev/pytest/issues/10831>`_: Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.
|
||||
|
||||
|
||||
- `#11068 <https://github.com/pytest-dev/pytest/issues/11068>`_: Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
|
||||
|
||||
|
||||
- `#11104 <https://github.com/pytest-dev/pytest/issues/11104>`_: Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests,
|
||||
even when it was not utilized (e.g. when explicit paths were given on the command line).
|
||||
Now the ``testpaths`` are only considered when they are in use.
|
||||
|
||||
|
||||
- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
|
||||
|
||||
|
||||
- `#7781 <https://github.com/pytest-dev/pytest/issues/7781>`_: Fix writing non-encodable text to log file when using ``--debug``.
|
||||
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- `#9146 <https://github.com/pytest-dev/pytest/issues/9146>`_: Improved documentation for :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>`.
|
||||
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- `#11031 <https://github.com/pytest-dev/pytest/issues/11031>`_: Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.
|
||||
|
||||
|
||||
pytest 7.3.2 (2023-06-10)
|
||||
=========================
|
||||
|
||||
|
||||
@@ -15,10 +15,12 @@
|
||||
#
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
# The short X.Y version.
|
||||
import ast
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from textwrap import dedent
|
||||
from typing import List
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from _pytest import __version__ as version
|
||||
@@ -449,6 +451,25 @@ def setup(app: "sphinx.application.Sphinx") -> None:
|
||||
|
||||
configure_logging(app)
|
||||
|
||||
# Make Sphinx mark classes with "final" when decorated with @final.
|
||||
# We need this because we import final from pytest._compat, not from
|
||||
# typing (for Python < 3.8 compat), so Sphinx doesn't detect it.
|
||||
# To keep things simple we accept any `@final` decorator.
|
||||
# Ref: https://github.com/pytest-dev/pytest/pull/7780
|
||||
import sphinx.pycode.ast
|
||||
import sphinx.pycode.parser
|
||||
|
||||
original_is_final = sphinx.pycode.parser.VariableCommentPicker.is_final
|
||||
|
||||
def patched_is_final(self, decorators: List[ast.expr]) -> bool:
|
||||
if original_is_final(self, decorators):
|
||||
return True
|
||||
return any(
|
||||
sphinx.pycode.ast.unparse(decorator) == "final" for decorator in decorators
|
||||
)
|
||||
|
||||
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
|
||||
|
||||
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
|
||||
# that autodoc can discover references to it.
|
||||
import _pytest.legacypath # noqa: F401
|
||||
|
||||
+3
-52
@@ -380,25 +380,6 @@ 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.
|
||||
|
||||
Applying a mark to a fixture function
|
||||
-------------------------------------
|
||||
|
||||
.. deprecated:: 7.4
|
||||
|
||||
Applying a mark to a fixture function never had any effect, but it is a common user error.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.usefixtures("clean_database")
|
||||
@pytest.fixture
|
||||
def user() -> User:
|
||||
...
|
||||
|
||||
Users expected in this case that the ``usefixtures`` mark would have its intended effect of using the ``clean_database`` fixture when ``user`` was invoked, when in fact it has no effect at all.
|
||||
|
||||
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
|
||||
|
||||
|
||||
Backward compatibilities in ``Parser.addoption``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -486,42 +467,12 @@ The ``yield_fixture`` function/decorator
|
||||
It has been so for a very long time, so can be search/replaced safely.
|
||||
|
||||
|
||||
Removed Features and Breaking Changes
|
||||
-------------------------------------
|
||||
Removed Features
|
||||
----------------
|
||||
|
||||
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
|
||||
an appropriate period of deprecation has passed.
|
||||
|
||||
Some breaking changes which could not be deprecated are also listed.
|
||||
|
||||
|
||||
:class:`pytest.Package` is no longer a :class:`pytest.Module` or :class:`pytest.File`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionchanged:: 8.0
|
||||
|
||||
The ``Package`` collector node designates a Python package, that is, a directory with an `__init__.py` file.
|
||||
Previously ``Package`` was a subtype of ``pytest.Module`` (which represents a single Python module),
|
||||
the module being the `__init__.py` file.
|
||||
This has been deemed a design mistake (see :issue:`11137` and :issue:`7777` for details).
|
||||
|
||||
The ``path`` property of ``Package`` nodes now points to the package directory instead of the ``__init__.py`` file.
|
||||
|
||||
Note that a ``Module`` node for ``__init__.py`` (which is not a ``Package``) may still exist,
|
||||
if it is picked up during collection (e.g. if you configured :confval:`python_files` to include ``__init__.py`` files).
|
||||
|
||||
|
||||
Collecting ``__init__.py`` files no longer collects package
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
Running `pytest pkg/__init__.py` now collects the `pkg/__init__.py` file (module) only.
|
||||
Previously, it collected the entire `pkg` package, including other test files in the directory, but excluding tests in the `__init__.py` file itself
|
||||
(unless :confval:`python_files` was changed to allow `__init__.py` file).
|
||||
|
||||
To collect the entire package, specify just the directory: `pytest pkg`.
|
||||
|
||||
|
||||
The ``pytest.collect`` module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -645,7 +596,7 @@ By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading
|
||||
pytest 6.0, where the default format will be ``xunit2``.
|
||||
|
||||
In order to let users know about the transition, pytest will issue a warning in case
|
||||
the ``--junit-xml`` option is given in the command line but ``junit_family`` is not explicitly
|
||||
the ``--junitxml`` option is given in the command line but ``junit_family`` is not explicitly
|
||||
configured in ``pytest.ini``.
|
||||
|
||||
Services known to support the ``xunit2`` format:
|
||||
|
||||
@@ -136,7 +136,7 @@ Or select multiple nodes:
|
||||
|
||||
Node IDs for failing tests are displayed in the test summary info
|
||||
when running pytest with the ``-rf`` option. You can also
|
||||
construct Node IDs from the output of ``pytest --collect-only``.
|
||||
construct Node IDs from the output of ``pytest --collectonly``.
|
||||
|
||||
Using ``-k expr`` to select tests based on their name
|
||||
-------------------------------------------------------
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
"""Module containing a parametrized tests testing cross-python serialization
|
||||
via the pickle module."""
|
||||
"""
|
||||
module containing a parametrized tests testing cross-python
|
||||
serialization via the pickle module.
|
||||
"""
|
||||
import shutil
|
||||
import subprocess
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
pythonlist = ["python3.9", "python3.10", "python3.11"]
|
||||
pythonlist = ["python3.5", "python3.6", "python3.7"]
|
||||
|
||||
|
||||
@pytest.fixture(params=pythonlist)
|
||||
@@ -42,7 +43,7 @@ class Python:
|
||||
)
|
||||
)
|
||||
)
|
||||
subprocess.run((self.pythonpath, str(dumpfile)), check=True)
|
||||
subprocess.check_call((self.pythonpath, str(dumpfile)))
|
||||
|
||||
def load_and_is_true(self, expression):
|
||||
loadfile = self.picklefile.with_name("load.py")
|
||||
@@ -62,7 +63,7 @@ class Python:
|
||||
)
|
||||
)
|
||||
print(loadfile)
|
||||
subprocess.run((self.pythonpath, str(loadfile)), check=True)
|
||||
subprocess.check_call((self.pythonpath, str(loadfile)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])
|
||||
|
||||
@@ -12,7 +12,7 @@ class YamlFile(pytest.File):
|
||||
# We need a yaml parser, e.g. PyYAML.
|
||||
import yaml
|
||||
|
||||
raw = yaml.safe_load(self.path.open(encoding="utf-8"))
|
||||
raw = yaml.safe_load(self.path.open())
|
||||
for name, spec in sorted(raw.items()):
|
||||
yield YamlItem.from_parent(self, name=name, spec=spec)
|
||||
|
||||
|
||||
@@ -483,8 +483,8 @@ argument sets to use for each test function. Let's run it:
|
||||
FAILED test_parametrize.py::TestClass::test_equals[1-2] - assert 1 == 2
|
||||
1 failed, 2 passed in 0.12s
|
||||
|
||||
Parametrization with multiple fixtures
|
||||
--------------------------------------
|
||||
Indirect parametrization with multiple fixtures
|
||||
--------------------------------------------------------------
|
||||
|
||||
Here is a stripped down real-life example of using parametrized
|
||||
testing for testing serialization of objects between different python
|
||||
@@ -509,8 +509,8 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
SKIPPED [9] multipython.py:69: 'python3.7' not found
|
||||
27 skipped in 0.12s
|
||||
|
||||
Parametrization of optional implementations/imports
|
||||
---------------------------------------------------
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
|
||||
If you want to compare the outcomes of several implementations of a given
|
||||
API, you can write test functions that receive the already imported implementations
|
||||
@@ -657,16 +657,13 @@ Use :func:`pytest.raises` with the
|
||||
:ref:`pytest.mark.parametrize ref` decorator to write parametrized tests
|
||||
in which some tests raise exceptions and others do not.
|
||||
|
||||
``contextlib.nullcontext`` can be used to test cases that are not expected to
|
||||
raise exceptions but that should result in some value. The value is given as the
|
||||
``enter_result`` parameter, which will be available as the ``with`` statement’s
|
||||
target (``e`` in the example below).
|
||||
It may be helpful to use ``nullcontext`` as a complement to ``raises``.
|
||||
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from contextlib import nullcontext
|
||||
from contextlib import nullcontext as does_not_raise
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -674,17 +671,16 @@ For example:
|
||||
@pytest.mark.parametrize(
|
||||
"example_input,expectation",
|
||||
[
|
||||
(3, nullcontext(2)),
|
||||
(2, nullcontext(3)),
|
||||
(1, nullcontext(6)),
|
||||
(3, does_not_raise()),
|
||||
(2, does_not_raise()),
|
||||
(1, does_not_raise()),
|
||||
(0, pytest.raises(ZeroDivisionError)),
|
||||
],
|
||||
)
|
||||
def test_division(example_input, expectation):
|
||||
"""Test how much I know division."""
|
||||
with expectation as e:
|
||||
assert (6 / example_input) == e
|
||||
with expectation:
|
||||
assert (6 / example_input) is not None
|
||||
|
||||
In the example above, the first three test cases should run without any
|
||||
exceptions, while the fourth should raise a``ZeroDivisionError`` exception,
|
||||
which is expected by pytest.
|
||||
In the example above, the first three test cases should run unexceptionally,
|
||||
while the fourth should raise ``ZeroDivisionError``.
|
||||
|
||||
@@ -808,15 +808,16 @@ case we just write some information out to a ``failures`` file:
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
# execute all other hooks to obtain the report object
|
||||
rep = yield
|
||||
outcome = yield
|
||||
rep = outcome.get_result()
|
||||
|
||||
# we only look at actual failing test calls, not setup/teardown
|
||||
if rep.when == "call" and rep.failed:
|
||||
mode = "a" if os.path.exists("failures") else "w"
|
||||
with open("failures", mode, encoding="utf-8") as f:
|
||||
with open("failures", mode) as f:
|
||||
# let's also access a fixture for the fun of it
|
||||
if "tmp_path" in item.fixturenames:
|
||||
extra = " ({})".format(item.funcargs["tmp_path"])
|
||||
@@ -825,8 +826,6 @@ case we just write some information out to a ``failures`` file:
|
||||
|
||||
f.write(rep.nodeid + extra + "\n")
|
||||
|
||||
return rep
|
||||
|
||||
|
||||
if you then have failing tests:
|
||||
|
||||
@@ -900,17 +899,16 @@ here is a little example implemented via a local plugin:
|
||||
phase_report_key = StashKey[Dict[str, CollectReport]]()
|
||||
|
||||
|
||||
@pytest.hookimpl(wrapper=True, tryfirst=True)
|
||||
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
# execute all other hooks to obtain the report object
|
||||
rep = yield
|
||||
outcome = yield
|
||||
rep = outcome.get_result()
|
||||
|
||||
# store test results for each phase of a call, which can
|
||||
# be "setup", "call", "teardown"
|
||||
item.stash.setdefault(phase_report_key, {})[rep.when] = rep
|
||||
|
||||
return rep
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def something(request):
|
||||
@@ -1090,4 +1088,4 @@ application with standard ``pytest`` command-line options:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
./app_main --pytest --verbose --tb=long --junit=xml=results.xml test-suite/
|
||||
./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/
|
||||
|
||||
@@ -34,7 +34,7 @@ a function/method call.
|
||||
|
||||
**Assert** is where we look at that resulting state and check if it looks how
|
||||
we'd expect after the dust has settled. It's where we gather evidence to say the
|
||||
behavior does or does not align with what we expect. The ``assert`` in our test
|
||||
behavior does or does not aligns with what we expect. The ``assert`` in our test
|
||||
is where we take that measurement/observation and apply our judgement to it. If
|
||||
something should be green, we'd say ``assert thing == "green"``.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Get Started
|
||||
Install ``pytest``
|
||||
----------------------------------------
|
||||
|
||||
``pytest`` requires: Python 3.8+ or PyPy3.
|
||||
``pytest`` requires: Python 3.7+ or PyPy3.
|
||||
|
||||
1. Run the following command in your command line:
|
||||
|
||||
@@ -22,7 +22,7 @@ Install ``pytest``
|
||||
.. code-block:: bash
|
||||
|
||||
$ pytest --version
|
||||
pytest 7.4.3
|
||||
pytest 7.3.2
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
@@ -97,30 +97,6 @@ Use the :ref:`raises <assertraises>` helper to assert that some code raises an e
|
||||
with pytest.raises(SystemExit):
|
||||
f()
|
||||
|
||||
You can also use the context provided by :ref:`raises <assertraises>` to
|
||||
assert that an expected exception is part of a raised ``ExceptionGroup``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_exceptiongroup.py
|
||||
import pytest
|
||||
|
||||
|
||||
def f():
|
||||
raise ExceptionGroup(
|
||||
"Group message",
|
||||
[
|
||||
RuntimeError(),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def test_exception_in_group():
|
||||
with pytest.raises(ExceptionGroup) as excinfo:
|
||||
f()
|
||||
assert excinfo.group_contains(RuntimeError)
|
||||
assert not excinfo.group_contains(TypeError)
|
||||
|
||||
Execute the test function with “quiet” reporting mode:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
@@ -54,13 +54,14 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the
|
||||
idiomatic python constructs without boilerplate code while not losing
|
||||
introspection information.
|
||||
|
||||
If a message is specified with the assertion like this:
|
||||
However, if you specify a message with the assertion like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert a % 2 == 0, "value was odd, should be even"
|
||||
|
||||
it is printed alongside the assertion introspection in the traceback.
|
||||
then no assertion introspection takes places at all and the message
|
||||
will be simply shown in the traceback.
|
||||
|
||||
See :ref:`assert-details` for more information on assertion introspection.
|
||||
|
||||
@@ -115,56 +116,10 @@ that a regular expression matches on the string representation of an exception
|
||||
with pytest.raises(ValueError, match=r".* 123 .*"):
|
||||
myfunc()
|
||||
|
||||
The regexp parameter of the ``match`` parameter is matched with the ``re.search``
|
||||
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
||||
function, so in the above example ``match='123'`` would have worked as
|
||||
well.
|
||||
|
||||
You can also use the :func:`excinfo.group_contains() <pytest.ExceptionInfo.group_contains>`
|
||||
method to test for exceptions returned as part of an ``ExceptionGroup``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_exception_in_group():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
raise ExceptionGroup(
|
||||
"Group message",
|
||||
[
|
||||
RuntimeError("Exception 123 raised"),
|
||||
],
|
||||
)
|
||||
assert excinfo.group_contains(RuntimeError, match=r".* 123 .*")
|
||||
assert not excinfo.group_contains(TypeError)
|
||||
|
||||
The optional ``match`` keyword parameter works the same way as for
|
||||
:func:`pytest.raises`.
|
||||
|
||||
By default ``group_contains()`` will recursively search for a matching
|
||||
exception at any level of nested ``ExceptionGroup`` instances. You can
|
||||
specify a ``depth`` keyword parameter if you only want to match an
|
||||
exception at a specific level; exceptions contained directly in the top
|
||||
``ExceptionGroup`` would match ``depth=1``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_exception_in_group_at_given_depth():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
raise ExceptionGroup(
|
||||
"Group message",
|
||||
[
|
||||
RuntimeError(),
|
||||
ExceptionGroup(
|
||||
"Nested group",
|
||||
[
|
||||
TypeError(),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
assert excinfo.group_contains(RuntimeError, depth=1)
|
||||
assert excinfo.group_contains(TypeError, depth=2)
|
||||
assert not excinfo.group_contains(RuntimeError, depth=2)
|
||||
assert not excinfo.group_contains(TypeError, depth=1)
|
||||
|
||||
There's an alternate form of the :func:`pytest.raises` function where you pass
|
||||
a function that will be executed with the given ``*args`` and ``**kwargs`` and
|
||||
assert that the given exception is raised:
|
||||
|
||||
+8
-15
@@ -176,21 +176,14 @@ with more recent files coming first.
|
||||
Behavior when no tests failed in the last run
|
||||
---------------------------------------------
|
||||
|
||||
The ``--lfnf/--last-failed-no-failures`` option governs the behavior of ``--last-failed``.
|
||||
Determines whether to execute tests when there are no previously (known)
|
||||
failures or when no cached ``lastfailed`` data was found.
|
||||
|
||||
There are two options:
|
||||
|
||||
* ``all``: when there are no known test failures, runs all tests (the full test suite). This is the default.
|
||||
* ``none``: when there are no known test failures, just emits a message stating this and exit successfully.
|
||||
|
||||
Example:
|
||||
When no tests failed in the last run, or when no cached ``lastfailed`` data was
|
||||
found, ``pytest`` can be configured either to run all of the tests or no tests,
|
||||
using the ``--last-failed-no-failures`` option, which takes one of the following values:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --last-failed --last-failed-no-failures all # runs the full test suite (default behavior)
|
||||
pytest --last-failed --last-failed-no-failures none # runs no tests and exits successfully
|
||||
pytest --last-failed --last-failed-no-failures all # run all tests (default behavior)
|
||||
pytest --last-failed --last-failed-no-failures none # run no tests and exit
|
||||
|
||||
The new config.cache object
|
||||
--------------------------------
|
||||
@@ -213,12 +206,12 @@ across pytest invocations:
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mydata(pytestconfig):
|
||||
val = pytestconfig.cache.get("example/value", None)
|
||||
def mydata(request):
|
||||
val = request.config.cache.get("example/value", None)
|
||||
if val is None:
|
||||
expensive_computation()
|
||||
val = 42
|
||||
pytestconfig.cache.set("example/value", val)
|
||||
request.config.cache.set("example/value", val)
|
||||
return val
|
||||
|
||||
|
||||
|
||||
@@ -135,6 +135,10 @@ Warning about unraisable exceptions and unhandled thread exceptions
|
||||
|
||||
.. versionadded:: 6.2
|
||||
|
||||
.. note::
|
||||
|
||||
These features only work on Python>=3.8.
|
||||
|
||||
Unhandled exceptions are exceptions that are raised in a situation in which
|
||||
they cannot propagate to a caller. The most common case is an exception raised
|
||||
in a :meth:`__del__ <object.__del__>` implementation.
|
||||
|
||||
@@ -1698,7 +1698,7 @@ and declare its use in a test module via a ``usefixtures`` marker:
|
||||
class TestDirectoryInit:
|
||||
def test_cwd_starts_empty(self):
|
||||
assert os.listdir(os.getcwd()) == []
|
||||
with open("myfile", "w", encoding="utf-8") as f:
|
||||
with open("myfile", "w") as f:
|
||||
f.write("hello")
|
||||
|
||||
def test_cwd_again_starts_empty(self):
|
||||
@@ -1752,7 +1752,8 @@ into an ini-file:
|
||||
def my_fixture_that_sadly_wont_use_my_other_fixture():
|
||||
...
|
||||
|
||||
This generates a deprecation warning, and will become an error in Pytest 8.
|
||||
Currently this will not generate any error or warning, but this is intended
|
||||
to be handled by :issue:`3664`.
|
||||
|
||||
.. _`override fixtures`:
|
||||
|
||||
|
||||
@@ -172,13 +172,6 @@ the records for the ``setup`` and ``call`` stages during teardown like so:
|
||||
|
||||
The full API is available at :class:`pytest.LogCaptureFixture`.
|
||||
|
||||
.. warning::
|
||||
|
||||
The ``caplog`` fixture adds a handler to the root logger to capture logs. If the root logger is
|
||||
modified during a test, for example with ``logging.config.dictConfig``, this handler may be
|
||||
removed and cause no logs to be captured. To avoid this, ensure that any root logger configuration
|
||||
only adds to the existing handlers.
|
||||
|
||||
|
||||
.. _live_logs:
|
||||
|
||||
|
||||
+4
-21
@@ -47,7 +47,8 @@ Unsupported idioms / known issues
|
||||
- nose imports test modules with the same import path (e.g.
|
||||
``tests.test_mode``) but different file system paths
|
||||
(e.g. ``tests/test_mode.py`` and ``other/tests/test_mode.py``)
|
||||
by extending sys.path/import semantics. pytest does not do that. Note that
|
||||
by extending sys.path/import semantics. pytest does not do that
|
||||
but there is discussion in :issue:`268` for adding some support. Note that
|
||||
`nose2 choose to avoid this sys.path/import hackery <https://nose2.readthedocs.io/en/latest/differences.html#test-discovery-and-loading>`_.
|
||||
|
||||
If you place a conftest.py file in the root directory of your project
|
||||
@@ -65,34 +66,16 @@ Unsupported idioms / known issues
|
||||
|
||||
- no nose-configuration is recognized.
|
||||
|
||||
- ``yield``-based methods are
|
||||
- ``yield``-based methods are unsupported as of pytest 4.1.0. They are
|
||||
fundamentally incompatible with pytest because they don't support fixtures
|
||||
properly since collection and test execution are separated.
|
||||
|
||||
Here is a table comparing the default supported naming conventions for both
|
||||
nose and pytest.
|
||||
|
||||
========= ========================== ======= =====
|
||||
what default naming convention pytest nose
|
||||
========= ========================== ======= =====
|
||||
module ``test*.py`` ✅
|
||||
module ``test_*.py`` ✅ ✅
|
||||
module ``*_test.py`` ✅
|
||||
module ``*_tests.py``
|
||||
class ``*(unittest.TestCase)`` ✅ ✅
|
||||
method ``test_*`` ✅ ✅
|
||||
class ``Test*`` ✅
|
||||
method ``test_*`` ✅
|
||||
function ``test_*`` ✅
|
||||
========= ========================== ======= =====
|
||||
|
||||
|
||||
Migrating from nose to pytest
|
||||
------------------------------
|
||||
|
||||
`nose2pytest <https://github.com/pytest-dev/nose2pytest>`_ is a Python script
|
||||
and pytest plugin to help convert Nose-based tests into pytest-based tests.
|
||||
Specifically, the script transforms ``nose.tools.assert_*`` function calls into
|
||||
Specifically, the script transforms nose.tools.assert_* function calls into
|
||||
raw assert statements, while preserving format of original arguments
|
||||
as much as possible.
|
||||
|
||||
|
||||
@@ -16,12 +16,6 @@ Examples for modifying traceback printing:
|
||||
pytest -l # show local variables (shortcut)
|
||||
pytest --no-showlocals # hide local variables (if addopts enables them)
|
||||
|
||||
pytest --capture=fd # default, capture at the file descriptor level
|
||||
pytest --capture=sys # capture at the sys level
|
||||
pytest --capture=no # don't capture
|
||||
pytest -s # don't capture (shortcut)
|
||||
pytest --capture=tee-sys # capture to logs but also output to sys level streams
|
||||
|
||||
pytest --tb=auto # (default) 'long' tracebacks for the first and last
|
||||
# entry, but 'short' style for the other entries
|
||||
pytest --tb=long # exhaustive, informative traceback formatting
|
||||
@@ -42,16 +36,6 @@ option you make sure a trace is shown.
|
||||
Verbosity
|
||||
--------------------------------------------------
|
||||
|
||||
Examples for modifying printing verbosity:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --quiet # quiet - less verbose - mode
|
||||
pytest -q # quiet - less verbose - mode (shortcut)
|
||||
pytest -v # increase verbosity, display individual test names
|
||||
pytest -vv # more verbose, display more details from the test output
|
||||
pytest -vvv # not a standard , but may be used for even more detail in certain setups
|
||||
|
||||
The ``-v`` flag controls the verbosity of pytest output in various aspects: test session progress, assertion
|
||||
details when tests fail, fixtures details with ``--fixtures``, etc.
|
||||
|
||||
@@ -494,7 +478,7 @@ integration servers, use this invocation:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest --junit-xml=path
|
||||
pytest --junitxml=path
|
||||
|
||||
to create an XML file at ``path``.
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ created in the `base temporary directory`_.
|
||||
d = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
p = d / "hello.txt"
|
||||
p.write_text(CONTENT, encoding="utf-8")
|
||||
assert p.read_text(encoding="utf-8") == CONTENT
|
||||
p.write_text(CONTENT)
|
||||
assert p.read_text() == CONTENT
|
||||
assert len(list(tmp_path.iterdir())) == 1
|
||||
assert 0
|
||||
|
||||
@@ -51,8 +51,8 @@ Running this would result in a passed test except for the last
|
||||
d = tmp_path / "sub"
|
||||
d.mkdir()
|
||||
p = d / "hello.txt"
|
||||
p.write_text(CONTENT, encoding="utf-8")
|
||||
assert p.read_text(encoding="utf-8") == CONTENT
|
||||
p.write_text(CONTENT)
|
||||
assert p.read_text() == CONTENT
|
||||
assert len(list(tmp_path.iterdir())) == 1
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
@@ -207,10 +207,10 @@ creation of a per-test temporary directory:
|
||||
@pytest.fixture(autouse=True)
|
||||
def initdir(self, tmp_path, monkeypatch):
|
||||
monkeypatch.chdir(tmp_path) # change to pytest-provided temporary directory
|
||||
tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")
|
||||
tmp_path.joinpath("samplefile.ini").write_text("# testdata")
|
||||
|
||||
def test_method(self):
|
||||
with open("samplefile.ini", encoding="utf-8") as f:
|
||||
with open("samplefile.ini") as f:
|
||||
s = f.read()
|
||||
assert "testdata" in s
|
||||
|
||||
|
||||
+8
-20
@@ -44,34 +44,23 @@ Use ``""`` instead of ``''`` in expression when running this on Windows
|
||||
|
||||
.. _nodeids:
|
||||
|
||||
**Run tests by collection arguments**
|
||||
**Run tests by node ids**
|
||||
|
||||
Pass the module filename relative to the working directory, followed by specifiers like the class name and function name
|
||||
separated by ``::`` characters, and parameters from parameterization enclosed in ``[]``.
|
||||
Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
|
||||
by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
|
||||
|
||||
To run a specific test within a module:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest tests/test_mod.py::test_func
|
||||
pytest test_mod.py::test_func
|
||||
|
||||
To run all tests in a class:
|
||||
|
||||
Another example specifying a test method in the command line:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest tests/test_mod.py::TestClass
|
||||
|
||||
Specifying a specific test method:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest tests/test_mod.py::TestClass::test_method
|
||||
|
||||
Specifying a specific parametrization of a test:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest tests/test_mod.py::test_func[x1,y2]
|
||||
pytest test_mod.py::TestClass::test_method
|
||||
|
||||
**Run tests by marker expressions**
|
||||
|
||||
@@ -184,8 +173,7 @@ You can invoke ``pytest`` from Python code directly:
|
||||
|
||||
this acts as if you would call "pytest" from the command line.
|
||||
It will not raise :class:`SystemExit` but return the :ref:`exit code <exit-codes>` instead.
|
||||
If you don't pass it any arguments, ``main`` reads the arguments from the command line arguments of the process (:data:`sys.argv`), which may be undesirable.
|
||||
You can pass in options and arguments explicitly:
|
||||
You can pass in options and arguments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ The remaining hook functions will not be called in this case.
|
||||
|
||||
.. _`hookwrapper`:
|
||||
|
||||
hook wrappers: executing around other hooks
|
||||
hookwrapper: executing around other hooks
|
||||
-------------------------------------------------
|
||||
|
||||
.. currentmodule:: _pytest.core
|
||||
@@ -69,8 +69,10 @@ which yields exactly once. When pytest invokes hooks it first executes
|
||||
hook wrappers and passes the same arguments as to the regular hooks.
|
||||
|
||||
At the yield point of the hook wrapper pytest will execute the next hook
|
||||
implementations and return their result to the yield point, or will
|
||||
propagate an exception if they raised.
|
||||
implementations and return their result to the yield point in the form of
|
||||
a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
|
||||
exception info. The yield point itself will thus typically not raise
|
||||
exceptions (unless there are bugs).
|
||||
|
||||
Here is an example definition of a hook wrapper:
|
||||
|
||||
@@ -79,35 +81,26 @@ Here is an example definition of a hook wrapper:
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.hookimpl(wrapper=True)
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
do_something_before_next_hook_executes()
|
||||
|
||||
# If the outcome is an exception, will raise the exception.
|
||||
res = yield
|
||||
outcome = yield
|
||||
# outcome.excinfo may be None or a (cls, val, tb) tuple
|
||||
|
||||
new_res = post_process_result(res)
|
||||
res = outcome.get_result() # will raise if outcome was exception
|
||||
|
||||
# Override the return value to the plugin system.
|
||||
return new_res
|
||||
post_process_result(res)
|
||||
|
||||
The hook wrapper needs to return a result for the hook, or raise an exception.
|
||||
outcome.force_result(new_res) # to override the return value to the plugin system
|
||||
|
||||
In many cases, the wrapper only needs to perform tracing or other side effects
|
||||
around the actual hook implementations, in which case it can return the result
|
||||
value of the ``yield``. The simplest (though useless) hook wrapper is
|
||||
``return (yield)``.
|
||||
|
||||
In other cases, the wrapper wants the adjust or adapt the result, in which case
|
||||
it can return a new value. If the result of the underlying hook is a mutable
|
||||
object, the wrapper may modify that result, but it's probably better to avoid it.
|
||||
|
||||
If the hook implementation failed with an exception, the wrapper can handle that
|
||||
exception using a ``try-catch-finally`` around the ``yield``, by propagating it,
|
||||
supressing it, or raising a different exception entirely.
|
||||
Note that hook wrappers don't return results themselves, they merely
|
||||
perform tracing or other side effects around the actual hook implementations.
|
||||
If the result of the underlying hook is a mutable object, they may modify
|
||||
that result but it's probably better to avoid it.
|
||||
|
||||
For more information, consult the
|
||||
:ref:`pluggy documentation about hook wrappers <pluggy:hookwrappers>`.
|
||||
:ref:`pluggy documentation about hookwrappers <pluggy:hookwrappers>`.
|
||||
|
||||
.. _plugin-hookorder:
|
||||
|
||||
@@ -137,14 +130,11 @@ after others, i.e. the position in the ``N``-sized list of functions:
|
||||
|
||||
|
||||
# Plugin 3
|
||||
@pytest.hookimpl(wrapper=True)
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_collection_modifyitems(items):
|
||||
# will execute even before the tryfirst one above!
|
||||
try:
|
||||
return (yield)
|
||||
finally:
|
||||
# will execute after all non-wrappers executed
|
||||
...
|
||||
outcome = yield
|
||||
# will execute after all non-hookwrappers executed
|
||||
|
||||
Here is the order of execution:
|
||||
|
||||
@@ -159,11 +149,12 @@ Here is the order of execution:
|
||||
Plugin1).
|
||||
|
||||
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
|
||||
point. The yield receives the result from calling the non-wrappers, or raises
|
||||
an exception if the non-wrappers raised.
|
||||
point. The yield receives a :py:class:`Result <pluggy._Result>` instance which encapsulates
|
||||
the result from calling the non-wrappers. Wrappers shall not modify the result.
|
||||
|
||||
It's possible to use ``tryfirst`` and ``trylast`` also on hook wrappers
|
||||
in which case it will influence the ordering of hook wrappers among each other.
|
||||
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
|
||||
``hookwrapper=True`` in which case it will influence the ordering of hookwrappers
|
||||
among each other.
|
||||
|
||||
|
||||
Declaring new hooks
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
.. sidebar:: Next Open Trainings
|
||||
|
||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, **March 5th to 7th 2024** (3 day in-depth training), **Leipzig, Germany / Remote**
|
||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote
|
||||
|
||||
Also see :doc:`previous talks and blogposts <talks>`.
|
||||
|
||||
@@ -17,7 +17,7 @@ The ``pytest`` framework makes it easy to write small, readable tests, and can
|
||||
scale to support complex functional testing for applications and libraries.
|
||||
|
||||
|
||||
``pytest`` requires: Python 3.8+ or PyPy3.
|
||||
``pytest`` requires: Python 3.7+ or PyPy3.
|
||||
|
||||
**PyPI package name**: :pypi:`pytest`
|
||||
|
||||
@@ -76,7 +76,7 @@ Features
|
||||
|
||||
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box
|
||||
|
||||
- Python 3.8+ or PyPy 3
|
||||
- Python 3.7+ or PyPy 3
|
||||
|
||||
- Rich plugin architecture, with over 800+ :ref:`external plugins <plugin-list>` and thriving community
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se
|
||||
setup.cfg
|
||||
~~~~~~~~~
|
||||
|
||||
``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and `setuptools <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`__, and can also be used to hold pytest configuration
|
||||
``setup.cfg`` files are general purpose configuration files, used originally by :doc:`distutils <python:distutils/configfile>`, and can also be used to hold pytest configuration
|
||||
if they have a ``[tool:pytest]`` section.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
+473
-1079
File diff suppressed because it is too large
Load Diff
+78
-110
@@ -1,5 +1,3 @@
|
||||
:tocdepth: 3
|
||||
|
||||
.. _`api-reference`:
|
||||
|
||||
API Reference
|
||||
@@ -84,8 +82,6 @@ pytest.exit
|
||||
pytest.main
|
||||
~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`pytest.main-usage`
|
||||
|
||||
.. autofunction:: pytest.main
|
||||
|
||||
pytest.param
|
||||
@@ -239,7 +235,7 @@ pytest.mark.xfail
|
||||
|
||||
Marks a test function as *expected to fail*.
|
||||
|
||||
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=xfail_strict)
|
||||
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
|
||||
|
||||
:type condition: bool or str
|
||||
:param condition:
|
||||
@@ -251,10 +247,10 @@ Marks a test function as *expected to fail*.
|
||||
:keyword Type[Exception] raises:
|
||||
Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test.
|
||||
:keyword bool run:
|
||||
Whether the test function should actually be executed. If ``False``, the function will always xfail and will
|
||||
If the test function should actually be executed. If ``False``, the function will always xfail and will
|
||||
not be executed (useful if a function is segfaulting).
|
||||
:keyword bool strict:
|
||||
* If ``False`` the function will be shown in the terminal output as ``xfailed`` if it fails
|
||||
* If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails
|
||||
and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
|
||||
is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
|
||||
* If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
|
||||
@@ -262,8 +258,6 @@ Marks a test function as *expected to fail*.
|
||||
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
|
||||
a new release of a library fixes a known bug).
|
||||
|
||||
Defaults to :confval:`xfail_strict`, which is ``False`` by default.
|
||||
|
||||
|
||||
Custom marks
|
||||
~~~~~~~~~~~~
|
||||
@@ -789,66 +783,18 @@ reporting or interaction with exceptions:
|
||||
.. autofunction:: pytest_leave_pdb
|
||||
|
||||
|
||||
Collection tree objects
|
||||
-----------------------
|
||||
Objects
|
||||
-------
|
||||
|
||||
These are the collector and item classes (collectively called "nodes") which
|
||||
make up the collection tree.
|
||||
Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`.
|
||||
|
||||
Node
|
||||
~~~~
|
||||
|
||||
.. autoclass:: _pytest.nodes.Node()
|
||||
CallInfo
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.CallInfo()
|
||||
:members:
|
||||
|
||||
Collector
|
||||
~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Collector()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Item
|
||||
~~~~
|
||||
|
||||
.. autoclass:: pytest.Item()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
File
|
||||
~~~~
|
||||
|
||||
.. autoclass:: pytest.File()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
FSCollector
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.nodes.FSCollector()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Session
|
||||
~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Session()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Package
|
||||
~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Package()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Module
|
||||
~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Module()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Class
|
||||
~~~~~
|
||||
@@ -857,34 +803,13 @@ Class
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Function
|
||||
~~~~~~~~
|
||||
Collector
|
||||
~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Function()
|
||||
.. autoclass:: pytest.Collector()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
FunctionDefinition
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.python.FunctionDefinition()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
Objects
|
||||
-------
|
||||
|
||||
Objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`
|
||||
or importable from ``pytest``.
|
||||
|
||||
|
||||
CallInfo
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.CallInfo()
|
||||
:members:
|
||||
|
||||
CollectReport
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
@@ -912,11 +837,46 @@ ExitCode
|
||||
.. autoclass:: pytest.ExitCode
|
||||
:members:
|
||||
|
||||
File
|
||||
~~~~
|
||||
|
||||
.. autoclass:: pytest.File()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
FixtureDef
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.FixtureDef()
|
||||
.. autoclass:: _pytest.fixtures.FixtureDef()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
FSCollector
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.nodes.FSCollector()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Function
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Function()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
FunctionDefinition
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.python.FunctionDefinition()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Item
|
||||
~~~~
|
||||
|
||||
.. autoclass:: pytest.Item()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -947,6 +907,19 @@ Metafunc
|
||||
.. autoclass:: pytest.Metafunc()
|
||||
:members:
|
||||
|
||||
Module
|
||||
~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Module()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Node
|
||||
~~~~
|
||||
|
||||
.. autoclass:: _pytest.nodes.Node()
|
||||
:members:
|
||||
|
||||
Parser
|
||||
~~~~~~
|
||||
|
||||
@@ -968,6 +941,13 @@ PytestPluginManager
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
Session
|
||||
~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Session()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
TestReport
|
||||
~~~~~~~~~~
|
||||
|
||||
@@ -982,10 +962,10 @@ TestShortLogReport
|
||||
.. autoclass:: pytest.TestShortLogReport()
|
||||
:members:
|
||||
|
||||
Result
|
||||
_Result
|
||||
~~~~~~~
|
||||
|
||||
Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`Result in the pluggy documentation <pluggy.Result>` for more information.
|
||||
Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`_Result in the pluggy documentation <pluggy._callers._Result>` for more information.
|
||||
|
||||
Stash
|
||||
~~~~~
|
||||
@@ -1173,9 +1153,6 @@ Custom warnings generated in some situations such as improper usage or deprecate
|
||||
.. autoclass:: pytest.PytestRemovedIn8Warning
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pytest.PytestRemovedIn9Warning
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
|
||||
:show-inheritance:
|
||||
|
||||
@@ -1642,11 +1619,11 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
|
||||
virtualenv by the presence of an activation script. Any directory deemed to
|
||||
be the root of a virtual environment will not be considered during test
|
||||
collection unless ``--collect-in-virtualenv`` is given. Note also that
|
||||
``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if
|
||||
collection unless ``‑‑collect‑in‑virtualenv`` is given. Note also that
|
||||
``norecursedirs`` takes precedence over ``‑‑collect‑in‑virtualenv``; e.g. if
|
||||
you intend to run tests in a virtualenv with a base directory that matches
|
||||
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
|
||||
``--collect-in-virtualenv`` flag.
|
||||
``‑‑collect‑in‑virtualenv`` flag.
|
||||
|
||||
|
||||
.. confval:: python_classes
|
||||
@@ -1726,11 +1703,6 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
[pytest]
|
||||
pythonpath = src1 src2
|
||||
|
||||
.. note::
|
||||
|
||||
``pythonpath`` does not affect some imports that happen very early,
|
||||
most notably plugins loaded using the ``-p`` command line option.
|
||||
|
||||
|
||||
.. confval:: required_plugins
|
||||
|
||||
@@ -1894,12 +1866,8 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
tests. Optional argument: glob (default: '*').
|
||||
--cache-clear Remove all cache contents at start of test run
|
||||
--lfnf={all,none}, --last-failed-no-failures={all,none}
|
||||
With ``--lf``, determines whether to execute tests
|
||||
when there are no previously (known) failures or
|
||||
when no cached ``lastfailed`` data was found.
|
||||
``all`` (the default) runs the full test suite
|
||||
again. ``none`` just emits a message about no known
|
||||
failures and exits successfully.
|
||||
Which tests to run with no previously (known)
|
||||
failures
|
||||
--sw, --stepwise Exit on test failure and continue from last failing
|
||||
test next time
|
||||
--sw-skip, --stepwise-skip
|
||||
@@ -1950,9 +1918,9 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
--strict-markers Markers not registered in the `markers` section of
|
||||
the configuration file raise errors
|
||||
--strict (Deprecated) alias to --strict-markers
|
||||
-c FILE, --config-file=FILE
|
||||
-c, --config-file FILE
|
||||
Load configuration from `FILE` instead of trying to
|
||||
locate one of the implicit configuration files.
|
||||
locate one of the implicit configuration files
|
||||
--continue-on-collection-errors
|
||||
Force test execution even if collection errors occur
|
||||
--rootdir=ROOTDIR Define root directory for tests. Can be relative
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pallets-sphinx-themes
|
||||
pluggy>=1.2.0
|
||||
pluggy>=1.0
|
||||
pygments-pytest>=2.3.0
|
||||
sphinx-removed-in>=0.2.0
|
||||
sphinx>=5,<8
|
||||
sphinx>=5,<6
|
||||
sphinxcontrib-trio
|
||||
sphinxcontrib-svg2pdfconverter
|
||||
# Pin packaging because it no longer handles 'latest' version, which
|
||||
|
||||
+2
-7
@@ -17,12 +17,7 @@ python_classes = ["Test", "Acceptance"]
|
||||
python_functions = ["test"]
|
||||
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||
testpaths = ["testing"]
|
||||
norecursedirs = [
|
||||
"testing/example_scripts",
|
||||
".*",
|
||||
"build",
|
||||
"dist",
|
||||
]
|
||||
norecursedirs = ["testing/example_scripts"]
|
||||
xfail_strict = true
|
||||
filterwarnings = [
|
||||
"error",
|
||||
@@ -118,7 +113,7 @@ template = "changelog/_template.rst"
|
||||
showcontent = true
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38']
|
||||
target-version = ['py37']
|
||||
|
||||
# check-wheel-contents is executed by the build-and-inspect-python-package action.
|
||||
[tool.check-wheel-contents]
|
||||
|
||||
@@ -31,22 +31,10 @@ class InvalidFeatureRelease(Exception):
|
||||
SLUG = "pytest-dev/pytest"
|
||||
|
||||
PR_BODY = """\
|
||||
Created by the [prepare release pr]\
|
||||
(https://github.com/pytest-dev/pytest/actions/workflows/prepare-release-pr.yml) workflow.
|
||||
Created automatically from manual trigger.
|
||||
|
||||
Once all builds pass and it has been **approved** by one or more maintainers, start the \
|
||||
[deploy](https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml) workflow, using these parameters:
|
||||
|
||||
* `Use workflow from`: `release-{version}`.
|
||||
* `Release version`: `{version}`.
|
||||
|
||||
Or execute on the command line:
|
||||
|
||||
```console
|
||||
gh workflow run deploy.yml -r release-{version} -f version={version}
|
||||
```
|
||||
|
||||
After the workflow has been approved by a core maintainer, the package will be uploaded to PyPI automatically.
|
||||
Once all builds pass and it has been **approved** by one or more maintainers, the build
|
||||
can be released by pushing a tag `{version}` to this repository.
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -7,9 +7,7 @@ def main():
|
||||
Platform agnostic wrapper script for towncrier.
|
||||
Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs.
|
||||
"""
|
||||
with open(
|
||||
"doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8"
|
||||
) as draft_file:
|
||||
with open("doc/en/_changelog_towncrier_draft.rst", "w") as draft_file:
|
||||
return call(("towncrier", "--draft"), stdout=draft_file)
|
||||
|
||||
|
||||
|
||||
@@ -5,41 +5,22 @@ from textwrap import dedent
|
||||
from textwrap import indent
|
||||
|
||||
import packaging.version
|
||||
import platformdirs
|
||||
import requests
|
||||
import tabulate
|
||||
import wcwidth
|
||||
from requests_cache import CachedResponse
|
||||
from requests_cache import CachedSession
|
||||
from requests_cache import OriginalResponse
|
||||
from requests_cache import SQLiteCache
|
||||
from tqdm import tqdm
|
||||
|
||||
|
||||
FILE_HEAD = r"""
|
||||
.. Note this file is autogenerated by scripts/update-plugin-list.py - usually weekly via github action
|
||||
|
||||
.. _plugin-list:
|
||||
|
||||
Pytest Plugin List
|
||||
==================
|
||||
Plugin List
|
||||
===========
|
||||
|
||||
Below is an automated compilation of ``pytest``` plugins available on `PyPI <https://pypi.org>`_.
|
||||
It includes PyPI projects whose names begin with "pytest-" and a handful of manually selected projects.
|
||||
PyPI projects that match "pytest-\*" are considered plugins and are listed
|
||||
automatically together with a manually-maintained list in `the source
|
||||
code <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
|
||||
Packages classified as inactive are excluded.
|
||||
|
||||
For detailed insights into how this list is generated,
|
||||
please refer to `the update script <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
|
||||
|
||||
.. warning::
|
||||
|
||||
Please be aware that this list is not a curated collection of projects
|
||||
and does not undergo a systematic review process.
|
||||
It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins.
|
||||
|
||||
Do not presume any endorsement from the ``pytest`` project or its developers,
|
||||
and always conduct your own quality assessment before incorporating any of these plugins into your own projects.
|
||||
|
||||
|
||||
.. The following conditional uses a different format for this list when
|
||||
creating a PDF, because otherwise the table gets far too wide for the
|
||||
page.
|
||||
@@ -56,8 +37,6 @@ DEVELOPMENT_STATUS_CLASSIFIERS = (
|
||||
)
|
||||
ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins
|
||||
"logassert",
|
||||
"nuts",
|
||||
"flask_fixture",
|
||||
}
|
||||
|
||||
|
||||
@@ -74,47 +53,19 @@ def escape_rst(text: str) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def project_response_with_refresh(
|
||||
session: CachedSession, name: str, last_serial: int
|
||||
) -> OriginalResponse | CachedResponse:
|
||||
"""Get a http cached pypi project
|
||||
|
||||
force refresh in case of last serial mismatch
|
||||
"""
|
||||
|
||||
response = session.get(f"https://pypi.org/pypi/{name}/json")
|
||||
if int(response.headers.get("X-PyPI-Last-Serial", -1)) != last_serial:
|
||||
response = session.get(f"https://pypi.org/pypi/{name}/json", refresh=True)
|
||||
return response
|
||||
|
||||
|
||||
def get_session() -> CachedSession:
|
||||
"""Configures the requests-cache session"""
|
||||
cache_path = platformdirs.user_cache_path("pytest-plugin-list")
|
||||
cache_path.mkdir(exist_ok=True, parents=True)
|
||||
cache_file = cache_path.joinpath("http_cache.sqlite3")
|
||||
return CachedSession(backend=SQLiteCache(cache_file))
|
||||
|
||||
|
||||
def pytest_plugin_projects_from_pypi(session: CachedSession) -> dict[str, int]:
|
||||
response = session.get(
|
||||
"https://pypi.org/simple",
|
||||
headers={"Accept": "application/vnd.pypi.simple.v1+json"},
|
||||
refresh=True,
|
||||
)
|
||||
return {
|
||||
name: p["_last-serial"]
|
||||
for p in response.json()["projects"]
|
||||
if (name := p["name"]).startswith("pytest-") or name in ADDITIONAL_PROJECTS
|
||||
}
|
||||
|
||||
|
||||
def iter_plugins():
|
||||
session = get_session()
|
||||
name_2_serial = pytest_plugin_projects_from_pypi(session)
|
||||
regex = r">([\d\w-]*)</a>"
|
||||
response = requests.get("https://pypi.org/simple")
|
||||
|
||||
for name, last_serial in tqdm(name_2_serial.items(), smoothing=0):
|
||||
response = project_response_with_refresh(session, name, last_serial)
|
||||
match_names = (match.groups()[0] for match in re.finditer(regex, response.text))
|
||||
plugin_names = [
|
||||
name
|
||||
for name in match_names
|
||||
if name.startswith("pytest-") or name in ADDITIONAL_PROJECTS
|
||||
]
|
||||
|
||||
for name in tqdm(plugin_names, smoothing=0):
|
||||
response = requests.get(f"https://pypi.org/pypi/{name}/json")
|
||||
if response.status_code == 404:
|
||||
# Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple
|
||||
# but return 404 on the JSON API. Skip.
|
||||
@@ -185,7 +136,7 @@ def plugin_definitions(plugins):
|
||||
|
||||
|
||||
def main():
|
||||
plugins = [*iter_plugins()]
|
||||
plugins = list(iter_plugins())
|
||||
|
||||
reference_dir = pathlib.Path("doc", "en", "reference")
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ classifiers =
|
||||
Operating System :: POSIX
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3 :: Only
|
||||
Programming Language :: Python :: 3.7
|
||||
Programming Language :: Python :: 3.8
|
||||
Programming Language :: Python :: 3.9
|
||||
Programming Language :: Python :: 3.10
|
||||
@@ -46,11 +47,12 @@ py_modules = py
|
||||
install_requires =
|
||||
iniconfig
|
||||
packaging
|
||||
pluggy>=1.3.0,<2.0
|
||||
pluggy>=0.12,<2.0
|
||||
colorama;sys_platform=="win32"
|
||||
exceptiongroup>=1.0.0rc8;python_version<"3.11"
|
||||
importlib-metadata>=0.12;python_version<"3.8"
|
||||
tomli>=1.0.0;python_version<"3.11"
|
||||
python_requires = >=3.8
|
||||
python_requires = >=3.7
|
||||
package_dir =
|
||||
=src
|
||||
setup_requires =
|
||||
|
||||
+20
-88
@@ -17,21 +17,18 @@ from typing import Any
|
||||
from typing import Callable
|
||||
from typing import ClassVar
|
||||
from typing import Dict
|
||||
from typing import Final
|
||||
from typing import final
|
||||
from typing import Generic
|
||||
from typing import Iterable
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Mapping
|
||||
from typing import Optional
|
||||
from typing import overload
|
||||
from typing import Pattern
|
||||
from typing import Sequence
|
||||
from typing import Set
|
||||
from typing import SupportsIndex
|
||||
from typing import Tuple
|
||||
from typing import Type
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TypeVar
|
||||
from typing import Union
|
||||
|
||||
@@ -45,16 +42,22 @@ from _pytest._code.source import Source
|
||||
from _pytest._io import TerminalWriter
|
||||
from _pytest._io.saferepr import safeformat
|
||||
from _pytest._io.saferepr import saferepr
|
||||
from _pytest.compat import final
|
||||
from _pytest.compat import get_real_func
|
||||
from _pytest.deprecated import check_ispytest
|
||||
from _pytest.pathlib import absolutepath
|
||||
from _pytest.pathlib import bestrelpath
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Final
|
||||
from typing_extensions import Literal
|
||||
from typing_extensions import SupportsIndex
|
||||
|
||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||
|
||||
if sys.version_info[:2] < (3, 11):
|
||||
from exceptiongroup import BaseExceptionGroup
|
||||
|
||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||
|
||||
|
||||
class Code:
|
||||
"""Wrapper around Python code objects."""
|
||||
@@ -393,11 +396,11 @@ class Traceback(List[TracebackEntry]):
|
||||
|
||||
def filter(
|
||||
self,
|
||||
excinfo_or_fn: Union[
|
||||
# TODO(py38): change to positional only.
|
||||
_excinfo_or_fn: Union[
|
||||
"ExceptionInfo[BaseException]",
|
||||
Callable[[TracebackEntry], bool],
|
||||
],
|
||||
/,
|
||||
) -> "Traceback":
|
||||
"""Return a Traceback instance with certain items removed.
|
||||
|
||||
@@ -408,10 +411,10 @@ class Traceback(List[TracebackEntry]):
|
||||
``TracebackEntry`` instance, and should return True when the item should
|
||||
be added to the ``Traceback``, False when not.
|
||||
"""
|
||||
if isinstance(excinfo_or_fn, ExceptionInfo):
|
||||
fn = lambda x: not x.ishidden(excinfo_or_fn) # noqa: E731
|
||||
if isinstance(_excinfo_or_fn, ExceptionInfo):
|
||||
fn = lambda x: not x.ishidden(_excinfo_or_fn) # noqa: E731
|
||||
else:
|
||||
fn = excinfo_or_fn
|
||||
fn = _excinfo_or_fn
|
||||
return Traceback(filter(fn, self))
|
||||
|
||||
def recursionindex(self) -> Optional[int]:
|
||||
@@ -630,7 +633,7 @@ class ExceptionInfo(Generic[E]):
|
||||
def getrepr(
|
||||
self,
|
||||
showlocals: bool = False,
|
||||
style: _TracebackStyle = "long",
|
||||
style: "_TracebackStyle" = "long",
|
||||
abspath: bool = False,
|
||||
tbfilter: Union[
|
||||
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
||||
@@ -697,14 +700,6 @@ class ExceptionInfo(Generic[E]):
|
||||
)
|
||||
return fmt.repr_excinfo(self)
|
||||
|
||||
def _stringify_exception(self, exc: BaseException) -> str:
|
||||
return "\n".join(
|
||||
[
|
||||
str(exc),
|
||||
*getattr(exc, "__notes__", []),
|
||||
]
|
||||
)
|
||||
|
||||
def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]":
|
||||
"""Check whether the regular expression `regexp` matches the string
|
||||
representation of the exception using :func:`python:re.search`.
|
||||
@@ -712,7 +707,7 @@ class ExceptionInfo(Generic[E]):
|
||||
If it matches `True` is returned, otherwise an `AssertionError` is raised.
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
value = self._stringify_exception(self.value)
|
||||
value = str(self.value)
|
||||
msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
|
||||
if regexp == value:
|
||||
msg += "\n Did you mean to `re.escape()` the regex?"
|
||||
@@ -720,69 +715,6 @@ class ExceptionInfo(Generic[E]):
|
||||
# Return True to allow for "assert excinfo.match()".
|
||||
return True
|
||||
|
||||
def _group_contains(
|
||||
self,
|
||||
exc_group: BaseExceptionGroup[BaseException],
|
||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||
match: Union[str, Pattern[str], None],
|
||||
target_depth: Optional[int] = None,
|
||||
current_depth: int = 1,
|
||||
) -> bool:
|
||||
"""Return `True` if a `BaseExceptionGroup` contains a matching exception."""
|
||||
if (target_depth is not None) and (current_depth > target_depth):
|
||||
# already descended past the target depth
|
||||
return False
|
||||
for exc in exc_group.exceptions:
|
||||
if isinstance(exc, BaseExceptionGroup):
|
||||
if self._group_contains(
|
||||
exc, expected_exception, match, target_depth, current_depth + 1
|
||||
):
|
||||
return True
|
||||
if (target_depth is not None) and (current_depth != target_depth):
|
||||
# not at the target depth, no match
|
||||
continue
|
||||
if not isinstance(exc, expected_exception):
|
||||
continue
|
||||
if match is not None:
|
||||
value = self._stringify_exception(exc)
|
||||
if not re.search(match, value):
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
def group_contains(
|
||||
self,
|
||||
expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]],
|
||||
*,
|
||||
match: Union[str, Pattern[str], None] = None,
|
||||
depth: Optional[int] = None,
|
||||
) -> bool:
|
||||
"""Check whether a captured exception group contains a matching exception.
|
||||
|
||||
:param Type[BaseException] | Tuple[Type[BaseException]] expected_exception:
|
||||
The expected exception type, or a tuple if one of multiple possible
|
||||
exception types are expected.
|
||||
|
||||
:param str | Pattern[str] | None match:
|
||||
If specified, a string containing a regular expression,
|
||||
or a regular expression object, that is tested against the string
|
||||
representation of the exception and its `PEP-678 <https://peps.python.org/pep-0678/>` `__notes__`
|
||||
using :func:`re.search`.
|
||||
|
||||
To match a literal string that may contain :ref:`special characters
|
||||
<re-syntax>`, the pattern can first be escaped with :func:`re.escape`.
|
||||
|
||||
:param Optional[int] depth:
|
||||
If `None`, will search for a matching exception at any nesting depth.
|
||||
If >= 1, will only match an exception if it's at the specified depth (depth = 1 being
|
||||
the exceptions contained within the topmost exception group).
|
||||
"""
|
||||
msg = "Captured exception is not an instance of `BaseExceptionGroup`"
|
||||
assert isinstance(self.value, BaseExceptionGroup), msg
|
||||
msg = "`depth` must be >= 1 if specified"
|
||||
assert (depth is None) or (depth >= 1), msg
|
||||
return self._group_contains(self.value, expected_exception, match, depth)
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class FormattedExcinfo:
|
||||
@@ -793,7 +725,7 @@ class FormattedExcinfo:
|
||||
fail_marker: ClassVar = "E"
|
||||
|
||||
showlocals: bool = False
|
||||
style: _TracebackStyle = "long"
|
||||
style: "_TracebackStyle" = "long"
|
||||
abspath: bool = True
|
||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
||||
funcargs: bool = False
|
||||
@@ -1158,7 +1090,7 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||
class ReprTraceback(TerminalRepr):
|
||||
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
||||
extraline: Optional[str]
|
||||
style: _TracebackStyle
|
||||
style: "_TracebackStyle"
|
||||
|
||||
entrysep: ClassVar = "_ "
|
||||
|
||||
@@ -1192,7 +1124,7 @@ class ReprTracebackNative(ReprTraceback):
|
||||
class ReprEntryNative(TerminalRepr):
|
||||
lines: Sequence[str]
|
||||
|
||||
style: ClassVar[_TracebackStyle] = "native"
|
||||
style: ClassVar["_TracebackStyle"] = "native"
|
||||
|
||||
def toterminal(self, tw: TerminalWriter) -> None:
|
||||
tw.write("".join(self.lines))
|
||||
@@ -1204,7 +1136,7 @@ class ReprEntry(TerminalRepr):
|
||||
reprfuncargs: Optional["ReprFuncArgs"]
|
||||
reprlocals: Optional["ReprLocals"]
|
||||
reprfileloc: Optional["ReprFileLocation"]
|
||||
style: _TracebackStyle
|
||||
style: "_TracebackStyle"
|
||||
|
||||
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
||||
"""Write the source code portions of a list of traceback entries with syntax highlighting.
|
||||
|
||||
@@ -149,7 +149,8 @@ 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)):
|
||||
# The lineno points to the class/def, so need to include the decorators.
|
||||
# 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)
|
||||
|
||||
@@ -2,13 +2,12 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
from typing import final
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
from typing import Sequence
|
||||
from typing import TextIO
|
||||
|
||||
from .wcwidth import wcswidth
|
||||
from _pytest.compat import final
|
||||
|
||||
|
||||
# This code was initially copied from py 1.8.1, file _io/terminalwriter.py.
|
||||
@@ -194,21 +193,15 @@ class TerminalWriter:
|
||||
for indent, new_line in zip(indents, new_lines):
|
||||
self.line(indent + new_line)
|
||||
|
||||
def _highlight(
|
||||
self, source: str, lexer: Literal["diff", "python"] = "python"
|
||||
) -> str:
|
||||
"""Highlight the given source if we have markup support."""
|
||||
def _highlight(self, source: str) -> str:
|
||||
"""Highlight the given source code if we have markup support."""
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
if not self.hasmarkup or not self.code_highlight:
|
||||
return source
|
||||
try:
|
||||
from pygments.formatters.terminal import TerminalFormatter
|
||||
|
||||
if lexer == "python":
|
||||
from pygments.lexers.python import PythonLexer as Lexer
|
||||
elif lexer == "diff":
|
||||
from pygments.lexers.diff import DiffLexer as Lexer
|
||||
from pygments.lexers.python import PythonLexer
|
||||
from pygments import highlight
|
||||
import pygments.util
|
||||
except ImportError:
|
||||
@@ -217,7 +210,7 @@ class TerminalWriter:
|
||||
try:
|
||||
highlighted: str = highlight(
|
||||
source,
|
||||
Lexer(),
|
||||
PythonLexer(),
|
||||
TerminalFormatter(
|
||||
bg=os.getenv("PYTEST_THEME_MODE", "dark"),
|
||||
style=os.getenv("PYTEST_THEME"),
|
||||
|
||||
+6
-16
@@ -25,12 +25,14 @@ from stat import S_ISREG
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import cast
|
||||
from typing import Literal
|
||||
from typing import overload
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import error
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Literal
|
||||
|
||||
# Moved from local.py.
|
||||
iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt")
|
||||
|
||||
@@ -755,13 +757,7 @@ class LocalPath:
|
||||
if ensure:
|
||||
self.dirpath().ensure(dir=1)
|
||||
if encoding:
|
||||
# Using type ignore here because of this error:
|
||||
# error: Argument 1 has incompatible type overloaded function;
|
||||
# expected "Callable[[str, Any, Any], TextIOWrapper]" [arg-type]
|
||||
# Which seems incorrect, given io.open supports the given argument types.
|
||||
return error.checked_call(
|
||||
io.open, self.strpath, mode, encoding=encoding # type:ignore[arg-type]
|
||||
)
|
||||
return error.checked_call(io.open, self.strpath, mode, encoding=encoding)
|
||||
return error.checked_call(open, self.strpath, mode)
|
||||
|
||||
def _fastjoin(self, name):
|
||||
@@ -1267,19 +1263,13 @@ class LocalPath:
|
||||
@classmethod
|
||||
def mkdtemp(cls, rootdir=None):
|
||||
"""Return a Path object pointing to a fresh new temporary directory
|
||||
(which we created ourselves).
|
||||
(which we created ourself).
|
||||
"""
|
||||
import tempfile
|
||||
|
||||
if rootdir is None:
|
||||
rootdir = cls.get_temproot()
|
||||
# Using type ignore here because of this error:
|
||||
# error: Argument 1 has incompatible type overloaded function; expected "Callable[[str], str]" [arg-type]
|
||||
# Which seems incorrect, given tempfile.mkdtemp supports the given argument types.
|
||||
path = error.checked_call(
|
||||
tempfile.mkdtemp, dir=str(rootdir) # type:ignore[arg-type]
|
||||
)
|
||||
return cls(path)
|
||||
return cls(error.checked_call(tempfile.mkdtemp, dir=str(rootdir)))
|
||||
|
||||
@classmethod
|
||||
def make_numbered_dir(
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user