Compare commits
1 Commits
8.2.0.dev0
...
7.3.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef982aaf2b |
@@ -23,11 +23,6 @@ afc607cfd81458d4e4f3b1f3cf8cc931b933907e
|
||||
5f95dce95602921a70bfbc7d8de2f7712c5e4505
|
||||
# ran pyupgrade-docs again
|
||||
75d0b899bbb56d6849e9d69d83a9426ed3f43f8b
|
||||
|
||||
# move argument parser to own file
|
||||
c9df77cbd6a365dcb73c39618e4842711817e871
|
||||
# Replace reorder-python-imports by isort due to black incompatibility (#11896)
|
||||
8b54596639f41dfac070030ef20394b9001fe63c
|
||||
# Run blacken-docs with black's 2024's style
|
||||
4546d5445aaefe6a03957db028c263521dfb5c4b
|
||||
# Migration to ruff / ruff format
|
||||
4588653b2497ed25976b7aaff225b889fb476756
|
||||
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: true
|
||||
|
||||
89
.github/workflows/deploy.yml
vendored
89
.github/workflows/deploy.yml
vendored
@@ -1,101 +1,60 @@
|
||||
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@v2.0.1
|
||||
|
||||
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@v4
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.8.11
|
||||
|
||||
- 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
|
||||
|
||||
- name: Download Package
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
password: ${{ secrets.pypi_token }}
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Install tox
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install --upgrade tox
|
||||
|
||||
- name: Generate release notes
|
||||
- name: Publish GitHub release notes
|
||||
env:
|
||||
GH_RELEASE_NOTES_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
sudo apt-get install pandoc
|
||||
tox -e generate-gh-release-notes -- ${{ github.event.inputs.version }} scripts/latest-release-notes.md
|
||||
|
||||
- name: Publish GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
body_path: scripts/latest-release-notes.md
|
||||
files: dist/*
|
||||
tag_name: ${{ github.event.inputs.version }}
|
||||
tox -e publish-gh-release-notes
|
||||
|
||||
4
.github/workflows/prepare-release-pr.yml
vendored
4
.github/workflows/prepare-release-pr.yml
vendored
@@ -27,12 +27,12 @@ jobs:
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.8"
|
||||
|
||||
|
||||
23
.github/workflows/stale.yml
vendored
23
.github/workflows/stale.yml
vendored
@@ -1,23 +0,0 @@
|
||||
name: close needs-information issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 1 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
- uses: actions/stale@v9
|
||||
with:
|
||||
debug-only: false
|
||||
days-before-issue-stale: 14
|
||||
days-before-issue-close: 7
|
||||
only-labels: "status: needs information"
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 14 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
120
.github/workflows/test.yml
vendored
120
.github/workflows/test.yml
vendored
@@ -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@v2.0.1
|
||||
|
||||
build:
|
||||
needs: [package]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 45
|
||||
permissions:
|
||||
@@ -49,41 +37,46 @@ 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-py37",
|
||||
"macos-py38",
|
||||
"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
|
||||
@@ -93,27 +86,27 @@ jobs:
|
||||
os: windows-latest
|
||||
tox_env: "py310-xdist"
|
||||
- name: "windows-py311"
|
||||
python: "3.11"
|
||||
python: "3.11-dev"
|
||||
os: windows-latest
|
||||
tox_env: "py311"
|
||||
- name: "windows-py312"
|
||||
python: "3.12-dev"
|
||||
os: windows-latest
|
||||
tox_env: "py312"
|
||||
|
||||
- name: "ubuntu-py37"
|
||||
python: "3.7"
|
||||
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
|
||||
@@ -123,66 +116,58 @@ jobs:
|
||||
os: ubuntu-latest
|
||||
tox_env: "py310-xdist"
|
||||
- name: "ubuntu-py311"
|
||||
python: "3.11"
|
||||
python: "3.11-dev"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py311"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-py312"
|
||||
python: "3.12-dev"
|
||||
os: ubuntu-latest
|
||||
tox_env: "py312"
|
||||
use_coverage: true
|
||||
- name: "ubuntu-pypy3"
|
||||
python: "pypy-3.8"
|
||||
python: "pypy-3.7"
|
||||
os: ubuntu-latest
|
||||
tox_env: "pypy3-xdist"
|
||||
|
||||
- name: "macos-py37"
|
||||
python: "3.7"
|
||||
os: macos-latest
|
||||
tox_env: "py37-xdist"
|
||||
- name: "macos-py38"
|
||||
python: "3.8"
|
||||
os: macos-latest
|
||||
tox_env: "py38-xdist"
|
||||
use_coverage: true
|
||||
- name: "macos-py39"
|
||||
python: "3.9"
|
||||
os: macos-latest
|
||||
tox_env: "py39-xdist"
|
||||
use_coverage: true
|
||||
- name: "macos-py310"
|
||||
python: "3.10"
|
||||
os: macos-latest
|
||||
tox_env: "py310-xdist"
|
||||
- name: "macos-py312"
|
||||
python: "3.12-dev"
|
||||
os: macos-latest
|
||||
tox_env: "py312-xdist"
|
||||
|
||||
- name: "plugins"
|
||||
python: "3.12"
|
||||
python: "3.9"
|
||||
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@v4
|
||||
with:
|
||||
name: Packages
|
||||
path: dist
|
||||
|
||||
- name: Set up Python ${{ matrix.python }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python }}
|
||||
check-latest: ${{ endsWith(matrix.python, '-dev') }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
@@ -191,13 +176,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"
|
||||
@@ -205,9 +188,16 @@ jobs:
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
if: "matrix.use_coverage"
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@v3
|
||||
continue-on-error: true
|
||||
with:
|
||||
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
|
||||
|
||||
18
.github/workflows/update-plugin-list.yml
vendored
18
.github/workflows/update-plugin-list.yml
vendored
@@ -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@v5
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.11"
|
||||
cache: pip
|
||||
- name: requests-cache
|
||||
uses: actions/cache@v4
|
||||
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@b1ddad2c994a25fbc81a28b3ec0e368bb2021c50
|
||||
uses: peter-evans/create-pull-request@38e0b6e68b4c852a5500a94740f0e535e0d7ba54
|
||||
with:
|
||||
commit-message: '[automated] Update plugin list'
|
||||
author: 'pytest bot <pytestbot@users.noreply.github.com>'
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
default_language_version:
|
||||
python: "3.10"
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: "v0.2.2"
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: ["--fix"]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: 1.13.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
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
|
||||
@@ -16,38 +22,56 @@ repos:
|
||||
- id: debug-statements
|
||||
exclude: _pytest/(debugging|hookspec).py
|
||||
language_version: python3
|
||||
- repo: https://github.com/adamchainz/blacken-docs
|
||||
rev: 1.16.0
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.0.2
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==24.1.1]
|
||||
- id: autoflake
|
||||
name: autoflake
|
||||
args: ["--in-place", "--remove-unused-variables", "--remove-all-unused-imports"]
|
||||
language: python
|
||||
files: \.py$
|
||||
- repo: https://github.com/PyCQA/flake8
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
additional_dependencies:
|
||||
- flake8-typing-imports==1.12.0
|
||||
- flake8-docstrings==1.5.0
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v3.9.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src', --py37-plus]
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py37-plus]
|
||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||
rev: v2.2.0
|
||||
hooks:
|
||||
- id: setup-cfg-fmt
|
||||
args: ["--max-py-version=3.11", "--include-version-classifiers"]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.10.0
|
||||
hooks:
|
||||
- id: python-use-type-annotations
|
||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||
rev: v1.8.0
|
||||
rev: v1.1.1
|
||||
hooks:
|
||||
- id: mypy
|
||||
files: ^(src/|testing/|scripts/)
|
||||
files: ^(src/|testing/)
|
||||
args: []
|
||||
additional_dependencies:
|
||||
- iniconfig>=1.1.0
|
||||
- attrs>=19.2.0
|
||||
- pluggy>=1.4.0
|
||||
- packaging
|
||||
- tomli
|
||||
- types-pkg_resources
|
||||
- types-tabulate
|
||||
# for mypy running on python>=3.11 since exceptiongroup is only a dependency
|
||||
# on <3.11
|
||||
- exceptiongroup>=1.0.0rc8
|
||||
- repo: https://github.com/tox-dev/pyproject-fmt
|
||||
rev: "1.7.0"
|
||||
hooks:
|
||||
- id: pyproject-fmt
|
||||
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
|
||||
additional_dependencies: ["tox>=4.9"]
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: rst
|
||||
|
||||
@@ -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:
|
||||
|
||||
46
AUTHORS
46
AUTHORS
@@ -8,15 +8,11 @@ Abdeali JK
|
||||
Abdelrahman Elbehery
|
||||
Abhijeet Kasurde
|
||||
Adam Johnson
|
||||
Adam Stewart
|
||||
Adam Uhlir
|
||||
Ahn Ki-Wook
|
||||
Akhilesh Ramakrishnan
|
||||
Akiomi Kamakura
|
||||
Alan Velasco
|
||||
Alessio Izzo
|
||||
Alex Jones
|
||||
Alex Lambson
|
||||
Alexander Johnson
|
||||
Alexander King
|
||||
Alexei Kozlenok
|
||||
@@ -48,23 +44,17 @@ Ariel Pillemer
|
||||
Armin Rigo
|
||||
Aron Coyle
|
||||
Aron Curzon
|
||||
Arthur Richard
|
||||
Ashish Kurmi
|
||||
Aviral Verma
|
||||
Aviv Palivoda
|
||||
Babak Keyvani
|
||||
Barney Gale
|
||||
Ben Brown
|
||||
Ben Gartner
|
||||
Ben Leith
|
||||
Ben Webb
|
||||
Benjamin Peterson
|
||||
Benjamin Schubert
|
||||
Bernard Pratz
|
||||
Bo Wu
|
||||
Bob Ippolito
|
||||
Brian Dorsey
|
||||
Brian Larsen
|
||||
Brian Maissy
|
||||
Brian Okken
|
||||
Brianna Laugher
|
||||
@@ -78,7 +68,6 @@ Charles Cloud
|
||||
Charles Machalow
|
||||
Charnjit SiNGH (CCSJ)
|
||||
Cheuk Ting Ho
|
||||
Chris Mahoney
|
||||
Chris Lamb
|
||||
Chris NeJame
|
||||
Chris Rose
|
||||
@@ -94,7 +83,6 @@ Christopher Dignam
|
||||
Christopher Gilling
|
||||
Claire Cecil
|
||||
Claudio Madotto
|
||||
Clément M.T. Robert
|
||||
CrazyMerlyn
|
||||
Cristian Vera
|
||||
Cyrus Maden
|
||||
@@ -128,8 +116,6 @@ Edison Gustavo Muenz
|
||||
Edoardo Batini
|
||||
Edson Tadeu M. Manoel
|
||||
Eduardo Schettino
|
||||
Edward Haigh
|
||||
Eero Vaher
|
||||
Eli Boyarski
|
||||
Elizaveta Shashkova
|
||||
Éloi Rivard
|
||||
@@ -138,21 +124,16 @@ Eric Hunsberger
|
||||
Eric Liu
|
||||
Eric Siegerman
|
||||
Erik Aronesty
|
||||
Erik Hasse
|
||||
Erik M. Bray
|
||||
Evan Kepner
|
||||
Evgeny Seliverstov
|
||||
Fabian Sturm
|
||||
Fabien Zarifian
|
||||
Fabio Zadrozny
|
||||
faph
|
||||
Felix Hofstätter
|
||||
Felix Nieuwenhuizen
|
||||
Feng Ma
|
||||
Florian Bruhin
|
||||
Florian Dahlitz
|
||||
Floris Bruynooghe
|
||||
Fraser Stark
|
||||
Gabriel Landau
|
||||
Gabriel Reis
|
||||
Garvit Shubham
|
||||
@@ -179,8 +160,6 @@ Ian Bicking
|
||||
Ian Lesperance
|
||||
Ilya Konstantinov
|
||||
Ionuț Turturică
|
||||
Isaac Virshup
|
||||
Israel Fruchter
|
||||
Itxaso Aizpurua
|
||||
Iwan Briquemont
|
||||
Jaap Broekhuizen
|
||||
@@ -196,7 +175,6 @@ Javier Romero
|
||||
Jeff Rackauckas
|
||||
Jeff Widman
|
||||
Jenni Rinker
|
||||
Jens Tröger
|
||||
John Eddie Ayson
|
||||
John Litborn
|
||||
John Towler
|
||||
@@ -214,7 +192,6 @@ Justice Ndou
|
||||
Justyna Janczyszyn
|
||||
Kale Kundert
|
||||
Kamran Ahmad
|
||||
Kenny Y
|
||||
Karl O. Pinc
|
||||
Karthikeyan Singaravelan
|
||||
Katarzyna Jachim
|
||||
@@ -245,11 +222,9 @@ Maho
|
||||
Maik Figura
|
||||
Mandeep Bhutani
|
||||
Manuel Krebber
|
||||
Marc Mueller
|
||||
Marc Schlaich
|
||||
Marcelo Duarte Trevisani
|
||||
Marcin Bachry
|
||||
Marc Bresson
|
||||
Marco Gorelli
|
||||
Mark Abramowitz
|
||||
Mark Dickinson
|
||||
@@ -274,20 +249,15 @@ Michael Goerz
|
||||
Michael Krebs
|
||||
Michael Seifert
|
||||
Michal Wajszczuk
|
||||
Michał Górny
|
||||
Michał Zięba
|
||||
Mickey Pashov
|
||||
Mihai Capotă
|
||||
Mihail Milushev
|
||||
Mike Hoyle (hoylemd)
|
||||
Mike Lundy
|
||||
Milan Lesnek
|
||||
Miro Hrončok
|
||||
mrbean-bremen
|
||||
Nathaniel Compton
|
||||
Nathaniel Waisbrot
|
||||
Ned Batchelder
|
||||
Neil Martin
|
||||
Neven Mundar
|
||||
Nicholas Devenish
|
||||
Nicholas Murphy
|
||||
@@ -305,7 +275,6 @@ Ondřej Súkup
|
||||
Oscar Benjamin
|
||||
Parth Patel
|
||||
Patrick Hayes
|
||||
Patrick Lannigan
|
||||
Paul Müller
|
||||
Paul Reece
|
||||
Pauli Virtanen
|
||||
@@ -334,9 +303,7 @@ Raphael Pierzina
|
||||
Rafal Semik
|
||||
Raquel Alegre
|
||||
Ravi Chandra
|
||||
Reagan Lee
|
||||
Robert Holt
|
||||
Roberto Aldera
|
||||
Roberto Polli
|
||||
Roland Puntaier
|
||||
Romain Dorgueil
|
||||
@@ -345,33 +312,25 @@ Ronny Pfannschmidt
|
||||
Ross Lawley
|
||||
Ruaridh Williamson
|
||||
Russel Winder
|
||||
Russell Martin
|
||||
Ryan Puddephatt
|
||||
Ryan Wooden
|
||||
Sadra Barikbin
|
||||
Saiprasad Kale
|
||||
Samuel Colvin
|
||||
Samuel Dion-Girardeau
|
||||
Samuel Searles-Bryant
|
||||
Samuel Therrien (Avasam)
|
||||
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
|
||||
@@ -385,7 +344,6 @@ Tadek Teleżyński
|
||||
Takafumi Arakaki
|
||||
Taneli Hukkinen
|
||||
Tanvi Mehta
|
||||
Tanya Agarwal
|
||||
Tarcisio Fischer
|
||||
Tareq Alayan
|
||||
Tatiana Ovary
|
||||
@@ -404,23 +362,19 @@ 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
|
||||
Vlad Dragos
|
||||
Vlad Radziuk
|
||||
Vladyslav Rachek
|
||||
Volodymyr Kochetkov
|
||||
Volodymyr Piskun
|
||||
Wei Lin
|
||||
Wil Cooley
|
||||
|
||||
@@ -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 <https://www.python.org/dev/peps/pep-0008/>`_ for naming.
|
||||
#. 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 <https://www.python.org/dev/peps/pep-0008/>`_ for naming.
|
||||
#. 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.
|
||||
|
||||
13
README.rst
13
README.rst
@@ -20,13 +20,16 @@
|
||||
:target: https://codecov.io/gh/pytest-dev/pytest
|
||||
:alt: Code coverage Status
|
||||
|
||||
.. image:: https://github.com/pytest-dev/pytest/actions/workflows/test.yml/badge.svg
|
||||
.. image:: https://github.com/pytest-dev/pytest/workflows/test/badge.svg
|
||||
:target: https://github.com/pytest-dev/pytest/actions?query=workflow%3Atest
|
||||
|
||||
.. image:: https://results.pre-commit.ci/badge/github/pytest-dev/pytest/main.svg
|
||||
:target: https://results.pre-commit.ci/latest/github/pytest-dev/pytest/main
|
||||
:alt: pre-commit.ci status
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/psf/black
|
||||
|
||||
.. image:: https://www.codetriage.com/pytest-dev/pytest/badges/users.svg
|
||||
:target: https://www.codetriage.com/pytest-dev/pytest
|
||||
|
||||
@@ -94,12 +97,12 @@ Features
|
||||
- `Modular fixtures <https://docs.pytest.org/en/stable/explanation/fixtures.html>`_ for
|
||||
managing small or parametrized long-lived test resources
|
||||
|
||||
- Can run `unittest <https://docs.pytest.org/en/stable/how-to/unittest.html>`_ (or trial)
|
||||
test suites out of the box
|
||||
- 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 1300+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
|
||||
- Rich plugin architecture, with over 850+ `external plugins <https://docs.pytest.org/en/latest/reference/plugin_list.html>`_ and thriving community
|
||||
|
||||
|
||||
Documentation
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ members of the `contributors team`_ interested in receiving funding.
|
||||
|
||||
The current list of contributors receiving funding are:
|
||||
|
||||
* `@asottile`_
|
||||
* `@nicoddemus`_
|
||||
* `@The-Compiler`_
|
||||
|
||||
@@ -54,5 +55,6 @@ funds. Just drop a line to one of the `@pytest-dev/tidelift-admins`_ or use the
|
||||
.. _`@pytest-dev/tidelift-admins`: https://github.com/orgs/pytest-dev/teams/tidelift-admins/members
|
||||
.. _`agreement`: https://tidelift.com/docs/lifting/agreement
|
||||
|
||||
.. _`@asottile`: https://github.com/asottile
|
||||
.. _`@nicoddemus`: https://github.com/nicoddemus
|
||||
.. _`@The-Compiler`: https://github.com/The-Compiler
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import sys
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import cProfile
|
||||
import pytest # NOQA
|
||||
import pstats
|
||||
|
||||
import pytest # noqa: F401
|
||||
|
||||
script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"]
|
||||
cProfile.run("pytest.cmdline.main(%r)" % script, "prof")
|
||||
p = pstats.Stats("prof")
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
# FastFilesCompleter 0.7383 1.0760
|
||||
import timeit
|
||||
|
||||
|
||||
imports = [
|
||||
"from argcomplete.completers import FilesCompleter as completer",
|
||||
"from _pytest._argcomplete import FastFilesCompleter as completer",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import pytest
|
||||
|
||||
|
||||
SKIP = True
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
from unittest import TestCase # noqa: F401
|
||||
|
||||
|
||||
for i in range(15000):
|
||||
exec(
|
||||
f"""
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
<div id="searchbox" style="display: none" role="search">
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="{{ pathto('search') }}" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="text" name="q" aria-labelledby="searchlabel"
|
||||
placeholder="Search"/>
|
||||
<input type="submit" value="{{ _('Go') }}" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
||||
<script type="text/javascript">$('#searchbox').show(0);</script>
|
||||
{%- endif %}
|
||||
|
||||
@@ -44,7 +44,7 @@ Partner projects, sign up here! (by 22 March)
|
||||
What does it mean to "adopt pytest"?
|
||||
-----------------------------------------
|
||||
|
||||
There can be many different definitions of "success". Pytest can run many unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
|
||||
There can be many different definitions of "success". Pytest can run many nose_ and unittest_ tests by default, so using pytest as your testrunner may be possible from day 1. Job done, right?
|
||||
|
||||
Progressive success might look like:
|
||||
|
||||
@@ -62,6 +62,7 @@ Progressive success might look like:
|
||||
|
||||
It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
|
||||
|
||||
.. _nose: nose.html
|
||||
.. _unittest: unittest.html
|
||||
.. _assert: assert.html
|
||||
.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
|
||||
|
||||
@@ -6,19 +6,6 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-8.1.0
|
||||
release-8.0.2
|
||||
release-8.0.1
|
||||
release-8.0.0
|
||||
release-8.0.0rc2
|
||||
release-8.0.0rc1
|
||||
release-7.4.4
|
||||
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
|
||||
release-7.2.2
|
||||
release-7.2.1
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
pytest-7.3.1
|
||||
=======================================
|
||||
|
||||
pytest 7.3.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,21 +0,0 @@
|
||||
pytest-7.3.2
|
||||
=======================================
|
||||
|
||||
pytest 7.3.2 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Adam J. Stewart
|
||||
* Alessio Izzo
|
||||
* Bruno Oliveira
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -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
|
||||
@@ -1,20 +0,0 @@
|
||||
pytest-7.4.4
|
||||
=======================================
|
||||
|
||||
pytest 7.4.4 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
|
||||
* Ran Benita
|
||||
* Zac Hatfield-Dodds
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,26 +0,0 @@
|
||||
pytest-8.0.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 8.0.0 release!
|
||||
|
||||
This release contains new features, improvements, bug fixes, and breaking changes, so users
|
||||
are encouraged to take a look at the CHANGELOG carefully:
|
||||
|
||||
https://docs.pytest.org/en/stable/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/stable/
|
||||
|
||||
As usual, you can upgrade from PyPI via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,82 +0,0 @@
|
||||
pytest-8.0.0rc1
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 8.0.0rc1 release!
|
||||
|
||||
This release contains new features, improvements, bug fixes, and breaking changes, so users
|
||||
are encouraged to take a look at the CHANGELOG carefully:
|
||||
|
||||
https://docs.pytest.org/en/stable/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/stable/
|
||||
|
||||
As usual, you can upgrade from PyPI via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Akhilesh Ramakrishnan
|
||||
* Aleksandr Brodin
|
||||
* Anthony Sottile
|
||||
* Arthur Richard
|
||||
* Avasam
|
||||
* Benjamin Schubert
|
||||
* Bruno Oliveira
|
||||
* Carsten Grohmann
|
||||
* Cheukting
|
||||
* Chris Mahoney
|
||||
* Christoph Anton Mitterer
|
||||
* DetachHead
|
||||
* Erik Hasse
|
||||
* Florian Bruhin
|
||||
* Fraser Stark
|
||||
* Ha Pam
|
||||
* Hugo van Kemenade
|
||||
* Isaac Virshup
|
||||
* Israel Fruchter
|
||||
* Jens Tröger
|
||||
* Jon Parise
|
||||
* Kenny Y
|
||||
* Lesnek
|
||||
* Marc Mueller
|
||||
* Michał Górny
|
||||
* Mihail Milushev
|
||||
* Milan Lesnek
|
||||
* Miro Hrončok
|
||||
* Patrick Lannigan
|
||||
* Ran Benita
|
||||
* Reagan Lee
|
||||
* Ronny Pfannschmidt
|
||||
* Sadra Barikbin
|
||||
* Sean Malloy
|
||||
* Sean Patrick Malloy
|
||||
* Sharad Nair
|
||||
* Simon Blanchard
|
||||
* Sourabh Beniwal
|
||||
* Stefaan Lippens
|
||||
* Tanya Agarwal
|
||||
* Thomas Grainger
|
||||
* Tom Mortimer-Jones
|
||||
* Tushar Sadhwani
|
||||
* Tyler Smart
|
||||
* Uday Kumar
|
||||
* Warren Markham
|
||||
* WarrenTheRabbit
|
||||
* Zac Hatfield-Dodds
|
||||
* Ziad Kermadi
|
||||
* akhilramkee
|
||||
* antosikv
|
||||
* bowugit
|
||||
* mickeypash
|
||||
* neilmartin2000
|
||||
* pomponchik
|
||||
* ryanpudd
|
||||
* touilleWoman
|
||||
* ubaumann
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,32 +0,0 @@
|
||||
pytest-8.0.0rc2
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 8.0.0rc2 prerelease!
|
||||
|
||||
This is a prerelease, not intended for production use, but to test the upcoming features and improvements
|
||||
in order to catch any major problems before the final version is released to the major public.
|
||||
|
||||
We appreciate your help testing this out before the final release, making sure to report any
|
||||
regressions to our issue tracker:
|
||||
|
||||
https://github.com/pytest-dev/pytest/issues
|
||||
|
||||
When doing so, please include the string ``[prerelease]`` in the title.
|
||||
|
||||
You can upgrade from PyPI via:
|
||||
|
||||
pip install pytest==8.0.0rc2
|
||||
|
||||
Users are encouraged to take a look at the CHANGELOG carefully:
|
||||
|
||||
https://docs.pytest.org/en/release-8.0.0rc2/changelog.html
|
||||
|
||||
Thanks to all the contributors to this release:
|
||||
|
||||
* Ben Brown
|
||||
* Bruno Oliveira
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,21 +0,0 @@
|
||||
pytest-8.0.1
|
||||
=======================================
|
||||
|
||||
pytest 8.0.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Clément Robert
|
||||
* Pierre Sassoulas
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,18 +0,0 @@
|
||||
pytest-8.0.2
|
||||
=======================================
|
||||
|
||||
pytest 8.0.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:
|
||||
|
||||
* Ran Benita
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -1,54 +0,0 @@
|
||||
pytest-8.1.0
|
||||
=======================================
|
||||
|
||||
The pytest team is proud to announce the 8.1.0 release!
|
||||
|
||||
This release contains new features, improvements, and bug fixes,
|
||||
the full list of changes is available in the changelog:
|
||||
|
||||
https://docs.pytest.org/en/stable/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
https://docs.pytest.org/en/stable/
|
||||
|
||||
As usual, you can upgrade from PyPI via:
|
||||
|
||||
pip install -U pytest
|
||||
|
||||
Thanks to all of the contributors to this release:
|
||||
|
||||
* Ben Brown
|
||||
* Ben Leith
|
||||
* Bruno Oliveira
|
||||
* Clément Robert
|
||||
* Dave Hall
|
||||
* Dương Quốc Khánh
|
||||
* Eero Vaher
|
||||
* Eric Larson
|
||||
* Fabian Sturm
|
||||
* Faisal Fawad
|
||||
* Florian Bruhin
|
||||
* Franck Charras
|
||||
* Joachim B Haga
|
||||
* John Litborn
|
||||
* Loïc Estève
|
||||
* Marc Bresson
|
||||
* Patrick Lannigan
|
||||
* Pierre Sassoulas
|
||||
* Ran Benita
|
||||
* Reagan Lee
|
||||
* Ronny Pfannschmidt
|
||||
* Russell Martin
|
||||
* clee2000
|
||||
* donghui
|
||||
* faph
|
||||
* jakkdl
|
||||
* mrbean-bremen
|
||||
* robotherapist
|
||||
* whysage
|
||||
* woutdenolf
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -22,7 +22,7 @@ b) transitional: the old and new API don't conflict
|
||||
|
||||
We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0).
|
||||
|
||||
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`).
|
||||
A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationwarning`).
|
||||
|
||||
When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed.
|
||||
|
||||
@@ -87,11 +87,8 @@ 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+
|
||||
3.3 - 4.6 2.7, 3.4+
|
||||
============== ===================
|
||||
|
||||
`Status of Python Versions <https://devguide.python.org/versions/>`__.
|
||||
|
||||
@@ -18,11 +18,11 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
$ pytest --fixtures -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collected 0 items
|
||||
cache -- .../_pytest/cacheprovider.py:527
|
||||
cache -- .../_pytest/cacheprovider.py:510
|
||||
Return a cache object that can persist state between testing sessions.
|
||||
|
||||
cache.get(key, default)
|
||||
@@ -33,7 +33,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
|
||||
Values can be any object handled by the json stdlib module.
|
||||
|
||||
capsysbinary -- .../_pytest/capture.py:1008
|
||||
capsysbinary -- .../_pytest/capture.py:1001
|
||||
Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsysbinary.readouterr()``
|
||||
@@ -43,6 +43,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_output(capsysbinary):
|
||||
@@ -50,7 +51,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
captured = capsysbinary.readouterr()
|
||||
assert captured.out == b"hello\n"
|
||||
|
||||
capfd -- .../_pytest/capture.py:1035
|
||||
capfd -- .../_pytest/capture.py:1029
|
||||
Enable text capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
@@ -60,6 +61,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_system_echo(capfd):
|
||||
@@ -67,7 +69,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
captured = capfd.readouterr()
|
||||
assert captured.out == "hello\n"
|
||||
|
||||
capfdbinary -- .../_pytest/capture.py:1062
|
||||
capfdbinary -- .../_pytest/capture.py:1057
|
||||
Enable bytes capturing of writes to file descriptors ``1`` and ``2``.
|
||||
|
||||
The captured output is made available via ``capfd.readouterr()`` method
|
||||
@@ -77,6 +79,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
Returns an instance of :class:`CaptureFixture[bytes] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_system_echo(capfdbinary):
|
||||
@@ -84,7 +87,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
captured = capfdbinary.readouterr()
|
||||
assert captured.out == b"hello\n"
|
||||
|
||||
capsys -- .../_pytest/capture.py:981
|
||||
capsys -- .../_pytest/capture.py:973
|
||||
Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``.
|
||||
|
||||
The captured output is made available via ``capsys.readouterr()`` method
|
||||
@@ -94,6 +97,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
Returns an instance of :class:`CaptureFixture[str] <pytest.CaptureFixture>`.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_output(capsys):
|
||||
@@ -115,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:1346
|
||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1360
|
||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||
object.
|
||||
|
||||
@@ -125,7 +129,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
if pytestconfig.getoption("verbose") > 0:
|
||||
...
|
||||
|
||||
record_property -- .../_pytest/junitxml.py:283
|
||||
record_property -- .../_pytest/junitxml.py:282
|
||||
Add extra properties to the calling test.
|
||||
|
||||
User properties become part of the test report and are available to the
|
||||
@@ -139,13 +143,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
def test_function(record_property):
|
||||
record_property("example_key", 1)
|
||||
|
||||
record_xml_attribute -- .../_pytest/junitxml.py:306
|
||||
record_xml_attribute -- .../_pytest/junitxml.py:305
|
||||
Add extra xml attributes to the tag for the calling test.
|
||||
|
||||
The fixture is callable with ``name, value``. The value is
|
||||
automatically XML-encoded.
|
||||
|
||||
record_testsuite_property [session scope] -- .../_pytest/junitxml.py:344
|
||||
record_testsuite_property [session scope] -- .../_pytest/junitxml.py:343
|
||||
Record a new ``<property>`` tag as child of the root ``<testsuite>``.
|
||||
|
||||
This is suitable to writing global information regarding the entire test
|
||||
@@ -170,18 +174,18 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
`pytest-xdist <https://github.com/pytest-dev/pytest-xdist>`__ plugin. See
|
||||
:issue:`7767` for details.
|
||||
|
||||
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:317
|
||||
tmpdir_factory [session scope] -- .../_pytest/legacypath.py:302
|
||||
Return a :class:`pytest.TempdirFactory` instance for the test session.
|
||||
|
||||
tmpdir -- .../_pytest/legacypath.py:324
|
||||
tmpdir -- .../_pytest/legacypath.py:309
|
||||
Return a temporary directory path object which is unique to each test
|
||||
function invocation, created as a sub directory of the base temporary
|
||||
directory.
|
||||
|
||||
By default, a new base temporary directory is created each test session,
|
||||
and old bases are removed after 3 sessions, to aid in debugging. If
|
||||
``--basetemp`` is used then it is cleared each session. See
|
||||
:ref:`temporary directory location and retention`.
|
||||
``--basetemp`` is used then it is cleared each session. See :ref:`base
|
||||
temporary directory`.
|
||||
|
||||
The returned object is a `legacy_path`_ object.
|
||||
|
||||
@@ -192,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:601
|
||||
caplog -- .../_pytest/logging.py:498
|
||||
Access and control log capturing.
|
||||
|
||||
Captured logs are available through the following properties/methods::
|
||||
@@ -203,7 +207,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
* caplog.record_tuples -> list of (logger_name, level, message) tuples
|
||||
* caplog.clear() -> clear captured records and formatted log output string
|
||||
|
||||
monkeypatch -- .../_pytest/monkeypatch.py:32
|
||||
monkeypatch -- .../_pytest/monkeypatch.py:29
|
||||
A convenient fixture for monkey-patching.
|
||||
|
||||
The fixture provides these methods to modify objects, dictionaries, or
|
||||
@@ -227,16 +231,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
To undo modifications done by the fixture in a contained scope,
|
||||
use :meth:`context() <pytest.MonkeyPatch.context>`.
|
||||
|
||||
recwarn -- .../_pytest/recwarn.py:31
|
||||
recwarn -- .../_pytest/recwarn.py:30
|
||||
Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions.
|
||||
|
||||
See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information
|
||||
on warning categories.
|
||||
|
||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:241
|
||||
tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:245
|
||||
Return a :class:`pytest.TempPathFactory` instance for the test session.
|
||||
|
||||
tmp_path -- .../_pytest/tmpdir.py:256
|
||||
tmp_path -- .../_pytest/tmpdir.py:260
|
||||
Return a temporary directory path object which is unique to each test
|
||||
function invocation, created as a sub directory of the base temporary
|
||||
directory.
|
||||
@@ -245,8 +249,8 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||
and old bases are removed after 3 sessions, to aid in debugging.
|
||||
This behavior can be configured with :confval:`tmp_path_retention_count` and
|
||||
:confval:`tmp_path_retention_policy`.
|
||||
If ``--basetemp`` is used then it is cleared each session. See
|
||||
:ref:`temporary directory location and retention`.
|
||||
If ``--basetemp`` is used then it is cleared each session. See :ref:`base
|
||||
temporary directory`.
|
||||
|
||||
The returned object is a :class:`pathlib.Path` object.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,15 +15,16 @@
|
||||
#
|
||||
# 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
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
import sphinx.application
|
||||
|
||||
@@ -170,49 +171,6 @@ extlinks = {
|
||||
}
|
||||
|
||||
|
||||
nitpicky = True
|
||||
nitpick_ignore = [
|
||||
# TODO (fix in pluggy?)
|
||||
("py:class", "HookCaller"),
|
||||
("py:class", "HookspecMarker"),
|
||||
("py:exc", "PluginValidationError"),
|
||||
# Might want to expose/TODO (https://github.com/pytest-dev/pytest/issues/7469)
|
||||
("py:class", "ExceptionRepr"),
|
||||
("py:class", "Exit"),
|
||||
("py:class", "SubRequest"),
|
||||
("py:class", "SubRequest"),
|
||||
("py:class", "TerminalReporter"),
|
||||
("py:class", "_pytest._code.code.TerminalRepr"),
|
||||
("py:class", "_pytest.fixtures.FixtureFunctionMarker"),
|
||||
("py:class", "_pytest.logging.LogCaptureHandler"),
|
||||
("py:class", "_pytest.mark.structures.ParameterSet"),
|
||||
# Intentionally undocumented/private
|
||||
("py:class", "_pytest._code.code.Traceback"),
|
||||
("py:class", "_pytest._py.path.LocalPath"),
|
||||
("py:class", "_pytest.capture.CaptureResult"),
|
||||
("py:class", "_pytest.compat.NotSetType"),
|
||||
("py:class", "_pytest.python.PyCollector"),
|
||||
("py:class", "_pytest.python.PyobjMixin"),
|
||||
("py:class", "_pytest.python_api.RaisesContext"),
|
||||
("py:class", "_pytest.recwarn.WarningsChecker"),
|
||||
("py:class", "_pytest.reports.BaseReport"),
|
||||
# Undocumented third parties
|
||||
("py:class", "_tracing.TagTracerSub"),
|
||||
("py:class", "warnings.WarningMessage"),
|
||||
# Undocumented type aliases
|
||||
("py:class", "_PluggyPlugin"),
|
||||
# TypeVars
|
||||
("py:class", "_pytest._code.code.E"),
|
||||
("py:class", "_pytest.fixtures.FixtureFunction"),
|
||||
("py:class", "_pytest.nodes._NodeType"),
|
||||
("py:class", "_pytest.python_api.E"),
|
||||
("py:class", "_pytest.recwarn.T"),
|
||||
("py:class", "_pytest.runner.TResult"),
|
||||
("py:obj", "_pytest.fixtures.FixtureValue"),
|
||||
("py:obj", "_pytest.stash.T"),
|
||||
]
|
||||
|
||||
|
||||
# -- Options for HTML output ---------------------------------------------------
|
||||
|
||||
sys.path.append(os.path.abspath("_themes"))
|
||||
@@ -383,7 +341,7 @@ epub_copyright = "2013, holger krekel et alii"
|
||||
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||
# epub_scheme = ''
|
||||
|
||||
# The unique identifier of the text. This can be an ISBN number
|
||||
# The unique identifier of the text. This can be a ISBN number
|
||||
# or the project homepage.
|
||||
# epub_identifier = ''
|
||||
|
||||
@@ -441,9 +399,8 @@ intersphinx_mapping = {
|
||||
|
||||
def configure_logging(app: "sphinx.application.Sphinx") -> None:
|
||||
"""Configure Sphinx's WarningHandler to handle (expected) missing include."""
|
||||
import logging
|
||||
|
||||
import sphinx.util.logging
|
||||
import logging
|
||||
|
||||
class WarnLogFilter(logging.Filter):
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
@@ -494,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
|
||||
|
||||
@@ -44,6 +44,7 @@ How-to guides
|
||||
|
||||
how-to/existingtestsuite
|
||||
how-to/unittest
|
||||
how-to/nose
|
||||
how-to/xunit_setup
|
||||
|
||||
how-to/bash-completion
|
||||
|
||||
@@ -19,6 +19,170 @@ Below is a complete list of all pytest features which are considered deprecated.
|
||||
:class:`~pytest.PytestWarning` or subclasses, which can be filtered using :ref:`standard warning filters <warnings>`.
|
||||
|
||||
|
||||
.. _nose-deprecation:
|
||||
|
||||
Support for tests written for nose
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.2
|
||||
|
||||
Support for running tests written for `nose <https://nose.readthedocs.io/en/latest/>`__ is now deprecated.
|
||||
|
||||
``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills
|
||||
over the code base (see :issue:`9886` for more details).
|
||||
|
||||
setup/teardown
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native,
|
||||
they are in fact part of the ``nose`` support.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Test:
|
||||
def setup(self):
|
||||
self.resource = make_resource()
|
||||
|
||||
def teardown(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self):
|
||||
...
|
||||
|
||||
def test_bar(self):
|
||||
...
|
||||
|
||||
|
||||
|
||||
Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Test:
|
||||
def setup_method(self):
|
||||
self.resource = make_resource()
|
||||
|
||||
def teardown_method(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self):
|
||||
...
|
||||
|
||||
def test_bar(self):
|
||||
...
|
||||
|
||||
|
||||
This is easy to do in an entire code base by doing a simple find/replace.
|
||||
|
||||
@with_setup
|
||||
^^^^^^^^^^^
|
||||
|
||||
Code using `@with_setup <with-setup-nose>`_ such as this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from nose.tools import with_setup
|
||||
|
||||
|
||||
def setup_some_resource():
|
||||
...
|
||||
|
||||
|
||||
def teardown_some_resource():
|
||||
...
|
||||
|
||||
|
||||
@with_setup(setup_some_resource, teardown_some_resource)
|
||||
def test_foo():
|
||||
...
|
||||
|
||||
Will also need to be ported to a supported pytest style. One way to do it is using a fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def setup_some_resource():
|
||||
...
|
||||
|
||||
|
||||
def teardown_some_resource():
|
||||
...
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def some_resource():
|
||||
setup_some_resource()
|
||||
yield
|
||||
teardown_some_resource()
|
||||
|
||||
|
||||
def test_foo(some_resource):
|
||||
...
|
||||
|
||||
|
||||
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
||||
|
||||
.. _instance-collector-deprecation:
|
||||
|
||||
The ``pytest.Instance`` collector
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 7.0
|
||||
|
||||
The ``pytest.Instance`` collector type has been removed.
|
||||
|
||||
Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`.
|
||||
Now :class:`~pytest.Class` collects the test methods directly.
|
||||
|
||||
Most plugins which reference ``Instance`` do so in order to ignore or skip it,
|
||||
using a check such as ``if isinstance(node, Instance): return``.
|
||||
Such plugins should simply remove consideration of ``Instance`` on pytest>=7.
|
||||
However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``,
|
||||
and importing it emits a deprecation warning. This will be removed in pytest 8.
|
||||
|
||||
|
||||
.. _node-ctor-fspath-deprecation:
|
||||
|
||||
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
|
||||
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
|
||||
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
|
||||
is now deprecated.
|
||||
|
||||
Plugins which construct nodes should pass the ``path`` argument, of type
|
||||
:class:`pathlib.Path`, instead of the ``fspath`` argument.
|
||||
|
||||
Plugins which implement custom items and collectors are encouraged to replace
|
||||
``fspath`` parameters (``py.path.local``) with ``path`` parameters
|
||||
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
|
||||
|
||||
If possible, plugins with custom items should use :ref:`cooperative
|
||||
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
|
||||
arguments they only pass on to the superclass.
|
||||
|
||||
.. note::
|
||||
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
|
||||
new attribute being ``path``) is **the opposite** of the situation for
|
||||
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (the old
|
||||
argument being ``path``).
|
||||
|
||||
This is an unfortunate artifact due to historical reasons, which should be
|
||||
resolved in future versions as we slowly get rid of the :pypi:`py`
|
||||
dependency (see :issue:`9283` for a longer discussion).
|
||||
|
||||
Due to the ongoing migration of methods like :meth:`~_pytest.Item.reportinfo`
|
||||
which still is expected to return a ``py.path.local`` object, nodes still have
|
||||
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
|
||||
no matter what argument was used in the constructor. We expect to deprecate the
|
||||
``fspath`` attribute in a future release.
|
||||
|
||||
.. _legacy-path-hooks-deprecated:
|
||||
|
||||
Configuring hook specs/impls using markers
|
||||
@@ -33,11 +197,13 @@ have been available since years and should be used instead.
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.tryfirst
|
||||
def pytest_runtest_call(): ...
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
|
||||
|
||||
# or
|
||||
def pytest_runtest_call(): ...
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
|
||||
|
||||
pytest_runtest_call.tryfirst = True
|
||||
@@ -47,7 +213,8 @@ should be changed to:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.hookimpl(tryfirst=True)
|
||||
def pytest_runtest_call(): ...
|
||||
def pytest_runtest_call():
|
||||
...
|
||||
|
||||
Changed ``hookimpl`` attributes:
|
||||
|
||||
@@ -62,6 +229,31 @@ Changed ``hookwrapper`` attributes:
|
||||
* ``historic``
|
||||
|
||||
|
||||
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
|
||||
|
||||
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
|
||||
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
|
||||
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
|
||||
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
|
||||
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
|
||||
|
||||
The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
|
||||
|
||||
.. note::
|
||||
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
|
||||
:ref:`outlined above <node-ctor-fspath-deprecation>` (the new attribute
|
||||
being ``path``) is **the opposite** of the situation for hooks (the old
|
||||
argument being ``path``).
|
||||
|
||||
This is an unfortunate artifact due to historical reasons, which should be
|
||||
resolved in future versions as we slowly get rid of the :pypi:`py`
|
||||
dependency (see :issue:`9283` for a longer discussion).
|
||||
|
||||
Directly constructing internal classes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -81,6 +273,62 @@ Directly constructing the following classes is now deprecated:
|
||||
|
||||
These constructors have always been considered private, but now issue a deprecation warning, which may become a hard error in pytest 8.
|
||||
|
||||
.. _cmdline-preparse-deprecated:
|
||||
|
||||
Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit`
|
||||
is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these
|
||||
functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_fail_example():
|
||||
# old
|
||||
pytest.fail(msg="foo")
|
||||
# new
|
||||
pytest.fail(reason="bar")
|
||||
|
||||
|
||||
def test_skip_example():
|
||||
# old
|
||||
pytest.skip(msg="foo")
|
||||
# new
|
||||
pytest.skip(reason="bar")
|
||||
|
||||
|
||||
def test_exit_example():
|
||||
# old
|
||||
pytest.exit(msg="foo")
|
||||
# new
|
||||
pytest.exit(reason="bar")
|
||||
|
||||
|
||||
Implementing the ``pytest_cmdline_preparse`` hook
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
Implementing the :hook:`pytest_cmdline_preparse` hook has been officially deprecated.
|
||||
Implement the :hook:`pytest_load_initial_conftests` hook instead.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_cmdline_preparse(config: Config, args: List[str]) -> None:
|
||||
...
|
||||
|
||||
|
||||
# becomes:
|
||||
|
||||
|
||||
def pytest_load_initial_conftests(
|
||||
early_config: Config, parser: Parser, args: List[str]
|
||||
) -> None:
|
||||
...
|
||||
|
||||
.. _diamond-inheritance-deprecated:
|
||||
|
||||
Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item`
|
||||
@@ -88,7 +336,7 @@ Diamond inheritance between :class:`pytest.Collector` and :class:`pytest.Item`
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
Defining a custom pytest node type which is both an :class:`~pytest.Item` and a :class:`~pytest.Collector` (e.g. :class:`~pytest.File`) now issues a warning.
|
||||
Defining a custom pytest node type which is both an :class:`pytest.Item <Item>` and a :class:`pytest.Collector <Collector>` (e.g. :class:`pytest.File <File>`) now issues a warning.
|
||||
It was never sanely supported and triggers hard to debug errors.
|
||||
|
||||
Some plugins providing linting/code analysis have been using this as a hack.
|
||||
@@ -100,8 +348,8 @@ Instead, a separate collector node should be used, which collects the item. See
|
||||
|
||||
.. _uncooperative-constructors-deprecated:
|
||||
|
||||
Constructors of custom :class:`~_pytest.nodes.Node` subclasses should take ``**kwargs``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Constructors of custom :class:`pytest.Node` subclasses should take ``**kwargs``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
@@ -132,22 +380,28 @@ 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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Backward compatibilities in ``Parser.addoption``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.4
|
||||
.. deprecated:: 2.4
|
||||
|
||||
Applying a mark to a fixture function never had any effect, but it is a common user error.
|
||||
Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
|
||||
scheduled for removal in pytest 8 (deprecated since pytest 2.4.0):
|
||||
|
||||
.. code-block:: python
|
||||
- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.
|
||||
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
|
||||
|
||||
@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.
|
||||
Using ``pytest.warns(None)``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Now pytest will issue a warning when it encounters this problem, and will raise an error in the future versions.
|
||||
.. deprecated:: 7.0
|
||||
|
||||
:func:`pytest.warns(None) <pytest.warns>` is now deprecated because it was frequently misused.
|
||||
Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()``
|
||||
or ``pytest.warns(Warning)``.
|
||||
|
||||
See :ref:`warns use cases` for examples.
|
||||
|
||||
|
||||
Returning non-None value in test functions
|
||||
@@ -190,6 +444,19 @@ The proper fix is to change the `return` to an `assert`:
|
||||
assert foo(a, b) == result
|
||||
|
||||
|
||||
The ``--strict`` command-line option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 6.2
|
||||
|
||||
The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
|
||||
better conveys what the option does.
|
||||
|
||||
We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing
|
||||
flag for all strictness related options (``--strict-markers`` and ``--strict-config``
|
||||
at the moment, more might be introduced in the future).
|
||||
|
||||
|
||||
The ``yield_fixture`` function/decorator
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -200,415 +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.
|
||||
|
||||
.. _node-ctor-fspath-deprecation:
|
||||
|
||||
``fspath`` argument for Node constructors replaced with ``pathlib.Path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
|
||||
In order to support the transition from ``py.path.local`` to :mod:`pathlib`,
|
||||
the ``fspath`` argument to :class:`~_pytest.nodes.Node` constructors like
|
||||
:func:`pytest.Function.from_parent()` and :func:`pytest.Class.from_parent()`
|
||||
is now deprecated.
|
||||
|
||||
Plugins which construct nodes should pass the ``path`` argument, of type
|
||||
:class:`pathlib.Path`, instead of the ``fspath`` argument.
|
||||
|
||||
Plugins which implement custom items and collectors are encouraged to replace
|
||||
``fspath`` parameters (``py.path.local``) with ``path`` parameters
|
||||
(``pathlib.Path``), and drop any other usage of the ``py`` library if possible.
|
||||
|
||||
If possible, plugins with custom items should use :ref:`cooperative
|
||||
constructors <uncooperative-constructors-deprecated>` to avoid hardcoding
|
||||
arguments they only pass on to the superclass.
|
||||
|
||||
.. note::
|
||||
The name of the :class:`~_pytest.nodes.Node` arguments and attributes (the
|
||||
new attribute being ``path``) is **the opposite** of the situation for
|
||||
hooks, :ref:`outlined below <legacy-path-hooks-deprecated>` (the old
|
||||
argument being ``path``).
|
||||
|
||||
This is an unfortunate artifact due to historical reasons, which should be
|
||||
resolved in future versions as we slowly get rid of the :pypi:`py`
|
||||
dependency (see :issue:`9283` for a longer discussion).
|
||||
|
||||
Due to the ongoing migration of methods like :meth:`~pytest.Item.reportinfo`
|
||||
which still is expected to return a ``py.path.local`` object, nodes still have
|
||||
both ``fspath`` (``py.path.local``) and ``path`` (``pathlib.Path``) attributes,
|
||||
no matter what argument was used in the constructor. We expect to deprecate the
|
||||
``fspath`` attribute in a future release.
|
||||
|
||||
|
||||
``py.path.local`` arguments for hooks replaced with ``pathlib.Path``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
In order to support the transition from ``py.path.local`` to :mod:`pathlib`, the following hooks now receive additional arguments:
|
||||
|
||||
* :hook:`pytest_ignore_collect(collection_path: pathlib.Path) <pytest_ignore_collect>` as equivalent to ``path``
|
||||
* :hook:`pytest_collect_file(file_path: pathlib.Path) <pytest_collect_file>` as equivalent to ``path``
|
||||
* :hook:`pytest_pycollect_makemodule(module_path: pathlib.Path) <pytest_pycollect_makemodule>` as equivalent to ``path``
|
||||
* :hook:`pytest_report_header(start_path: pathlib.Path) <pytest_report_header>` as equivalent to ``startdir``
|
||||
* :hook:`pytest_report_collectionfinish(start_path: pathlib.Path) <pytest_report_collectionfinish>` as equivalent to ``startdir``
|
||||
|
||||
The accompanying ``py.path.local`` based paths have been deprecated: plugins which manually invoke those hooks should only pass the new ``pathlib.Path`` arguments, and users should change their hook implementations to use the new ``pathlib.Path`` arguments.
|
||||
|
||||
.. note::
|
||||
The name of the :class:`~_pytest.nodes.Node` arguments and attributes,
|
||||
:ref:`outlined above <node-ctor-fspath-deprecation>` (the new attribute
|
||||
being ``path``) is **the opposite** of the situation for hooks (the old
|
||||
argument being ``path``).
|
||||
|
||||
This is an unfortunate artifact due to historical reasons, which should be
|
||||
resolved in future versions as we slowly get rid of the :pypi:`py`
|
||||
dependency (see :issue:`9283` for a longer discussion).
|
||||
|
||||
|
||||
.. _nose-deprecation:
|
||||
|
||||
Support for tests written for nose
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.2
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
Support for running tests written for `nose <https://nose.readthedocs.io/en/latest/>`__ is now deprecated.
|
||||
|
||||
``nose`` has been in maintenance mode-only for years, and maintaining the plugin is not trivial as it spills
|
||||
over the code base (see :issue:`9886` for more details).
|
||||
|
||||
setup/teardown
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
One thing that might catch users by surprise is that plain ``setup`` and ``teardown`` methods are not pytest native,
|
||||
they are in fact part of the ``nose`` support.
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Test:
|
||||
def setup(self):
|
||||
self.resource = make_resource()
|
||||
|
||||
def teardown(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self): ...
|
||||
|
||||
def test_bar(self): ...
|
||||
|
||||
|
||||
|
||||
Native pytest support uses ``setup_method`` and ``teardown_method`` (see :ref:`xunit-method-setup`), so the above should be changed to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class Test:
|
||||
def setup_method(self):
|
||||
self.resource = make_resource()
|
||||
|
||||
def teardown_method(self):
|
||||
self.resource.close()
|
||||
|
||||
def test_foo(self): ...
|
||||
|
||||
def test_bar(self): ...
|
||||
|
||||
|
||||
This is easy to do in an entire code base by doing a simple find/replace.
|
||||
|
||||
@with_setup
|
||||
^^^^^^^^^^^
|
||||
|
||||
Code using `@with_setup <with-setup-nose>`_ such as this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from nose.tools import with_setup
|
||||
|
||||
|
||||
def setup_some_resource(): ...
|
||||
|
||||
|
||||
def teardown_some_resource(): ...
|
||||
|
||||
|
||||
@with_setup(setup_some_resource, teardown_some_resource)
|
||||
def test_foo(): ...
|
||||
|
||||
Will also need to be ported to a supported pytest style. One way to do it is using a fixture:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def setup_some_resource(): ...
|
||||
|
||||
|
||||
def teardown_some_resource(): ...
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def some_resource():
|
||||
setup_some_resource()
|
||||
yield
|
||||
teardown_some_resource()
|
||||
|
||||
|
||||
def test_foo(some_resource): ...
|
||||
|
||||
|
||||
.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup
|
||||
|
||||
|
||||
The ``compat_co_firstlineno`` attribute
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Nose inspects this attribute on function objects to allow overriding the function's inferred line number.
|
||||
Pytest no longer respects this attribute.
|
||||
|
||||
|
||||
|
||||
Passing ``msg=`` to ``pytest.skip``, ``pytest.fail`` or ``pytest.exit``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
Passing the keyword argument ``msg`` to :func:`pytest.skip`, :func:`pytest.fail` or :func:`pytest.exit`
|
||||
is now deprecated and ``reason`` should be used instead. This change is to bring consistency between these
|
||||
functions and the ``@pytest.mark.skip`` and ``@pytest.mark.xfail`` markers which already accept a ``reason`` argument.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_fail_example():
|
||||
# old
|
||||
pytest.fail(msg="foo")
|
||||
# new
|
||||
pytest.fail(reason="bar")
|
||||
|
||||
|
||||
def test_skip_example():
|
||||
# old
|
||||
pytest.skip(msg="foo")
|
||||
# new
|
||||
pytest.skip(reason="bar")
|
||||
|
||||
|
||||
def test_exit_example():
|
||||
# old
|
||||
pytest.exit(msg="foo")
|
||||
# new
|
||||
pytest.exit(reason="bar")
|
||||
|
||||
|
||||
.. _instance-collector-deprecation:
|
||||
|
||||
The ``pytest.Instance`` collector
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionremoved:: 7.0
|
||||
|
||||
The ``pytest.Instance`` collector type has been removed.
|
||||
|
||||
Previously, Python test methods were collected as :class:`~pytest.Class` -> ``Instance`` -> :class:`~pytest.Function`.
|
||||
Now :class:`~pytest.Class` collects the test methods directly.
|
||||
|
||||
Most plugins which reference ``Instance`` do so in order to ignore or skip it,
|
||||
using a check such as ``if isinstance(node, Instance): return``.
|
||||
Such plugins should simply remove consideration of ``Instance`` on pytest>=7.
|
||||
However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``,
|
||||
and importing it emits a deprecation warning. This was removed in pytest 8.
|
||||
|
||||
|
||||
Using ``pytest.warns(None)``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
:func:`pytest.warns(None) <pytest.warns>` is now deprecated because it was frequently misused.
|
||||
Its correct usage was checking that the code emits at least one warning of any type - like ``pytest.warns()``
|
||||
or ``pytest.warns(Warning)``.
|
||||
|
||||
See :ref:`warns use cases` for examples.
|
||||
|
||||
|
||||
Backward compatibilities in ``Parser.addoption``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 2.4
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
Several behaviors of :meth:`Parser.addoption <pytest.Parser.addoption>` are now
|
||||
removed in pytest 8 (deprecated since pytest 2.4.0):
|
||||
|
||||
- ``parser.addoption(..., help=".. %default ..")`` - use ``%(default)s`` instead.
|
||||
- ``parser.addoption(..., type="int/string/float/complex")`` - use ``type=int`` etc. instead.
|
||||
|
||||
|
||||
The ``--strict`` command-line option
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 6.2
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
The ``--strict`` command-line option has been deprecated in favor of ``--strict-markers``, which
|
||||
better conveys what the option does.
|
||||
|
||||
We have plans to maybe in the future to reintroduce ``--strict`` and make it an encompassing
|
||||
flag for all strictness related options (``--strict-markers`` and ``--strict-config``
|
||||
at the moment, more might be introduced in the future).
|
||||
|
||||
|
||||
.. _cmdline-preparse-deprecated:
|
||||
|
||||
Implementing the ``pytest_cmdline_preparse`` hook
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 7.0
|
||||
.. versionremoved:: 8.0
|
||||
|
||||
Implementing the ``pytest_cmdline_preparse`` hook has been officially deprecated.
|
||||
Implement the :hook:`pytest_load_initial_conftests` hook instead.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_cmdline_preparse(config: Config, args: List[str]) -> None: ...
|
||||
|
||||
|
||||
# becomes:
|
||||
|
||||
|
||||
def pytest_load_initial_conftests(
|
||||
early_config: Config, parser: Parser, args: List[str]
|
||||
) -> None: ...
|
||||
|
||||
|
||||
Collection changes in pytest 8
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Added a new :class:`pytest.Directory` base collection node, which all collector nodes for filesystem directories are expected to subclass.
|
||||
This is analogous to the existing :class:`pytest.File` for file nodes.
|
||||
|
||||
Changed :class:`pytest.Package` to be a subclass of :class:`pytest.Directory`.
|
||||
A ``Package`` represents a filesystem directory which is a Python package,
|
||||
i.e. contains an ``__init__.py`` file.
|
||||
|
||||
:class:`pytest.Package` now only collects files in its own directory; previously it collected recursively.
|
||||
Sub-directories are collected as sub-collector nodes, thus creating a collection tree which mirrors the filesystem hierarchy.
|
||||
|
||||
:attr:`session.name <pytest.Session.name>` is now ``""``; previously it was the rootdir directory name.
|
||||
This matches :attr:`session.nodeid <_pytest.nodes.Node.nodeid>` which has always been `""`.
|
||||
|
||||
Added a new :class:`pytest.Dir` concrete collection node, a subclass of :class:`pytest.Directory`.
|
||||
This node represents a filesystem directory, which is not a :class:`pytest.Package`,
|
||||
i.e. does not contain an ``__init__.py`` file.
|
||||
Similarly to ``Package``, it only collects the files in its own directory,
|
||||
while collecting sub-directories as sub-collector nodes.
|
||||
|
||||
Files and directories are now collected in alphabetical order jointly, unless changed by a plugin.
|
||||
Previously, files were collected before directories.
|
||||
|
||||
The collection tree now contains directories/packages up to the :ref:`rootdir <rootdir>`,
|
||||
for initial arguments that are found within the rootdir.
|
||||
For files outside the rootdir, only the immediate directory/package is collected --
|
||||
note however that collecting from outside the rootdir is discouraged.
|
||||
|
||||
As an example, given the following filesystem tree::
|
||||
|
||||
myroot/
|
||||
pytest.ini
|
||||
top/
|
||||
├── aaa
|
||||
│ └── test_aaa.py
|
||||
├── test_a.py
|
||||
├── test_b
|
||||
│ ├── __init__.py
|
||||
│ └── test_b.py
|
||||
├── test_c.py
|
||||
└── zzz
|
||||
├── __init__.py
|
||||
└── test_zzz.py
|
||||
|
||||
the collection tree, as shown by `pytest --collect-only top/` but with the otherwise-hidden :class:`~pytest.Session` node added for clarity,
|
||||
is now the following::
|
||||
|
||||
<Session>
|
||||
<Dir myroot>
|
||||
<Dir top>
|
||||
<Dir aaa>
|
||||
<Module test_aaa.py>
|
||||
<Function test_it>
|
||||
<Module test_a.py>
|
||||
<Function test_it>
|
||||
<Package test_b>
|
||||
<Module test_b.py>
|
||||
<Function test_it>
|
||||
<Module test_c.py>
|
||||
<Function test_it>
|
||||
<Package zzz>
|
||||
<Module test_zzz.py>
|
||||
<Function test_it>
|
||||
|
||||
Previously, it was::
|
||||
|
||||
<Session>
|
||||
<Module top/test_a.py>
|
||||
<Function test_it>
|
||||
<Module top/test_c.py>
|
||||
<Function test_it>
|
||||
<Module top/aaa/test_aaa.py>
|
||||
<Function test_it>
|
||||
<Package test_b>
|
||||
<Module test_b.py>
|
||||
<Function test_it>
|
||||
<Package zzz>
|
||||
<Module test_zzz.py>
|
||||
<Function test_it>
|
||||
|
||||
Code/plugins which rely on a specific shape of the collection tree might need to update.
|
||||
|
||||
|
||||
: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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@@ -732,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:
|
||||
@@ -909,7 +773,8 @@ Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated
|
||||
(50, 500),
|
||||
],
|
||||
)
|
||||
def test_foo(a, b): ...
|
||||
def test_foo(a, b):
|
||||
...
|
||||
|
||||
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
|
||||
call.
|
||||
@@ -932,7 +797,8 @@ To update the code, use ``pytest.param``:
|
||||
(50, 500),
|
||||
],
|
||||
)
|
||||
def test_foo(a, b): ...
|
||||
def test_foo(a, b):
|
||||
...
|
||||
|
||||
|
||||
.. _pytest_funcarg__ prefix deprecated:
|
||||
@@ -1083,13 +949,15 @@ This is just a matter of renaming the fixture as the API is the same:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_xml_property): ...
|
||||
def test_foo(record_xml_property):
|
||||
...
|
||||
|
||||
Change to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_property): ...
|
||||
def test_foo(record_property):
|
||||
...
|
||||
|
||||
|
||||
.. _passing command-line string to pytest.main deprecated:
|
||||
@@ -1251,7 +1119,8 @@ Example of usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MySymbol: ...
|
||||
class MySymbol:
|
||||
...
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
|
||||
@@ -172,7 +172,7 @@ class TestRaises:
|
||||
raise ValueError("demo error")
|
||||
|
||||
def test_tupleerror(self):
|
||||
a, b = [1] # noqa: F841
|
||||
a, b = [1] # NOQA
|
||||
|
||||
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
|
||||
items = [1, 2, 3]
|
||||
@@ -180,7 +180,7 @@ class TestRaises:
|
||||
a, b = items.pop()
|
||||
|
||||
def test_some_error(self):
|
||||
if namenotexi: # noqa: F821
|
||||
if namenotexi: # NOQA
|
||||
pass
|
||||
|
||||
def func1(self):
|
||||
|
||||
@@ -2,7 +2,6 @@ import os.path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
mydir = os.path.dirname(__file__)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import os.path
|
||||
import shutil
|
||||
|
||||
|
||||
failure_demo = os.path.join(os.path.dirname(__file__), "failure_demo.py")
|
||||
pytest_plugins = ("pytester",)
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
collect_ignore = ["nonpython", "customdirectory"]
|
||||
collect_ignore = ["nonpython"]
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
.. _`custom directory collectors`:
|
||||
|
||||
Using a custom directory collector
|
||||
====================================================
|
||||
|
||||
By default, pytest collects directories using :class:`pytest.Package`, for directories with ``__init__.py`` files,
|
||||
and :class:`pytest.Dir` for other directories.
|
||||
If you want to customize how a directory is collected, you can write your own :class:`pytest.Directory` collector,
|
||||
and use :hook:`pytest_collect_directory` to hook it up.
|
||||
|
||||
.. _`directory manifest plugin`:
|
||||
|
||||
A basic example for a directory manifest file
|
||||
--------------------------------------------------------------
|
||||
|
||||
Suppose you want to customize how collection is done on a per-directory basis.
|
||||
Here is an example ``conftest.py`` plugin that allows directories to contain a ``manifest.json`` file,
|
||||
which defines how the collection should be done for the directory.
|
||||
In this example, only a simple list of files is supported,
|
||||
however you can imagine adding other keys, such as exclusions and globs.
|
||||
|
||||
.. include:: customdirectory/conftest.py
|
||||
:literal:
|
||||
|
||||
You can create a ``manifest.json`` file and some test files:
|
||||
|
||||
.. include:: customdirectory/tests/manifest.json
|
||||
:literal:
|
||||
|
||||
.. include:: customdirectory/tests/test_first.py
|
||||
:literal:
|
||||
|
||||
.. include:: customdirectory/tests/test_second.py
|
||||
:literal:
|
||||
|
||||
.. include:: customdirectory/tests/test_third.py
|
||||
:literal:
|
||||
|
||||
An you can now execute the test specification:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
customdirectory $ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project/customdirectory
|
||||
configfile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
tests/test_first.py . [ 50%]
|
||||
tests/test_second.py . [100%]
|
||||
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
Notice how ``test_three.py`` was not executed, because it is not listed in the manifest.
|
||||
|
||||
You can verify that your custom collector appears in the collection tree:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
customdirectory $ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project/customdirectory
|
||||
configfile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
<Dir customdirectory>
|
||||
<ManifestDirectory tests>
|
||||
<Module test_first.py>
|
||||
<Function test_1>
|
||||
<Module test_second.py>
|
||||
<Function test_2>
|
||||
|
||||
======================== 2 tests collected in 0.12s ========================
|
||||
@@ -1,28 +0,0 @@
|
||||
# content of conftest.py
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class ManifestDirectory(pytest.Directory):
|
||||
def collect(self):
|
||||
# The standard pytest behavior is to loop over all `test_*.py` files and
|
||||
# call `pytest_collect_file` on each file. This collector instead reads
|
||||
# the `manifest.json` file and only calls `pytest_collect_file` for the
|
||||
# files defined there.
|
||||
manifest_path = self.path / "manifest.json"
|
||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
||||
ihook = self.ihook
|
||||
for file in manifest["files"]:
|
||||
yield from ihook.pytest_collect_file(
|
||||
file_path=self.path / file, parent=self
|
||||
)
|
||||
|
||||
|
||||
@pytest.hookimpl
|
||||
def pytest_collect_directory(path, parent):
|
||||
# Use our custom collector for directories containing a `mainfest.json` file.
|
||||
if path.joinpath("manifest.json").is_file():
|
||||
return ManifestDirectory.from_parent(parent=parent, path=path)
|
||||
# Otherwise fallback to the standard behavior.
|
||||
return None
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
"test_first.py",
|
||||
"test_second.py"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
# content of test_first.py
|
||||
def test_1():
|
||||
pass
|
||||
@@ -1,3 +0,0 @@
|
||||
# content of test_second.py
|
||||
def test_2():
|
||||
pass
|
||||
@@ -1,3 +0,0 @@
|
||||
# content of test_third.py
|
||||
def test_3():
|
||||
pass
|
||||
@@ -18,6 +18,7 @@ For basic examples, see
|
||||
- :ref:`Fixtures <fixtures>` for basic fixture/setup examples
|
||||
- :ref:`parametrize` for basic test function parametrization
|
||||
- :ref:`unittest` for basic unittest integration
|
||||
- :ref:`noseintegration` for basic nosetests integration
|
||||
|
||||
The following examples aim at various use cases you might encounter.
|
||||
|
||||
@@ -31,4 +32,3 @@ The following examples aim at various use cases you might encounter.
|
||||
special
|
||||
pythoncollection
|
||||
nonpython
|
||||
customdirectory
|
||||
|
||||
@@ -45,7 +45,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
|
||||
|
||||
$ pytest -v -m webtest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
@@ -60,7 +60,7 @@ Or the inverse, running all tests except the webtest ones:
|
||||
|
||||
$ pytest -v -m "not webtest"
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
@@ -82,7 +82,7 @@ tests based on their module, class, method, or function name:
|
||||
|
||||
$ pytest -v test_server.py::TestClass::test_method
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 1 item
|
||||
@@ -97,7 +97,7 @@ You can also select on the class:
|
||||
|
||||
$ pytest -v test_server.py::TestClass
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 1 item
|
||||
@@ -112,7 +112,7 @@ Or select multiple nodes:
|
||||
|
||||
$ pytest -v test_server.py::TestClass test_server.py::test_send_http
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 2 items
|
||||
@@ -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
|
||||
-------------------------------------------------------
|
||||
@@ -156,7 +156,7 @@ The expression matching is now case-insensitive.
|
||||
|
||||
$ pytest -v -k http # running with the above defined example module
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
@@ -171,7 +171,7 @@ And you can also run all tests except the ones that match the keyword:
|
||||
|
||||
$ pytest -k "not send_http" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
@@ -188,7 +188,7 @@ Or to select "http" and "quick" tests:
|
||||
|
||||
$ pytest -k "http or quick" -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 4 items / 2 deselected / 2 selected
|
||||
@@ -397,7 +397,7 @@ the test needs:
|
||||
|
||||
$ pytest -E stage2
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -411,7 +411,7 @@ and here is one that specifies exactly the environment needed:
|
||||
|
||||
$ pytest -E stage1
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -604,7 +604,7 @@ then you will see two tests skipped and two executed tests as expected:
|
||||
|
||||
$ pytest -rs # this option reports skip reasons
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items
|
||||
|
||||
@@ -620,7 +620,7 @@ Note that if you specify a platform via the marker-command line option like this
|
||||
|
||||
$ pytest -m linux
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
@@ -683,7 +683,7 @@ We can now use the ``-m option`` to select one set:
|
||||
|
||||
$ pytest -m interface --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
@@ -709,7 +709,7 @@ or to select both "event" and "interface" tests:
|
||||
|
||||
$ pytest -m "interface or event" --tb=short
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
|
||||
@@ -1,14 +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)
|
||||
@@ -33,33 +33,37 @@ class Python:
|
||||
dumpfile = self.picklefile.with_name("dump.py")
|
||||
dumpfile.write_text(
|
||||
textwrap.dedent(
|
||||
rf"""
|
||||
r"""
|
||||
import pickle
|
||||
f = open({str(self.picklefile)!r}, 'wb')
|
||||
s = pickle.dump({obj!r}, f, protocol=2)
|
||||
f = open({!r}, 'wb')
|
||||
s = pickle.dump({!r}, f, protocol=2)
|
||||
f.close()
|
||||
"""
|
||||
""".format(
|
||||
str(self.picklefile), obj
|
||||
)
|
||||
)
|
||||
)
|
||||
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")
|
||||
loadfile.write_text(
|
||||
textwrap.dedent(
|
||||
rf"""
|
||||
r"""
|
||||
import pickle
|
||||
f = open({str(self.picklefile)!r}, 'rb')
|
||||
f = open({!r}, 'rb')
|
||||
obj = pickle.load(f)
|
||||
f.close()
|
||||
res = eval({expression!r})
|
||||
res = eval({!r})
|
||||
if not res:
|
||||
raise SystemExit(1)
|
||||
"""
|
||||
""".format(
|
||||
str(self.picklefile), expression
|
||||
)
|
||||
)
|
||||
)
|
||||
print(loadfile)
|
||||
subprocess.run((self.pythonpath, str(loadfile)), check=True)
|
||||
subprocess.check_call((self.pythonpath, str(loadfile)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize("obj", [42, {}, {1: 3}])
|
||||
|
||||
@@ -28,7 +28,7 @@ now execute the test specification:
|
||||
|
||||
nonpython $ pytest test_simple.yaml
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project/nonpython
|
||||
collected 2 items
|
||||
|
||||
@@ -64,7 +64,7 @@ consulted when reporting in ``verbose`` mode:
|
||||
|
||||
nonpython $ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project/nonpython
|
||||
collecting ... collected 2 items
|
||||
@@ -90,7 +90,7 @@ interesting to just look at the collection tree:
|
||||
|
||||
nonpython $ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project/nonpython
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -38,7 +38,6 @@ class YamlItem(pytest.Item):
|
||||
" no further details known at this point.",
|
||||
]
|
||||
)
|
||||
return super().repr_failure(excinfo)
|
||||
|
||||
def reportinfo(self):
|
||||
return self.path, 0, f"usecase: {self.name}"
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
Parametrizing tests
|
||||
=================================================
|
||||
|
||||
.. currentmodule:: _pytest.python
|
||||
|
||||
``pytest`` allows to easily parametrize test functions.
|
||||
For basic docs, see :ref:`parametrize-basics`.
|
||||
|
||||
@@ -158,20 +160,19 @@ objects, they are still using the default pytest representation:
|
||||
|
||||
$ pytest test_time.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 8 items
|
||||
|
||||
<Dir parametrize.rst-195>
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
<Function test_timedistance_v0[a1-b1-expected1]>
|
||||
<Function test_timedistance_v1[forward]>
|
||||
<Function test_timedistance_v1[backward]>
|
||||
<Function test_timedistance_v2[20011212-20011211-expected0]>
|
||||
<Function test_timedistance_v2[20011211-20011212-expected1]>
|
||||
<Function test_timedistance_v3[forward]>
|
||||
<Function test_timedistance_v3[backward]>
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
<Function test_timedistance_v0[a1-b1-expected1]>
|
||||
<Function test_timedistance_v1[forward]>
|
||||
<Function test_timedistance_v1[backward]>
|
||||
<Function test_timedistance_v2[20011212-20011211-expected0]>
|
||||
<Function test_timedistance_v2[20011211-20011212-expected1]>
|
||||
<Function test_timedistance_v3[forward]>
|
||||
<Function test_timedistance_v3[backward]>
|
||||
|
||||
======================== 8 tests collected in 0.12s ========================
|
||||
|
||||
@@ -184,7 +185,7 @@ A quick port of "testscenarios"
|
||||
Here is a quick port to run tests configured with :pypi:`testscenarios`,
|
||||
an add-on from Robert Collins for the standard unittest framework. We
|
||||
only have to work a bit to construct the correct arguments for pytest's
|
||||
:py:func:`Metafunc.parametrize <pytest.Metafunc.parametrize>`:
|
||||
:py:func:`Metafunc.parametrize`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -221,7 +222,7 @@ this is a fully self-contained example which you can run with:
|
||||
|
||||
$ pytest test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items
|
||||
|
||||
@@ -235,17 +236,16 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||
|
||||
$ pytest --collect-only test_scenarios.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items
|
||||
|
||||
<Dir parametrize.rst-195>
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
<Function test_demo1[basic]>
|
||||
<Function test_demo2[basic]>
|
||||
<Function test_demo1[advanced]>
|
||||
<Function test_demo2[advanced]>
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
<Function test_demo1[basic]>
|
||||
<Function test_demo2[basic]>
|
||||
<Function test_demo1[advanced]>
|
||||
<Function test_demo2[advanced]>
|
||||
|
||||
======================== 4 tests collected in 0.12s ========================
|
||||
|
||||
@@ -314,14 +314,13 @@ Let's first see how it looks like at collection time:
|
||||
|
||||
$ pytest test_backends.py --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
<Dir parametrize.rst-195>
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
|
||||
======================== 2 tests collected in 0.12s ========================
|
||||
|
||||
@@ -413,7 +412,7 @@ The result of this test will be successful:
|
||||
|
||||
$ pytest -v test_indirect_list.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 1 item
|
||||
@@ -484,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
|
||||
@@ -503,14 +502,15 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
ssssssssssss...ssssssssssss [100%]
|
||||
sssssssssssssssssssssssssss [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [12] multipython.py:65: 'python3.9' not found
|
||||
SKIPPED [12] multipython.py:65: 'python3.11' not found
|
||||
3 passed, 24 skipped in 0.12s
|
||||
SKIPPED [9] multipython.py:69: 'python3.5' not found
|
||||
SKIPPED [9] multipython.py:69: 'python3.6' not found
|
||||
SKIPPED [9] multipython.py:69: 'python3.7' not found
|
||||
27 skipped in 0.12s
|
||||
|
||||
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
|
||||
@@ -567,7 +567,7 @@ If you run this with reporting for skips enabled:
|
||||
|
||||
$ pytest -rs test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
@@ -628,7 +628,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
||||
|
||||
$ pytest -v -m basic
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 24 items / 21 deselected / 3 selected
|
||||
@@ -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``.
|
||||
|
||||
@@ -147,16 +147,15 @@ The test collection would look like this:
|
||||
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
configfile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
<Dir pythoncollection.rst-196>
|
||||
<Module check_myapp.py>
|
||||
<Class CheckMyApp>
|
||||
<Function simple_check>
|
||||
<Function complex_check>
|
||||
<Module check_myapp.py>
|
||||
<Class CheckMyApp>
|
||||
<Function simple_check>
|
||||
<Function complex_check>
|
||||
|
||||
======================== 2 tests collected in 0.12s ========================
|
||||
|
||||
@@ -210,18 +209,16 @@ You can always peek at the collection tree without running tests like this:
|
||||
|
||||
. $ pytest --collect-only pythoncollection.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
configfile: pytest.ini
|
||||
collected 3 items
|
||||
|
||||
<Dir pythoncollection.rst-196>
|
||||
<Dir CWD>
|
||||
<Module pythoncollection.py>
|
||||
<Function test_function>
|
||||
<Class TestClass>
|
||||
<Function test_method>
|
||||
<Function test_anothermethod>
|
||||
<Module CWD/pythoncollection.py>
|
||||
<Function test_function>
|
||||
<Class TestClass>
|
||||
<Function test_method>
|
||||
<Function test_anothermethod>
|
||||
|
||||
======================== 3 tests collected in 0.12s ========================
|
||||
|
||||
@@ -294,7 +291,7 @@ file will be left out:
|
||||
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
configfile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
@@ -9,7 +9,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
|
||||
assertion $ pytest failure_demo.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project/assertion
|
||||
collected 44 items
|
||||
|
||||
@@ -80,7 +80,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_text(self):
|
||||
> assert "spam" == "eggs"
|
||||
E AssertionError: assert 'spam' == 'eggs'
|
||||
E
|
||||
E - eggs
|
||||
E + spam
|
||||
|
||||
@@ -92,7 +91,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_similar_text(self):
|
||||
> assert "foo 1 bar" == "foo 2 bar"
|
||||
E AssertionError: assert 'foo 1 bar' == 'foo 2 bar'
|
||||
E
|
||||
E - foo 2 bar
|
||||
E ? ^
|
||||
E + foo 1 bar
|
||||
@@ -106,7 +104,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_multiline_text(self):
|
||||
> assert "foo\nspam\nbar" == "foo\neggs\nbar"
|
||||
E AssertionError: assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
|
||||
E
|
||||
E foo
|
||||
E - eggs
|
||||
E + spam
|
||||
@@ -122,7 +119,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
b = "1" * 100 + "b" + "2" * 100
|
||||
> assert a == b
|
||||
E AssertionError: assert '111111111111...2222222222222' == '111111111111...2222222222222'
|
||||
E
|
||||
E Skipping 90 identical leading characters in diff, use -v to show
|
||||
E Skipping 91 identical trailing characters in diff, use -v to show
|
||||
E - 1111111111b222222222
|
||||
@@ -140,15 +136,15 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
b = "1\n" * 100 + "b" + "2\n" * 100
|
||||
> assert a == b
|
||||
E AssertionError: assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n'
|
||||
E
|
||||
E Skipping 190 identical leading characters in diff, use -v to show
|
||||
E Skipping 191 identical trailing characters in diff, use -v to show
|
||||
E 1
|
||||
E 1
|
||||
E 1
|
||||
E 1
|
||||
E 1...
|
||||
E
|
||||
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||
E ...Full output truncated (6 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:60: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
@@ -158,7 +154,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_list(self):
|
||||
> assert [0, 1, 2] == [0, 1, 3]
|
||||
E assert [0, 1, 2] == [0, 1, 3]
|
||||
E
|
||||
E At index 2 diff: 2 != 3
|
||||
E Use -v to get more diff
|
||||
|
||||
@@ -172,7 +167,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
b = [0] * 100 + [2] + [3] * 100
|
||||
> assert a == b
|
||||
E assert [0, 0, 0, 0, 0, 0, ...] == [0, 0, 0, 0, 0, 0, ...]
|
||||
E
|
||||
E At index 100 diff: 1 != 2
|
||||
E Use -v to get more diff
|
||||
|
||||
@@ -184,7 +178,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_dict(self):
|
||||
> assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0}
|
||||
E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
|
||||
E
|
||||
E Omitting 1 identical items, use -vv to show
|
||||
E Differing items:
|
||||
E {'b': 1} != {'b': 2}
|
||||
@@ -202,7 +195,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_set(self):
|
||||
> assert {0, 10, 11, 12} == {0, 20, 21}
|
||||
E assert {0, 10, 11, 12} == {0, 20, 21}
|
||||
E
|
||||
E Extra items in the left set:
|
||||
E 10
|
||||
E 11
|
||||
@@ -220,7 +212,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
def test_eq_longer_list(self):
|
||||
> assert [1, 2] == [1, 2, 3]
|
||||
E assert [1, 2] == [1, 2, 3]
|
||||
E
|
||||
E Right contains one more item: 3
|
||||
E Use -v to get more diff
|
||||
|
||||
@@ -242,7 +233,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
text = "some multiline\ntext\nwhich\nincludes foo\nand a\ntail"
|
||||
> assert "foo" not in text
|
||||
E AssertionError: assert 'foo' not in 'some multil...nand a\ntail'
|
||||
E
|
||||
E 'foo' is contained here:
|
||||
E some multiline
|
||||
E text
|
||||
@@ -261,7 +251,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
text = "single foo line"
|
||||
> assert "foo" not in text
|
||||
E AssertionError: assert 'foo' not in 'single foo line'
|
||||
E
|
||||
E 'foo' is contained here:
|
||||
E single foo line
|
||||
E ? +++
|
||||
@@ -275,7 +264,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
text = "head " * 50 + "foo " + "tail " * 20
|
||||
> assert "foo" not in text
|
||||
E AssertionError: assert 'foo' not in 'head head h...l tail tail '
|
||||
E
|
||||
E 'foo' is contained here:
|
||||
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? +++
|
||||
@@ -289,7 +277,6 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
text = "head " * 50 + "f" * 70 + "tail " * 20
|
||||
> assert "f" * 70 not in text
|
||||
E AssertionError: assert 'fffffffffff...ffffffffffff' not in 'head head h...l tail tail '
|
||||
E
|
||||
E 'ffffffffffffffffff...fffffffffffffffffff' is contained here:
|
||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
@@ -445,7 +432,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0020>
|
||||
|
||||
def test_tupleerror(self):
|
||||
> a, b = [1] # noqa: F841
|
||||
> a, b = [1] # NOQA
|
||||
E ValueError: not enough values to unpack (expected 2, got 1)
|
||||
|
||||
failure_demo.py:175: ValueError
|
||||
@@ -467,7 +454,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef0022>
|
||||
|
||||
def test_some_error(self):
|
||||
> if namenotexi: # noqa: F821
|
||||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:183: NameError
|
||||
|
||||
@@ -168,7 +168,7 @@ Now we'll get feedback on a bad argument:
|
||||
|
||||
|
||||
If you need to provide more detailed error messages, you can use the
|
||||
``type`` parameter and raise :exc:`pytest.UsageError`:
|
||||
``type`` parameter and raise ``pytest.UsageError``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -232,7 +232,7 @@ directory with the above conftest.py:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 0 items
|
||||
|
||||
@@ -296,7 +296,7 @@ and when running it will see a skipped "slow" test:
|
||||
|
||||
$ pytest -rs # "-rs" means report details on the little 's'
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
@@ -312,7 +312,7 @@ Or run it including the ``slow`` marked test:
|
||||
|
||||
$ pytest --runslow
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
@@ -456,7 +456,7 @@ which will add the string to the test header accordingly:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
project deps: mylib-1.1
|
||||
rootdir: /home/sweet/project
|
||||
collected 0 items
|
||||
@@ -484,7 +484,7 @@ which will add info only when run with "--v":
|
||||
|
||||
$ pytest -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
@@ -499,7 +499,7 @@ and nothing when run plainly:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 0 items
|
||||
|
||||
@@ -538,7 +538,7 @@ Now we can profile which test functions execute the slowest:
|
||||
|
||||
$ pytest --durations=3
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 3 items
|
||||
|
||||
@@ -644,7 +644,7 @@ If we run this:
|
||||
|
||||
$ pytest -rx
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 4 items
|
||||
|
||||
@@ -660,31 +660,6 @@ If we run this:
|
||||
E assert 0
|
||||
|
||||
test_step.py:11: AssertionError
|
||||
================================ XFAILURES =================================
|
||||
______________________ TestUserHandling.test_deletion ______________________
|
||||
|
||||
item = <Function test_deletion>
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
if "incremental" in item.keywords:
|
||||
# retrieve the class name of the test
|
||||
cls_name = str(item.cls)
|
||||
# check if a previous test has failed for this class
|
||||
if cls_name in _test_failed_incremental:
|
||||
# retrieve the index of the test (if parametrize is used in combination with incremental)
|
||||
parametrize_index = (
|
||||
tuple(item.callspec.indices.values())
|
||||
if hasattr(item, "callspec")
|
||||
else ()
|
||||
)
|
||||
# retrieve the name of the first test function to fail for this class name and index
|
||||
test_name = _test_failed_incremental[cls_name].get(parametrize_index, None)
|
||||
# if name found, test has failed for the combination of class name & test name
|
||||
if test_name is not None:
|
||||
> pytest.xfail(f"previous test failed ({test_name})")
|
||||
E _pytest.outcomes.XFailed: previous test failed (test_modification)
|
||||
|
||||
conftest.py:47: XFailed
|
||||
========================= short test summary info ==========================
|
||||
XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification)
|
||||
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
|
||||
@@ -716,7 +691,7 @@ Here is an example for making a ``db`` fixture available in a directory:
|
||||
pass
|
||||
|
||||
|
||||
@pytest.fixture(scope="package")
|
||||
@pytest.fixture(scope="session")
|
||||
def db():
|
||||
return DB()
|
||||
|
||||
@@ -751,14 +726,14 @@ We can run this:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 7 items
|
||||
|
||||
a/test_db.py F [ 14%]
|
||||
a/test_db2.py F [ 28%]
|
||||
b/test_error.py E [ 42%]
|
||||
test_step.py .Fx. [100%]
|
||||
test_step.py .Fx. [ 57%]
|
||||
a/test_db.py F [ 71%]
|
||||
a/test_db2.py F [ 85%]
|
||||
b/test_error.py E [100%]
|
||||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_root ________________________
|
||||
@@ -770,39 +745,39 @@ We can run this:
|
||||
|
||||
/home/sweet/project/b/test_error.py:1
|
||||
================================= FAILURES =================================
|
||||
_________________________________ test_a1 __________________________________
|
||||
|
||||
db = <conftest.DB object at 0xdeadbeef0002>
|
||||
|
||||
def test_a1(db):
|
||||
> assert 0, db # to show value
|
||||
E AssertionError: <conftest.DB object at 0xdeadbeef0002>
|
||||
E assert 0
|
||||
|
||||
a/test_db.py:2: AssertionError
|
||||
_________________________________ test_a2 __________________________________
|
||||
|
||||
db = <conftest.DB object at 0xdeadbeef0002>
|
||||
|
||||
def test_a2(db):
|
||||
> assert 0, db # to show value
|
||||
E AssertionError: <conftest.DB object at 0xdeadbeef0002>
|
||||
E assert 0
|
||||
|
||||
a/test_db2.py:2: AssertionError
|
||||
____________________ TestUserHandling.test_modification ____________________
|
||||
|
||||
self = <test_step.TestUserHandling object at 0xdeadbeef0003>
|
||||
self = <test_step.TestUserHandling object at 0xdeadbeef0002>
|
||||
|
||||
def test_modification(self):
|
||||
> assert 0
|
||||
E assert 0
|
||||
|
||||
test_step.py:11: AssertionError
|
||||
_________________________________ test_a1 __________________________________
|
||||
|
||||
db = <conftest.DB object at 0xdeadbeef0003>
|
||||
|
||||
def test_a1(db):
|
||||
> assert 0, db # to show value
|
||||
E AssertionError: <conftest.DB object at 0xdeadbeef0003>
|
||||
E assert 0
|
||||
|
||||
a/test_db.py:2: AssertionError
|
||||
_________________________________ test_a2 __________________________________
|
||||
|
||||
db = <conftest.DB object at 0xdeadbeef0003>
|
||||
|
||||
def test_a2(db):
|
||||
> assert 0, db # to show value
|
||||
E AssertionError: <conftest.DB object at 0xdeadbeef0003>
|
||||
E assert 0
|
||||
|
||||
a/test_db2.py:2: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
FAILED test_step.py::TestUserHandling::test_modification - assert 0
|
||||
FAILED a/test_db.py::test_a1 - AssertionError: <conftest.DB object at 0x7...
|
||||
FAILED a/test_db2.py::test_a2 - AssertionError: <conftest.DB object at 0x...
|
||||
FAILED test_step.py::TestUserHandling::test_modification - assert 0
|
||||
ERROR b/test_error.py::test_root
|
||||
============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ==============
|
||||
|
||||
@@ -833,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"])
|
||||
@@ -850,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:
|
||||
|
||||
@@ -871,7 +845,7 @@ and run them:
|
||||
|
||||
$ pytest test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
@@ -925,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):
|
||||
@@ -980,7 +953,7 @@ and run it:
|
||||
|
||||
$ pytest -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 3 items
|
||||
|
||||
@@ -1115,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/
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import pytest
|
||||
|
||||
|
||||
xfail = pytest.mark.xfail
|
||||
|
||||
|
||||
|
||||
@@ -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"``.
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ style of setup/teardown functions:
|
||||
In addition, pytest continues to support :ref:`xunitsetup`. You can mix
|
||||
both styles, moving incrementally from classic to new style, as you
|
||||
prefer. You can also start out from existing :ref:`unittest.TestCase
|
||||
style <unittest.TestCase>`.
|
||||
style <unittest.TestCase>` or :ref:`nose based <nosestyle>` projects.
|
||||
|
||||
|
||||
|
||||
@@ -162,7 +162,7 @@ A note about fixture cleanup
|
||||
----------------------------
|
||||
|
||||
pytest does not do any special processing for :data:`SIGTERM <signal.SIGTERM>` and
|
||||
``SIGQUIT`` signals (:data:`SIGINT <signal.SIGINT>` is handled naturally
|
||||
:data:`SIGQUIT <signal.SIGQUIT>` signals (:data:`SIGINT <signal.SIGINT>` is handled naturally
|
||||
by the Python runtime via :class:`KeyboardInterrupt`), so fixtures that manage external resources which are important
|
||||
to be cleared when the Python process is terminated (by those signals) might leak resources.
|
||||
|
||||
|
||||
@@ -60,10 +60,8 @@ Within Python modules, ``pytest`` also discovers tests using the standard
|
||||
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
|
||||
|
||||
|
||||
.. _`test layout`:
|
||||
|
||||
Choosing a test layout
|
||||
----------------------
|
||||
Choosing a test layout / import rules
|
||||
-------------------------------------
|
||||
|
||||
``pytest`` supports two common test layouts:
|
||||
|
||||
@@ -296,20 +294,3 @@ See also `pypa/setuptools#1684 <https://github.com/pypa/setuptools/issues/1684>`
|
||||
|
||||
setuptools intends to
|
||||
`remove the test command <https://github.com/pypa/setuptools/issues/931>`_.
|
||||
|
||||
Checking with flake8-pytest-style
|
||||
---------------------------------
|
||||
|
||||
In order to ensure that pytest is being used correctly in your project,
|
||||
it can be helpful to use the `flake8-pytest-style <https://github.com/m-burst/flake8-pytest-style>`_ flake8 plugin.
|
||||
|
||||
flake8-pytest-style checks for common mistakes and coding style violations in pytest code,
|
||||
such as incorrect use of fixtures, test function names, and markers.
|
||||
By using this plugin, you can catch these errors early in the development process
|
||||
and ensure that your pytest code is consistent and easy to maintain.
|
||||
|
||||
A list of the lints detected by flake8-pytest-style can be found on its `PyPI page <https://pypi.org/project/flake8-pytest-style/>`_.
|
||||
|
||||
.. note::
|
||||
|
||||
flake8-pytest-style is not an official pytest project. Some of the rules enforce certain style choices, such as using `@pytest.fixture()` over `@pytest.fixture`, but you can configure the plugin to fit your preferred style.
|
||||
|
||||
@@ -10,27 +10,19 @@ Import modes
|
||||
|
||||
pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution.
|
||||
|
||||
Importing files in Python is a non-trivial processes, so aspects of the
|
||||
Importing files in Python (at least until recently) is a non-trivial processes, often requiring
|
||||
changing :data:`sys.path`. Some aspects of the
|
||||
import process can be controlled through the ``--import-mode`` command-line flag, which can assume
|
||||
these values:
|
||||
|
||||
.. _`import-mode-prepend`:
|
||||
|
||||
* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning*
|
||||
of :py:data:`sys.path` if not already there, and then imported with
|
||||
the :func:`importlib.import_module <importlib.import_module>` function.
|
||||
of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module <importlib.import_module>` function.
|
||||
|
||||
It is highly recommended to arrange your test modules as packages by adding ``__init__.py`` files to your directories
|
||||
containing tests. This will make the tests part of a proper Python package, allowing pytest to resolve their full
|
||||
name (for example ``tests.core.test_core`` for ``test_core.py`` inside the ``tests.core`` package).
|
||||
|
||||
If the test directory tree is not arranged as packages, then each test file needs to have a unique name
|
||||
compared to the other test files, otherwise pytest will raise an error if it finds two tests with the same name.
|
||||
This requires test module names to be unique when the test directory tree is not arranged in
|
||||
packages, because the modules will put in :py:data:`sys.modules` after importing.
|
||||
|
||||
This is the classic mechanism, dating back from the time Python 2 was still supported.
|
||||
|
||||
.. _`import-mode-append`:
|
||||
|
||||
* ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already
|
||||
there, and imported with :func:`importlib.import_module <importlib.import_module>`.
|
||||
|
||||
@@ -46,78 +38,32 @@ these values:
|
||||
the tests will run against the installed version
|
||||
of ``pkg_under_test`` when ``--import-mode=append`` is used whereas
|
||||
with ``prepend`` they would pick up the local version. This kind of confusion is why
|
||||
we advocate for using :ref:`src-layouts <src-layout>`.
|
||||
we advocate for using :ref:`src <src-layout>` layouts.
|
||||
|
||||
Same as ``prepend``, requires test module names to be unique when the test directory tree is
|
||||
not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing.
|
||||
|
||||
.. _`import-mode-importlib`:
|
||||
* ``importlib``: new in pytest-6.0, this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`.
|
||||
|
||||
* ``importlib``: this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules, without changing :py:data:`sys.path`.
|
||||
For this reason this doesn't require test module names to be unique.
|
||||
|
||||
Advantages of this mode:
|
||||
One drawback however is that test modules are non-importable by each other. Also, utility
|
||||
modules in the tests directories are not automatically importable because the tests directory is no longer
|
||||
added to :py:data:`sys.path`.
|
||||
|
||||
* pytest will not change :py:data:`sys.path` at all.
|
||||
* Test module names do not need to be unique -- pytest will generate a unique name automatically based on the ``rootdir``.
|
||||
|
||||
Disadvantages:
|
||||
|
||||
* Test modules can't import each other.
|
||||
* Testing utility modules in the tests directories (for example a ``tests.helpers`` module containing test-related functions/classes)
|
||||
are not importable. The recommendation in this case it to place testing utility modules together with the application/library
|
||||
code, for example ``app.testing.helpers``.
|
||||
|
||||
Important: by "test utility modules" we mean functions/classes which are imported by
|
||||
other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along
|
||||
with the test modules, and are discovered automatically by pytest.
|
||||
|
||||
It works like this:
|
||||
|
||||
1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name
|
||||
like ``tests.core.test_models`` and tries to import it.
|
||||
|
||||
For non-test modules this will work if they are accessible via :py:data:`sys.path`, so
|
||||
for example ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``.
|
||||
This is happens when plugins import non-test modules (for example doctesting).
|
||||
|
||||
If this step succeeds, the module is returned.
|
||||
|
||||
For test modules, unless they are reachable from :py:data:`sys.path`, this step will fail.
|
||||
|
||||
2. If the previous step fails, we import the module directly using ``importlib`` facilities, which lets us import it without
|
||||
changing :py:data:`sys.path`.
|
||||
|
||||
Because Python requires the module to also be available in :py:data:`sys.modules`, pytest derives a unique name for it based
|
||||
on its relative location from the ``rootdir``, and adds the module to :py:data:`sys.modules`.
|
||||
|
||||
For example, ``tests/core/test_models.py`` will end up being imported as the module ``tests.core.test_models``.
|
||||
|
||||
.. versionadded:: 6.0
|
||||
|
||||
.. note::
|
||||
|
||||
Initially we intended to make ``importlib`` the default in future releases, however it is clear now that
|
||||
it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future.
|
||||
|
||||
.. note::
|
||||
|
||||
By default, pytest will not attempt to resolve namespace packages automatically, but that can
|
||||
be changed via the :confval:`consider_namespace_packages` configuration variable.
|
||||
Initially we intended to make ``importlib`` the default in future releases, however it is clear now that
|
||||
it has its own set of drawbacks so the default will remain ``prepend`` for the foreseeable future.
|
||||
|
||||
.. seealso::
|
||||
|
||||
The :confval:`pythonpath` configuration variable.
|
||||
|
||||
The :confval:`consider_namespace_packages` configuration variable.
|
||||
|
||||
:ref:`test layout`.
|
||||
|
||||
|
||||
``prepend`` and ``append`` import modes scenarios
|
||||
-------------------------------------------------
|
||||
|
||||
Here's a list of scenarios when using ``prepend`` or ``append`` import modes where pytest needs to
|
||||
change :py:data:`sys.path` in order to import test modules or ``conftest.py`` files, and the issues users
|
||||
change ``sys.path`` in order to import test modules or ``conftest.py`` files, and the issues users
|
||||
might encounter because of that.
|
||||
|
||||
Test modules / ``conftest.py`` files inside packages
|
||||
@@ -146,7 +92,7 @@ pytest will find ``foo/bar/tests/test_foo.py`` and realize it is part of a packa
|
||||
there's an ``__init__.py`` file in the same folder. It will then search upwards until it can find the
|
||||
last folder which still contains an ``__init__.py`` file in order to find the package *root* (in
|
||||
this case ``foo/``). To load the module, it will insert ``root/`` to the front of
|
||||
:py:data:`sys.path` (if not there already) in order to load
|
||||
``sys.path`` (if not there already) in order to load
|
||||
``test_foo.py`` as the *module* ``foo.bar.tests.test_foo``.
|
||||
|
||||
The same logic applies to the ``conftest.py`` file: it will be imported as ``foo.conftest`` module.
|
||||
@@ -176,8 +122,8 @@ When executing:
|
||||
|
||||
pytest will find ``foo/bar/tests/test_foo.py`` and realize it is NOT part of a package given that
|
||||
there's no ``__init__.py`` file in the same folder. It will then add ``root/foo/bar/tests`` to
|
||||
:py:data:`sys.path` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
|
||||
with the ``conftest.py`` file by adding ``root/foo`` to :py:data:`sys.path` to import it as ``conftest``.
|
||||
``sys.path`` in order to import ``test_foo.py`` as the *module* ``test_foo``. The same is done
|
||||
with the ``conftest.py`` file by adding ``root/foo`` to ``sys.path`` to import it as ``conftest``.
|
||||
|
||||
For this reason this layout cannot have test modules with the same name, as they all will be
|
||||
imported in the global import namespace.
|
||||
@@ -190,7 +136,7 @@ Invoking ``pytest`` versus ``python -m pytest``
|
||||
-----------------------------------------------
|
||||
|
||||
Running pytest with ``pytest [...]`` instead of ``python -m pytest [...]`` yields nearly
|
||||
equivalent behaviour, except that the latter will add the current directory to :py:data:`sys.path`, which
|
||||
equivalent behaviour, except that the latter will add the current directory to ``sys.path``, which
|
||||
is standard ``python`` behavior.
|
||||
|
||||
See also :ref:`invoke-python`.
|
||||
|
||||
@@ -11,6 +11,8 @@ funcarg mechanism, see :ref:`historical funcargs and pytest.funcargs`.
|
||||
If you are new to pytest, then you can simply ignore this
|
||||
section and read the other sections.
|
||||
|
||||
.. currentmodule:: _pytest
|
||||
|
||||
Shortcomings of the previous ``pytest_funcarg__`` mechanism
|
||||
--------------------------------------------------------------
|
||||
|
||||
@@ -44,7 +46,7 @@ There are several limitations and difficulties with this approach:
|
||||
|
||||
2. parametrizing the "db" resource is not straight forward:
|
||||
you need to apply a "parametrize" decorator or implement a
|
||||
:hook:`pytest_generate_tests` hook
|
||||
:py:func:`~hookspec.pytest_generate_tests` hook
|
||||
calling :py:func:`~pytest.Metafunc.parametrize` which
|
||||
performs parametrization at the places where the resource
|
||||
is used. Moreover, you need to modify the factory to use an
|
||||
@@ -92,14 +94,15 @@ Direct parametrization of funcarg resource factories
|
||||
|
||||
Previously, funcarg factories could not directly cause parametrization.
|
||||
You needed to specify a ``@parametrize`` decorator on your test function
|
||||
or implement a :hook:`pytest_generate_tests` hook to perform
|
||||
or implement a ``pytest_generate_tests`` hook to perform
|
||||
parametrization, i.e. calling a test multiple times with different value
|
||||
sets. pytest-2.3 introduces a decorator for use on the factory itself:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture(params=["mysql", "pg"])
|
||||
def db(request): ... # use request.param
|
||||
def db(request):
|
||||
... # use request.param
|
||||
|
||||
Here the factory will be invoked twice (with the respective "mysql"
|
||||
and "pg" values set as ``request.param`` attributes) and all of
|
||||
@@ -140,7 +143,8 @@ argument:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.fixture()
|
||||
def db(request): ...
|
||||
def db(request):
|
||||
...
|
||||
|
||||
The name under which the funcarg resource can be requested is ``db``.
|
||||
|
||||
@@ -149,7 +153,8 @@ aka:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_funcarg__db(request): ...
|
||||
def pytest_funcarg__db(request):
|
||||
...
|
||||
|
||||
|
||||
But it is then not possible to define scoping and parametrization.
|
||||
|
||||
@@ -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 8.1.0
|
||||
pytest 7.3.0
|
||||
|
||||
.. _`simpletest`:
|
||||
|
||||
@@ -47,7 +47,7 @@ The test
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -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 :class:`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
|
||||
|
||||
@@ -112,7 +112,7 @@ More details can be found in the :pull:`original PR <3317>`.
|
||||
.. note::
|
||||
|
||||
in a future major release of pytest we will introduce class based markers,
|
||||
at which point markers will no longer be limited to instances of :py:class:`~pytest.Mark`.
|
||||
at which point markers will no longer be limited to instances of :py:class:`~_pytest.mark.Mark`.
|
||||
|
||||
|
||||
cache plugin integrated into the core
|
||||
@@ -227,7 +227,8 @@ to use strings:
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.version_info >= (3,3)")
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
During test function setup the skipif condition is evaluated by calling
|
||||
``eval('sys.version_info >= (3,0)', namespace)``. The namespace contains
|
||||
@@ -261,7 +262,8 @@ configuration value which you might have added:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif("not config.getvalue('db')")
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
The equivalent with "boolean conditions" is:
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ you will see the return value of the function call:
|
||||
|
||||
$ pytest test_assert1.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -98,27 +99,6 @@ and if you need to have access to the actual exception info you may use:
|
||||
the actual exception raised. The main attributes of interest are
|
||||
``.type``, ``.value`` and ``.traceback``.
|
||||
|
||||
Note that ``pytest.raises`` will match the exception type or any subclasses (like the standard ``except`` statement).
|
||||
If you want to check if a block of code is raising an exact exception type, you need to check that explicitly:
|
||||
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo_not_implemented():
|
||||
def foo():
|
||||
raise NotImplementedError
|
||||
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
foo()
|
||||
assert excinfo.type is RuntimeError
|
||||
|
||||
The :func:`pytest.raises` call will succeed, even though the function raises :class:`NotImplementedError`, because
|
||||
:class:`NotImplementedError` is a subclass of :class:`RuntimeError`; however the following `assert` statement will
|
||||
catch the problem.
|
||||
|
||||
Matching exception messages
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can pass a ``match`` keyword parameter to the context-manager to test
|
||||
that a regular expression matches on the string representation of an exception
|
||||
(similar to the ``TestCase.assertRaisesRegex`` method from ``unittest``):
|
||||
@@ -136,113 +116,36 @@ that a regular expression matches on the string representation of an exception
|
||||
with pytest.raises(ValueError, match=r".* 123 .*"):
|
||||
myfunc()
|
||||
|
||||
Notes:
|
||||
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.
|
||||
|
||||
* The ``match`` parameter is matched with the :func:`re.search`
|
||||
function, so in the above example ``match='123'`` would have worked as well.
|
||||
* The ``match`` parameter also matches against `PEP-678 <https://peps.python.org/pep-0678/>`__ ``__notes__``.
|
||||
|
||||
|
||||
.. _`assert-matching-exception-groups`:
|
||||
|
||||
Matching exception groups
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can also use the :func:`excinfo.group_contains() <pytest.ExceptionInfo.group_contains>`
|
||||
method to test for exceptions returned as part of an :class:`ExceptionGroup`:
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_exception_in_group():
|
||||
with pytest.raises(ExceptionGroup) 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(ExceptionGroup) 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)
|
||||
|
||||
Alternate form (legacy)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
There is an alternate form where you pass
|
||||
a function that will be executed, along ``*args`` and ``**kwargs``, and :func:`pytest.raises`
|
||||
will execute the function with the arguments and assert that the given exception is raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def func(x):
|
||||
if x <= 0:
|
||||
raise ValueError("x needs to be larger than zero")
|
||||
|
||||
|
||||
pytest.raises(ValueError, func, x=-1)
|
||||
pytest.raises(ExpectedException, func, *args, **kwargs)
|
||||
|
||||
The reporter will provide you with helpful output in case of failures such as *no
|
||||
exception* or *wrong exception*.
|
||||
|
||||
This form was the original :func:`pytest.raises` API, developed before the ``with`` statement was
|
||||
added to the Python language. Nowadays, this form is rarely used, with the context-manager form (using ``with``)
|
||||
being considered more readable.
|
||||
Nonetheless, this form is fully supported and not deprecated in any way.
|
||||
|
||||
xfail mark and pytest.raises
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
It is also possible to specify a ``raises`` argument to
|
||||
:ref:`pytest.mark.xfail <pytest.mark.xfail ref>`, which checks that the test is failing in a more
|
||||
Note that it is also possible to specify a "raises" argument to
|
||||
``pytest.mark.xfail``, which checks that the test is failing in a more
|
||||
specific way than just having any exception raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def f():
|
||||
raise IndexError()
|
||||
|
||||
|
||||
@pytest.mark.xfail(raises=IndexError)
|
||||
def test_f():
|
||||
f()
|
||||
|
||||
|
||||
This will only "xfail" if the test fails by raising ``IndexError`` or subclasses.
|
||||
|
||||
* Using :ref:`pytest.mark.xfail <pytest.mark.xfail ref>` with the ``raises`` parameter is probably better for something
|
||||
like documenting unfixed bugs (where the test describes what "should" happen) or bugs in dependencies.
|
||||
|
||||
* Using :func:`pytest.raises` is likely to be better for cases where you are
|
||||
testing exceptions your own code is deliberately raising, which is the majority of cases.
|
||||
Using :func:`pytest.raises` is likely to be better for cases where you are
|
||||
testing exceptions your own code is deliberately raising, whereas using
|
||||
``@pytest.mark.xfail`` with a check function is probably better for something
|
||||
like documenting unfixed bugs (where the test describes what "should" happen)
|
||||
or bugs in dependencies.
|
||||
|
||||
|
||||
.. _`assertwarns`:
|
||||
@@ -280,7 +183,7 @@ if you run this module:
|
||||
|
||||
$ pytest test_assert2.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -294,7 +197,6 @@ if you run this module:
|
||||
set2 = set("8035")
|
||||
> assert set1 == set2
|
||||
E AssertionError: assert {'0', '1', '3', '8'} == {'0', '3', '5', '8'}
|
||||
E
|
||||
E Extra items in the left set:
|
||||
E '1'
|
||||
E Extra items in the right set:
|
||||
|
||||
@@ -86,7 +86,7 @@ If you then run it with ``--lf``:
|
||||
|
||||
$ pytest --lf
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
run-last-failure: rerun previous 2 failures
|
||||
@@ -132,7 +132,7 @@ of ``FF`` and dots):
|
||||
|
||||
$ pytest --ff
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 50 items
|
||||
run-last-failure: rerun previous 2 failures first
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -281,7 +274,7 @@ You can always peek at the content of the cache using the
|
||||
|
||||
$ pytest --cache-show
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
cachedir: /home/sweet/project/.pytest_cache
|
||||
--------------------------- cache values for '*' ---------------------------
|
||||
@@ -303,7 +296,7 @@ filtering:
|
||||
|
||||
$ pytest --cache-show example/*
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
cachedir: /home/sweet/project/.pytest_cache
|
||||
----------------------- cache values for 'example/*' -----------------------
|
||||
|
||||
@@ -83,7 +83,7 @@ of the failing function and hide the other one:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ Running pytest now produces this output:
|
||||
|
||||
$ pytest test_show_warnings.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -382,6 +382,8 @@ warnings: a WarningsRecorder instance. To view the recorded warnings, you can
|
||||
iterate over this instance, call ``len`` on it to get the number of recorded
|
||||
warnings, or index into it to get a particular recorded warning.
|
||||
|
||||
.. currentmodule:: _pytest.warnings
|
||||
|
||||
Full API: :class:`~_pytest.recwarn.WarningsRecorder`.
|
||||
|
||||
.. _`warns use cases`:
|
||||
|
||||
@@ -30,7 +30,7 @@ then you can just invoke ``pytest`` directly:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -58,7 +58,7 @@ and functions, including from test modules:
|
||||
|
||||
$ pytest --doctest-modules
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ How to use pytest with an existing test suite
|
||||
==============================================
|
||||
|
||||
Pytest can be used with most existing test suites, but its
|
||||
behavior differs from other test runners such as Python's
|
||||
default unittest framework.
|
||||
behavior differs from other test runners such as :ref:`nose <noseintegration>` or
|
||||
Python's default unittest framework.
|
||||
|
||||
Before using this section you will want to :ref:`install pytest <getstarted>`.
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -433,7 +433,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
|
||||
|
||||
$ pytest test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
@@ -494,7 +494,7 @@ Fixtures are created when first requested by a test, and are destroyed based on
|
||||
* ``function``: the default scope, the fixture is destroyed at the end of the test.
|
||||
* ``class``: the fixture is destroyed during teardown of the last test in the class.
|
||||
* ``module``: the fixture is destroyed during teardown of the last test in the module.
|
||||
* ``package``: the fixture is destroyed during teardown of the last test in the package where the fixture is defined, including sub-packages and sub-directories within it.
|
||||
* ``package``: the fixture is destroyed during teardown of the last test in the package.
|
||||
* ``session``: the fixture is destroyed at the end of the test session.
|
||||
|
||||
.. note::
|
||||
@@ -771,7 +771,7 @@ For yield fixtures, the first teardown code to run is from the right-most fixtur
|
||||
|
||||
$ pytest -s test_finalizers.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -805,7 +805,7 @@ For finalizers, the first fixture to run is last call to `request.addfinalizer`.
|
||||
|
||||
$ pytest -s test_finalizers.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -1271,7 +1271,7 @@ configured in multiple ways.
|
||||
Extending the previous example, we can flag the fixture to create two
|
||||
``smtp_connection`` fixture instances which will cause all tests using the fixture
|
||||
to run twice. The fixture function gets access to each parameter
|
||||
through the special :py:class:`request <pytest.FixtureRequest>` object:
|
||||
through the special :py:class:`request <FixtureRequest>` object:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -1414,28 +1414,27 @@ Running the above tests results in the following test IDs being used:
|
||||
|
||||
$ pytest --collect-only
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 12 items
|
||||
|
||||
<Dir fixtures.rst-214>
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
<Function test_showhelo[mail.python.org]>
|
||||
<Module test_emaillib.py>
|
||||
<Function test_email_received>
|
||||
<Module test_finalizers.py>
|
||||
<Function test_bar>
|
||||
<Module test_ids.py>
|
||||
<Function test_a[spam]>
|
||||
<Function test_a[ham]>
|
||||
<Function test_b[eggs]>
|
||||
<Function test_b[1]>
|
||||
<Module test_module.py>
|
||||
<Function test_ehlo[smtp.gmail.com]>
|
||||
<Function test_noop[smtp.gmail.com]>
|
||||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
<Function test_showhelo[mail.python.org]>
|
||||
<Module test_emaillib.py>
|
||||
<Function test_email_received>
|
||||
<Module test_finalizers.py>
|
||||
<Function test_bar>
|
||||
<Module test_ids.py>
|
||||
<Function test_a[spam]>
|
||||
<Function test_a[ham]>
|
||||
<Function test_b[eggs]>
|
||||
<Function test_b[1]>
|
||||
<Module test_module.py>
|
||||
<Function test_ehlo[smtp.gmail.com]>
|
||||
<Function test_noop[smtp.gmail.com]>
|
||||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
|
||||
======================= 12 tests collected in 0.12s ========================
|
||||
|
||||
@@ -1469,7 +1468,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
|
||||
|
||||
$ pytest test_fixture_marks.py -v
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 3 items
|
||||
@@ -1519,7 +1518,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
||||
|
||||
$ pytest -v test_appsetup.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 2 items
|
||||
@@ -1599,7 +1598,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
|
||||
|
||||
$ pytest -v -s test_module.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: .pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
collecting ... collected 8 items
|
||||
@@ -1699,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):
|
||||
@@ -1721,7 +1720,8 @@ You can specify multiple fixtures like this:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.usefixtures("cleandir", "anotherfixture")
|
||||
def test(): ...
|
||||
def test():
|
||||
...
|
||||
|
||||
and you may specify fixture usage at the test module level using :globalvar:`pytestmark`:
|
||||
|
||||
@@ -1749,9 +1749,11 @@ into an ini-file:
|
||||
|
||||
@pytest.mark.usefixtures("my_other_fixture")
|
||||
@pytest.fixture
|
||||
def my_fixture_that_sadly_wont_use_my_other_fixture(): ...
|
||||
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`:
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ pytest and other test systems
|
||||
|
||||
existingtestsuite
|
||||
unittest
|
||||
nose
|
||||
xunit_setup
|
||||
|
||||
pytest development environment
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -206,9 +199,8 @@ option names are:
|
||||
* ``log_cli_date_format``
|
||||
|
||||
If you need to record the whole test suite logging calls to a file, you can pass
|
||||
``--log-file=/path/to/log/file``. This log file is opened in write mode by default which
|
||||
``--log-file=/path/to/log/file``. This log file is opened in write mode which
|
||||
means that it will be overwritten at each run tests session.
|
||||
If you'd like the file opened in append mode instead, then you can pass ``--log-file-mode=a``.
|
||||
Note that relative paths for the log-file location, whether passed on the CLI or declared in a
|
||||
config file, are always resolved relative to the current working directory.
|
||||
|
||||
@@ -224,13 +216,12 @@ All of the log file options can also be set in the configuration INI file. The
|
||||
option names are:
|
||||
|
||||
* ``log_file``
|
||||
* ``log_file_mode``
|
||||
* ``log_file_level``
|
||||
* ``log_file_format``
|
||||
* ``log_file_date_format``
|
||||
|
||||
You can call ``set_log_path()`` to customize the log_file path dynamically. This functionality
|
||||
is considered **experimental**. Note that ``set_log_path()`` respects the ``log_file_mode`` option.
|
||||
is considered **experimental**.
|
||||
|
||||
.. _log_colors:
|
||||
|
||||
@@ -243,7 +234,7 @@ through ``add_color_level()``. Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.hookimpl(trylast=True)
|
||||
@pytest.hookimpl
|
||||
def pytest_configure(config):
|
||||
logging_plugin = config.pluginmanager.get_plugin("logging-plugin")
|
||||
|
||||
|
||||
82
doc/en/how-to/nose.rst
Normal file
82
doc/en/how-to/nose.rst
Normal file
@@ -0,0 +1,82 @@
|
||||
.. _`noseintegration`:
|
||||
|
||||
How to run tests written for nose
|
||||
=======================================
|
||||
|
||||
``pytest`` has basic support for running tests written for nose_.
|
||||
|
||||
.. warning::
|
||||
This functionality has been deprecated and is likely to be removed in ``pytest 8.x``.
|
||||
|
||||
.. _nosestyle:
|
||||
|
||||
Usage
|
||||
-------------
|
||||
|
||||
After :ref:`installation` type:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
python setup.py develop # make sure tests can import our package
|
||||
pytest # instead of 'nosetests'
|
||||
|
||||
and you should be able to run your nose style tests and
|
||||
make use of pytest's capabilities.
|
||||
|
||||
Supported nose Idioms
|
||||
----------------------
|
||||
|
||||
* ``setup()`` and ``teardown()`` at module/class/method level: any function or method called ``setup`` will be called during the setup phase for each test, same for ``teardown``.
|
||||
* ``SkipTest`` exceptions and markers
|
||||
* setup/teardown decorators
|
||||
* ``__test__`` attribute on modules/classes/functions
|
||||
* general usage of nose utilities
|
||||
|
||||
Unsupported idioms / known issues
|
||||
----------------------------------
|
||||
|
||||
- unittest-style ``setUp, tearDown, setUpClass, tearDownClass``
|
||||
are recognized only on ``unittest.TestCase`` classes but not
|
||||
on plain classes. ``nose`` supports these methods also on plain
|
||||
classes but pytest deliberately does not. As nose and pytest already
|
||||
both support ``setup_class, teardown_class, setup_method, teardown_method``
|
||||
it doesn't seem useful to duplicate the unittest-API like nose does.
|
||||
If you however rather think pytest should support the unittest-spelling on
|
||||
plain classes please post to :issue:`377`.
|
||||
|
||||
- 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
|
||||
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
|
||||
(as determined by pytest) pytest will run tests "nose style" against
|
||||
the code below that directory by adding it to your ``sys.path`` instead of
|
||||
running against your installed code.
|
||||
|
||||
You may find yourself wanting to do this if you ran ``python setup.py install``
|
||||
to set up your project, as opposed to ``python setup.py develop`` or any of
|
||||
the package manager equivalents. Installing with develop in a
|
||||
virtual environment like tox is recommended over this pattern.
|
||||
|
||||
- nose-style doctests are not collected and executed correctly,
|
||||
also doctest fixtures don't work.
|
||||
|
||||
- no nose-configuration is recognized.
|
||||
|
||||
- ``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.
|
||||
|
||||
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
|
||||
raw assert statements, while preserving format of original arguments
|
||||
as much as possible.
|
||||
|
||||
.. _nose: https://nose.readthedocs.io/en/latest/
|
||||
@@ -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.
|
||||
|
||||
@@ -100,7 +84,6 @@ Executing pytest normally gives us this output (we are skipping the header to fo
|
||||
fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
|
||||
> assert fruits1 == fruits2
|
||||
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
|
||||
E
|
||||
E At index 2 diff: 'grapes' != 'orange'
|
||||
E Use -v to get more diff
|
||||
|
||||
@@ -112,7 +95,6 @@ Executing pytest normally gives us this output (we are skipping the header to fo
|
||||
number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
|
||||
> assert number_to_text1 == number_to_text2
|
||||
E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...}
|
||||
E
|
||||
E Omitting 1 identical items, use -vv to show
|
||||
E Left contains 4 more items:
|
||||
E {'1': 1, '2': 2, '3': 3, '4': 4}
|
||||
@@ -164,15 +146,12 @@ Now we can increase pytest's verbosity:
|
||||
fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
|
||||
> assert fruits1 == fruits2
|
||||
E AssertionError: assert ['banana', 'a...elon', 'kiwi'] == ['banana', 'a...elon', 'kiwi']
|
||||
E
|
||||
E At index 2 diff: 'grapes' != 'orange'
|
||||
E
|
||||
E Full diff:
|
||||
E [
|
||||
E 'banana',
|
||||
E 'apple',...
|
||||
E
|
||||
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||
E - ['banana', 'apple', 'orange', 'melon', 'kiwi']
|
||||
E ? ^ ^^
|
||||
E + ['banana', 'apple', 'grapes', 'melon', 'kiwi']
|
||||
E ? ^ ^ +
|
||||
|
||||
test_verbosity_example.py:8: AssertionError
|
||||
____________________________ test_numbers_fail _____________________________
|
||||
@@ -182,15 +161,15 @@ Now we can increase pytest's verbosity:
|
||||
number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
|
||||
> assert number_to_text1 == number_to_text2
|
||||
E AssertionError: assert {'0': 0, '1':..., '3': 3, ...} == {'0': 0, '10'...'30': 30, ...}
|
||||
E
|
||||
E Omitting 1 identical items, use -vv to show
|
||||
E Left contains 4 more items:
|
||||
E {'1': 1, '2': 2, '3': 3, '4': 4}
|
||||
E Right contains 4 more items:
|
||||
E {'10': 10, '20': 20, '30': 30, '40': 40}
|
||||
E ...
|
||||
E
|
||||
E ...Full output truncated (16 lines hidden), use '-vv' to show
|
||||
E Full diff:
|
||||
E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
|
||||
E ? - - - - - - - -
|
||||
E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}
|
||||
|
||||
test_verbosity_example.py:14: AssertionError
|
||||
___________________________ test_long_text_fail ____________________________
|
||||
@@ -236,20 +215,12 @@ Now if we increase verbosity even more:
|
||||
fruits2 = ["banana", "apple", "orange", "melon", "kiwi"]
|
||||
> assert fruits1 == fruits2
|
||||
E AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi']
|
||||
E
|
||||
E At index 2 diff: 'grapes' != 'orange'
|
||||
E
|
||||
E Full diff:
|
||||
E [
|
||||
E 'banana',
|
||||
E 'apple',
|
||||
E - 'orange',
|
||||
E ? ^ ^^
|
||||
E + 'grapes',
|
||||
E ? ^ ^ +
|
||||
E 'melon',
|
||||
E 'kiwi',
|
||||
E ]
|
||||
E - ['banana', 'apple', 'orange', 'melon', 'kiwi']
|
||||
E ? ^ ^^
|
||||
E + ['banana', 'apple', 'grapes', 'melon', 'kiwi']
|
||||
E ? ^ ^ +
|
||||
|
||||
test_verbosity_example.py:8: AssertionError
|
||||
____________________________ test_numbers_fail _____________________________
|
||||
@@ -259,30 +230,16 @@ Now if we increase verbosity even more:
|
||||
number_to_text2 = {str(x * 10): x * 10 for x in range(5)}
|
||||
> assert number_to_text1 == number_to_text2
|
||||
E AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
|
||||
E
|
||||
E Common items:
|
||||
E {'0': 0}
|
||||
E Left contains 4 more items:
|
||||
E {'1': 1, '2': 2, '3': 3, '4': 4}
|
||||
E Right contains 4 more items:
|
||||
E {'10': 10, '20': 20, '30': 30, '40': 40}
|
||||
E
|
||||
E Full diff:
|
||||
E {
|
||||
E '0': 0,
|
||||
E - '10': 10,
|
||||
E ? - -
|
||||
E + '1': 1,
|
||||
E - '20': 20,
|
||||
E ? - -
|
||||
E + '2': 2,
|
||||
E - '30': 30,
|
||||
E ? - -
|
||||
E + '3': 3,
|
||||
E - '40': 40,
|
||||
E ? - -
|
||||
E + '4': 4,
|
||||
E }
|
||||
E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}
|
||||
E ? - - - - - - - -
|
||||
E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4}
|
||||
|
||||
test_verbosity_example.py:14: AssertionError
|
||||
___________________________ test_long_text_fail ____________________________
|
||||
@@ -313,22 +270,6 @@ situations, for example you are shown even fixtures that start with ``_`` if you
|
||||
Using higher verbosity levels (``-vvv``, ``-vvvv``, ...) is supported, but has no effect in pytest itself at the moment,
|
||||
however some plugins might make use of higher verbosity.
|
||||
|
||||
.. _`pytest.fine_grained_verbosity`:
|
||||
|
||||
Fine-grained verbosity
|
||||
~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In addition to specifying the application wide verbosity level, it is possible to control specific aspects independently.
|
||||
This is done by setting a verbosity level in the configuration file for the specific aspect of the output.
|
||||
|
||||
:confval:`verbosity_assertions`: Controls how verbose the assertion output should be when pytest is executed. Running
|
||||
``pytest --no-header`` with a value of ``2`` would have the same output as the previous example, but each test inside
|
||||
the file is shown by a single character in the output.
|
||||
|
||||
:confval:`verbosity_test_cases`: Controls how verbose the test execution output should be when pytest is executed.
|
||||
Running ``pytest --no-header`` with a value of ``2`` would have the same output as the first verbosity example, but each
|
||||
test inside the file gets its own line in the output.
|
||||
|
||||
.. _`pytest.detailed_failed_tests_usage`:
|
||||
|
||||
Producing a detailed summary report
|
||||
@@ -383,7 +324,7 @@ Example:
|
||||
|
||||
$ pytest -ra
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 6 items
|
||||
|
||||
@@ -406,19 +347,10 @@ Example:
|
||||
E assert 0
|
||||
|
||||
test_example.py:14: AssertionError
|
||||
================================ XFAILURES =================================
|
||||
________________________________ test_xfail ________________________________
|
||||
|
||||
def test_xfail():
|
||||
> pytest.xfail("xfailing this test")
|
||||
E _pytest.outcomes.XFailed: xfailing this test
|
||||
|
||||
test_example.py:26: XFailed
|
||||
================================= XPASSES ==================================
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] test_example.py:22: skipping this test
|
||||
XFAIL test_example.py::test_xfail - reason: xfailing this test
|
||||
XPASS test_example.py::test_xpass - always xfail
|
||||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error - assert 0
|
||||
FAILED test_example.py::test_fail - assert 0
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
|
||||
@@ -448,7 +380,7 @@ More than one character can be used, so for example to only see failed and skipp
|
||||
|
||||
$ pytest -rfs
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 6 items
|
||||
|
||||
@@ -483,7 +415,7 @@ captured output:
|
||||
|
||||
$ pytest -rpP
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 6 items
|
||||
|
||||
@@ -546,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``.
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ them in turn:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 3 items
|
||||
|
||||
@@ -167,7 +167,7 @@ Let's run this:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 3 items
|
||||
|
||||
|
||||
@@ -47,7 +47,8 @@ which may be passed an optional ``reason``:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skip(reason="no way of currently testing this")
|
||||
def test_the_unknown(): ...
|
||||
def test_the_unknown():
|
||||
...
|
||||
|
||||
|
||||
Alternatively, it is also possible to skip imperatively during test execution or setup
|
||||
@@ -92,7 +93,8 @@ when run on an interpreter earlier than Python3.10:
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 10), reason="requires python3.10 or higher")
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
If the condition evaluates to ``True`` during collection, the test function will be skipped,
|
||||
with the specified reason appearing in the summary when using ``-rs``.
|
||||
@@ -110,7 +112,8 @@ You can share ``skipif`` markers between modules. Consider this test module:
|
||||
|
||||
|
||||
@minversion
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
You can import the marker and reuse it in another test module:
|
||||
|
||||
@@ -121,7 +124,8 @@ You can import the marker and reuse it in another test module:
|
||||
|
||||
|
||||
@minversion
|
||||
def test_anotherfunction(): ...
|
||||
def test_anotherfunction():
|
||||
...
|
||||
|
||||
For larger test suites it's usually a good idea to have one file
|
||||
where you define the markers which you then consistently apply
|
||||
@@ -228,7 +232,8 @@ expect a test to fail:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
This test will run but no traceback will be reported when it fails. Instead, terminal
|
||||
reporting will list it in the "expected to fail" (``XFAIL``) or "unexpectedly
|
||||
@@ -270,7 +275,8 @@ that condition as the first parameter:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(sys.platform == "win32", reason="bug in a 3rd party library")
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
Note that you have to pass a reason as well (see the parameter description at
|
||||
:ref:`pytest.mark.xfail ref`).
|
||||
@@ -283,7 +289,8 @@ You can specify the motive of an expected failure with the ``reason`` parameter:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(reason="known parser issue")
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
|
||||
``raises`` parameter
|
||||
@@ -295,7 +302,8 @@ a single exception, or a tuple of exceptions, in the ``raises`` argument.
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(raises=RuntimeError)
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
Then the test will be reported as a regular failure if it fails with an
|
||||
exception not mentioned in ``raises``.
|
||||
@@ -309,7 +317,8 @@ even executed, use the ``run`` parameter as ``False``:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(run=False)
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
This is specially useful for xfailing tests that are crashing the interpreter and should be
|
||||
investigated later.
|
||||
@@ -325,7 +334,8 @@ You can change this by setting the ``strict`` keyword-only parameter to ``True``
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(strict=True)
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
|
||||
This will make ``XPASS`` ("unexpectedly passing") results from this test to fail the test suite.
|
||||
|
||||
@@ -8,8 +8,9 @@ How to use temporary directories and files in tests
|
||||
The ``tmp_path`` fixture
|
||||
------------------------
|
||||
|
||||
You can use the ``tmp_path`` fixture which will provide a temporary directory
|
||||
unique to each test function.
|
||||
You can use the ``tmp_path`` fixture which will
|
||||
provide a temporary directory unique to the test invocation,
|
||||
created in the `base temporary directory`_.
|
||||
|
||||
``tmp_path`` is a :class:`pathlib.Path` object. Here is an example test usage:
|
||||
|
||||
@@ -23,8 +24,8 @@ unique to each test function.
|
||||
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
|
||||
|
||||
@@ -35,7 +36,7 @@ Running this would result in a passed test except for the last
|
||||
|
||||
$ pytest test_tmp_path.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -50,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
|
||||
@@ -61,11 +62,6 @@ Running this would result in a passed test except for the last
|
||||
FAILED test_tmp_path.py::test_create_file - assert 0
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
By default, ``pytest`` retains the temporary directory for the last 3 ``pytest``
|
||||
invocations. Concurrent invocations of the same test function are supported by
|
||||
configuring the base temporary directory to be unique for each concurrent
|
||||
run. See `temporary directory location and retention`_ for details.
|
||||
|
||||
.. _`tmp_path_factory example`:
|
||||
|
||||
The ``tmp_path_factory`` fixture
|
||||
@@ -104,7 +100,7 @@ See :ref:`tmp_path_factory API <tmp_path_factory factory api>` for details.
|
||||
.. _tmpdir:
|
||||
|
||||
The ``tmpdir`` and ``tmpdir_factory`` fixtures
|
||||
----------------------------------------------
|
||||
---------------------------------------------------
|
||||
|
||||
The ``tmpdir`` and ``tmpdir_factory`` fixtures are similar to ``tmp_path``
|
||||
and ``tmp_path_factory``, but use/return legacy `py.path.local`_ objects
|
||||
@@ -128,10 +124,10 @@ See :fixture:`tmpdir <tmpdir>` :fixture:`tmpdir_factory <tmpdir_factory>`
|
||||
API for details.
|
||||
|
||||
|
||||
.. _`temporary directory location and retention`:
|
||||
.. _`base temporary directory`:
|
||||
|
||||
Temporary directory location and retention
|
||||
------------------------------------------
|
||||
The default base temporary directory
|
||||
-----------------------------------------------
|
||||
|
||||
Temporary directories are by default created as sub-directories of
|
||||
the system temporary directory. The base name will be ``pytest-NUM`` where
|
||||
@@ -156,7 +152,7 @@ You can override the default temporary directory setting like this:
|
||||
for that purpose only.
|
||||
|
||||
When distributing tests on the local machine using ``pytest-xdist``, care is taken to
|
||||
automatically configure a `basetemp` directory for the sub processes such that all temporary
|
||||
data lands below a single per-test run temporary directory.
|
||||
automatically configure a basetemp directory for the sub processes such that all temporary
|
||||
data lands below a single per-test run basetemp directory.
|
||||
|
||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||
|
||||
@@ -140,7 +140,7 @@ the ``self.db`` values in the traceback:
|
||||
|
||||
$ pytest test_unittest_db.py
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 2 items
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -35,43 +35,31 @@ Pytest supports several ways to run and select tests from the command-line.
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pytest -k 'MyClass and not method'
|
||||
pytest -k "MyClass and not method"
|
||||
|
||||
This will run tests which contain names that match the given *string expression* (case-insensitive),
|
||||
which can include Python operators that use filenames, class names and function names as variables.
|
||||
The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``.
|
||||
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 +172,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,17 +56,23 @@ 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
|
||||
|
||||
|
||||
|
||||
pytest plugins can implement hook wrappers which wrap the execution
|
||||
of other hook implementations. A hook wrapper is a generator function
|
||||
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:
|
||||
|
||||
@@ -75,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:
|
||||
|
||||
@@ -133,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:
|
||||
|
||||
@@ -155,13 +149,13 @@ 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.
|
||||
|
||||
.. _`declaringhooks`:
|
||||
|
||||
Declaring new hooks
|
||||
------------------------
|
||||
@@ -171,11 +165,13 @@ Declaring new hooks
|
||||
This is a quick overview on how to add new hooks and how they work in general, but a more complete
|
||||
overview can be found in `the pluggy documentation <https://pluggy.readthedocs.io/en/latest/>`__.
|
||||
|
||||
.. currentmodule:: _pytest.hookspec
|
||||
|
||||
Plugins and ``conftest.py`` files may declare new hooks that can then be
|
||||
implemented by other plugins in order to alter behaviour or interact with
|
||||
the new plugin:
|
||||
|
||||
.. autofunction:: _pytest.hookspec.pytest_addhooks
|
||||
.. autofunction:: pytest_addhooks
|
||||
:noindex:
|
||||
|
||||
Hooks are usually declared as do-nothing functions that contain only
|
||||
|
||||
@@ -46,18 +46,24 @@ Plugin discovery order at tool startup
|
||||
|
||||
5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable.
|
||||
|
||||
6. by loading all "initial ":file:`conftest.py` files:
|
||||
6. by loading all :file:`conftest.py` files as inferred by the command line
|
||||
invocation:
|
||||
|
||||
- determine the test paths: specified on the command line, otherwise in
|
||||
:confval:`testpaths` if defined and running from the rootdir, otherwise the
|
||||
current dir
|
||||
- for each test path, load ``conftest.py`` and ``test*/conftest.py`` relative
|
||||
to the directory part of the test path, if exist. Before a ``conftest.py``
|
||||
file is loaded, load ``conftest.py`` files in all of its parent directories.
|
||||
After a ``conftest.py`` file is loaded, recursively load all plugins specified
|
||||
in its :globalvar:`pytest_plugins` variable if present.
|
||||
- if no test paths are specified, use the current dir as a test path
|
||||
- if exists, load ``conftest.py`` and ``test*/conftest.py`` relative
|
||||
to the directory part of the first test path. After the ``conftest.py``
|
||||
file is loaded, load all plugins specified in its
|
||||
:globalvar:`pytest_plugins` variable if present.
|
||||
|
||||
Note that pytest does not find ``conftest.py`` files in deeper nested
|
||||
sub directories at tool startup. It is usually a good idea to keep
|
||||
your ``conftest.py`` file in the top level test or project root directory.
|
||||
|
||||
7. by recursively loading all plugins specified by the
|
||||
:globalvar:`pytest_plugins` variable in ``conftest.py`` files.
|
||||
|
||||
|
||||
.. _`pytest/plugin`: http://bitbucket.org/pytest-dev/pytest/src/tip/pytest/plugin/
|
||||
.. _`conftest.py plugins`:
|
||||
.. _`localplugin`:
|
||||
.. _`local conftest plugins`:
|
||||
@@ -102,9 +108,9 @@ Here is how you might run it::
|
||||
See also: :ref:`pythonpath`.
|
||||
|
||||
.. note::
|
||||
Some hooks cannot be implemented in conftest.py files which are not
|
||||
:ref:`initial <pluginorder>` due to how pytest discovers plugins during
|
||||
startup. See the documentation of each hook for details.
|
||||
Some hooks should be implemented only in plugins or conftest.py files situated at the
|
||||
tests root directory due to how pytest discovers plugins during startup,
|
||||
see the documentation of each hook for details.
|
||||
|
||||
Writing your own plugin
|
||||
-----------------------
|
||||
@@ -442,7 +448,7 @@ in our ``pytest.ini`` to tell pytest where to look for example files.
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
configfile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
:orphan:
|
||||
|
||||
.. sidebar:: Next Open Trainings and Events
|
||||
..
|
||||
.. 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/>`_ (3 day in-depth training):
|
||||
* **June 11th to 13th 2024**, Remote
|
||||
* **March 4th to 6th 2025**, Leipzig, Germany / Remote
|
||||
- `pytest development sprint <https://github.com/pytest-dev/pytest/discussions/11655>`_, June 2024 (`date poll <https://nuudel.digitalcourage.de/2tEsEpRcwMNcAXVO>`_)
|
||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 7th to 9th 2023 (3 day in-depth training), Remote
|
||||
|
||||
Also see :doc:`previous talks and blogposts <talks>`.
|
||||
Also see :doc:`previous talks and blogposts <talks>`.
|
||||
|
||||
.. _features:
|
||||
|
||||
@@ -20,7 +18,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`
|
||||
|
||||
@@ -45,7 +43,7 @@ To execute it:
|
||||
|
||||
$ pytest
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y
|
||||
platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y
|
||||
rootdir: /home/sweet/project
|
||||
collected 1 item
|
||||
|
||||
@@ -77,11 +75,11 @@ Features
|
||||
|
||||
- :ref:`Modular fixtures <fixture>` for managing small or parametrized long-lived test resources
|
||||
|
||||
- Can run :ref:`unittest <unittest>` (including trial) test suites out of the box
|
||||
- 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 1300+ :ref:`external plugins <plugin-list>` and thriving community
|
||||
- Rich plugin architecture, with over 800+ :ref:`external plugins <plugin-list>` and thriving community
|
||||
|
||||
|
||||
Documentation
|
||||
|
||||
@@ -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
|
||||
@@ -177,20 +177,13 @@ Files will only be matched for configuration if:
|
||||
* ``tox.ini``: contains a ``[pytest]`` section.
|
||||
* ``setup.cfg``: contains a ``[tool:pytest]`` section.
|
||||
|
||||
Finally, a ``pyproject.toml`` file will be considered the ``configfile`` if no other match was found, in this case
|
||||
even if it does not contain a ``[tool.pytest.ini_options]`` table (this was added in ``8.1``).
|
||||
|
||||
The files are considered in the order above. Options from multiple ``configfiles`` candidates
|
||||
are never merged - the first match wins.
|
||||
|
||||
The configuration file also determines the value of the ``rootpath``.
|
||||
|
||||
The :class:`Config <pytest.Config>` object (accessible via hooks or through the :fixture:`pytestconfig` fixture)
|
||||
will subsequently carry these attributes:
|
||||
|
||||
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist. It is used as
|
||||
a reference directory for constructing test addresses ("nodeids") and can be used also by plugins for storing
|
||||
per-testrun information.
|
||||
- :attr:`config.rootpath <pytest.Config.rootpath>`: the determined root directory, guaranteed to exist.
|
||||
|
||||
- :attr:`config.inipath <pytest.Config.inipath>`: the determined ``configfile``, may be ``None``
|
||||
(it is named ``inipath`` for historical reasons).
|
||||
@@ -200,7 +193,9 @@ will subsequently carry these attributes:
|
||||
versions of the older ``config.rootdir`` and ``config.inifile``, which have type
|
||||
``py.path.local``, and still exist for backward compatibility.
|
||||
|
||||
|
||||
The ``rootdir`` is used as a reference directory for constructing test
|
||||
addresses ("nodeids") and can be used also by plugins for storing
|
||||
per-testrun information.
|
||||
|
||||
Example:
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ Fixtures reference
|
||||
.. seealso:: :ref:`about-fixtures`
|
||||
.. seealso:: :ref:`how-to-fixtures`
|
||||
|
||||
|
||||
.. currentmodule:: _pytest.python
|
||||
|
||||
.. _`Dependency injection`: https://en.wikipedia.org/wiki/Dependency_injection
|
||||
|
||||
|
||||
@@ -73,13 +76,15 @@ Built-in fixtures
|
||||
:class:`pathlib.Path` objects.
|
||||
|
||||
:fixture:`tmpdir`
|
||||
Provide a `py.path.local <https://py.readthedocs.io/en/latest/path.html>`_ object to a temporary
|
||||
Provide a :class:`py.path.local` object to a temporary
|
||||
directory which is unique to each test function;
|
||||
replaced by :fixture:`tmp_path`.
|
||||
|
||||
.. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html
|
||||
|
||||
:fixture:`tmpdir_factory`
|
||||
Make session-scoped temporary directories and return
|
||||
``py.path.local`` objects;
|
||||
:class:`py.path.local` objects;
|
||||
replaced by :fixture:`tmp_path_factory`.
|
||||
|
||||
|
||||
@@ -93,7 +98,7 @@ Fixture availability is determined from the perspective of the test. A fixture
|
||||
is only available for tests to request if they are in the scope that fixture is
|
||||
defined in. If a fixture is defined inside a class, it can only be requested by
|
||||
tests inside that class. But if a fixture is defined inside the global scope of
|
||||
the module, then every test in that module, even if it's defined inside a class,
|
||||
the module, than every test in that module, even if it's defined inside a class,
|
||||
can request it.
|
||||
|
||||
Similarly, a test can also only be affected by an autouse fixture if that test
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,3 @@
|
||||
:tocdepth: 3
|
||||
|
||||
.. _`api-reference`:
|
||||
|
||||
API Reference
|
||||
@@ -79,13 +77,11 @@ pytest.xfail
|
||||
pytest.exit
|
||||
~~~~~~~~~~~
|
||||
|
||||
.. autofunction:: pytest.exit(reason, [returncode=None, msg=None])
|
||||
.. autofunction:: pytest.exit(reason, [returncode=False, msg=None])
|
||||
|
||||
pytest.main
|
||||
~~~~~~~~~~~
|
||||
|
||||
**Tutorial**: :ref:`pytest.main-usage`
|
||||
|
||||
.. autofunction:: pytest.main
|
||||
|
||||
pytest.param
|
||||
@@ -164,7 +160,8 @@ Add warning filters to marked test items.
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:.*usage will be deprecated.*:DeprecationWarning")
|
||||
def test_foo(): ...
|
||||
def test_foo():
|
||||
...
|
||||
|
||||
|
||||
.. _`pytest.mark.parametrize ref`:
|
||||
@@ -238,23 +235,22 @@ pytest.mark.xfail
|
||||
|
||||
Marks a test function as *expected to fail*.
|
||||
|
||||
.. py:function:: pytest.mark.xfail(condition=False, *, reason=None, raises=None, run=True, strict=xfail_strict)
|
||||
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
|
||||
|
||||
:keyword Union[bool, str] condition:
|
||||
:type condition: bool or str
|
||||
:param condition:
|
||||
Condition for marking the test function as xfail (``True/False`` or a
|
||||
:ref:`condition string <string conditions>`). If a ``bool``, you also have
|
||||
:ref:`condition string <string conditions>`). If a bool, you also have
|
||||
to specify ``reason`` (see :ref:`condition string <string conditions>`).
|
||||
:keyword str reason:
|
||||
Reason why the test function is marked as xfail.
|
||||
:keyword Type[Exception] raises:
|
||||
Exception class (or tuple of classes) expected to be raised by the test function; other exceptions will fail the test.
|
||||
Note that subclasses of the classes passed will also result in a match (similar to how the ``except`` statement works).
|
||||
|
||||
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
|
||||
~~~~~~~~~~~~
|
||||
@@ -275,7 +269,8 @@ For example:
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.timeout(10, "slow", method="thread")
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
Will create and attach a :class:`Mark <pytest.Mark>` object to the collected
|
||||
:class:`Item <pytest.Item>`, which can then be accessed by fixtures or hooks with
|
||||
@@ -292,7 +287,8 @@ Example for using multiple custom markers:
|
||||
|
||||
@pytest.mark.timeout(10, "slow", method="thread")
|
||||
@pytest.mark.slow
|
||||
def test_function(): ...
|
||||
def test_function():
|
||||
...
|
||||
|
||||
When :meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>` or :meth:`Node.iter_markers_with_node <_pytest.nodes.Node.iter_markers_with_node>` is used with multiple markers, the marker closest to the function will be iterated over first. The above example will result in ``@pytest.mark.slow`` followed by ``@pytest.mark.timeout(...)``.
|
||||
|
||||
@@ -609,30 +605,10 @@ Hooks
|
||||
|
||||
**Tutorial**: :ref:`writing-plugins`
|
||||
|
||||
Reference to all hooks which can be implemented by :ref:`conftest.py files <localplugin>` and :ref:`plugins <plugins>`.
|
||||
|
||||
@pytest.hookimpl
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. function:: pytest.hookimpl
|
||||
:decorator:
|
||||
|
||||
pytest's decorator for marking functions as hook implementations.
|
||||
|
||||
See :ref:`writinghooks` and :func:`pluggy.HookimplMarker`.
|
||||
|
||||
@pytest.hookspec
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. function:: pytest.hookspec
|
||||
:decorator:
|
||||
|
||||
pytest's decorator for marking functions as hook specifications.
|
||||
|
||||
See :ref:`declaringhooks` and :func:`pluggy.HookspecMarker`.
|
||||
|
||||
.. currentmodule:: _pytest.hookspec
|
||||
|
||||
Reference to all hooks which can be implemented by :ref:`conftest.py files <localplugin>` and :ref:`plugins <plugins>`.
|
||||
|
||||
Bootstrapping hooks
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -640,6 +616,8 @@ Bootstrapping hooks called for plugins registered early enough (internal and set
|
||||
|
||||
.. hook:: pytest_load_initial_conftests
|
||||
.. autofunction:: pytest_load_initial_conftests
|
||||
.. hook:: pytest_cmdline_preparse
|
||||
.. autofunction:: pytest_cmdline_preparse
|
||||
.. hook:: pytest_cmdline_parse
|
||||
.. autofunction:: pytest_cmdline_parse
|
||||
.. hook:: pytest_cmdline_main
|
||||
@@ -677,8 +655,6 @@ Collection hooks
|
||||
.. autofunction:: pytest_collection
|
||||
.. hook:: pytest_ignore_collect
|
||||
.. autofunction:: pytest_ignore_collect
|
||||
.. hook:: pytest_collect_directory
|
||||
.. autofunction:: pytest_collect_directory
|
||||
.. hook:: pytest_collect_file
|
||||
.. autofunction:: pytest_collect_file
|
||||
.. hook:: pytest_pycollect_makemodule
|
||||
@@ -807,16 +783,23 @@ 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:
|
||||
|
||||
|
||||
Class
|
||||
~~~~~
|
||||
|
||||
.. autoclass:: pytest.Class()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -827,12 +810,32 @@ Collector
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Item
|
||||
~~~~
|
||||
CollectReport
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Item()
|
||||
.. autoclass:: pytest.CollectReport()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
Config
|
||||
~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Config()
|
||||
:members:
|
||||
|
||||
ExceptionInfo
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.ExceptionInfo()
|
||||
:members:
|
||||
|
||||
|
||||
ExitCode
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.ExitCode
|
||||
:members:
|
||||
|
||||
File
|
||||
~~~~
|
||||
@@ -841,6 +844,14 @@ File
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
|
||||
FixtureDef
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.fixtures.FixtureDef()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
FSCollector
|
||||
~~~~~~~~~~~
|
||||
|
||||
@@ -848,34 +859,6 @@ 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
|
||||
~~~~~
|
||||
|
||||
.. autoclass:: pytest.Class()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Function
|
||||
~~~~~~~~
|
||||
|
||||
@@ -890,64 +873,10 @@ FunctionDefinition
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Item
|
||||
~~~~
|
||||
|
||||
Objects
|
||||
-------
|
||||
|
||||
Objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`
|
||||
or importable from ``pytest``.
|
||||
|
||||
|
||||
CallInfo
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.CallInfo()
|
||||
:members:
|
||||
|
||||
CollectReport
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.CollectReport()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
Config
|
||||
~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Config()
|
||||
:members:
|
||||
|
||||
Dir
|
||||
~~~
|
||||
|
||||
.. autoclass:: pytest.Dir()
|
||||
:members:
|
||||
|
||||
Directory
|
||||
~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Directory()
|
||||
:members:
|
||||
|
||||
ExceptionInfo
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.ExceptionInfo()
|
||||
:members:
|
||||
|
||||
|
||||
ExitCode
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.ExitCode
|
||||
:members:
|
||||
|
||||
|
||||
FixtureDef
|
||||
~~~~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.FixtureDef()
|
||||
.. autoclass:: pytest.Item()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
@@ -978,6 +907,19 @@ Metafunc
|
||||
.. autoclass:: pytest.Metafunc()
|
||||
:members:
|
||||
|
||||
Module
|
||||
~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Module()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
Node
|
||||
~~~~
|
||||
|
||||
.. autoclass:: _pytest.nodes.Node()
|
||||
:members:
|
||||
|
||||
Parser
|
||||
~~~~~~
|
||||
|
||||
@@ -999,6 +941,13 @@ PytestPluginManager
|
||||
:inherited-members:
|
||||
:show-inheritance:
|
||||
|
||||
Session
|
||||
~~~~~~~
|
||||
|
||||
.. autoclass:: pytest.Session()
|
||||
:members:
|
||||
:show-inheritance:
|
||||
|
||||
TestReport
|
||||
~~~~~~~~~~
|
||||
|
||||
@@ -1007,16 +956,10 @@ TestReport
|
||||
:show-inheritance:
|
||||
:inherited-members:
|
||||
|
||||
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
|
||||
~~~~~
|
||||
@@ -1106,11 +1049,11 @@ Environment variables that can be used to change pytest's behavior.
|
||||
|
||||
.. envvar:: CI
|
||||
|
||||
When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to ``BUILD_NUMBER`` variable.
|
||||
When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to ``BUILD_NUMBER`` variable.
|
||||
|
||||
.. envvar:: BUILD_NUMBER
|
||||
|
||||
When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable.
|
||||
When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to CI variable.
|
||||
|
||||
.. envvar:: PYTEST_ADDOPTS
|
||||
|
||||
@@ -1155,22 +1098,19 @@ When set to ``0``, pytest will not use color.
|
||||
|
||||
.. envvar:: NO_COLOR
|
||||
|
||||
When set to a non-empty string (regardless of value), pytest will not use color in terminal output.
|
||||
When set (regardless of value), pytest will not use color in terminal output.
|
||||
``PY_COLORS`` takes precedence over ``NO_COLOR``, which takes precedence over ``FORCE_COLOR``.
|
||||
See `no-color.org <https://no-color.org/>`__ for other libraries supporting this community standard.
|
||||
|
||||
.. envvar:: FORCE_COLOR
|
||||
|
||||
When set to a non-empty string (regardless of value), pytest will use color in terminal output.
|
||||
When set (regardless of value), pytest will use color in terminal output.
|
||||
``PY_COLORS`` and ``NO_COLOR`` take precedence over ``FORCE_COLOR``.
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
.. autoexception:: pytest.UsageError()
|
||||
:show-inheritance:
|
||||
|
||||
.. autoexception:: pytest.FixtureLookupError()
|
||||
.. autoclass:: pytest.UsageError()
|
||||
:show-inheritance:
|
||||
|
||||
.. _`warnings ref`:
|
||||
@@ -1204,7 +1144,7 @@ Custom warnings generated in some situations such as improper usage or deprecate
|
||||
.. autoclass:: pytest.PytestReturnNotNoneWarning
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pytest.PytestRemovedIn9Warning
|
||||
.. autoclass:: pytest.PytestRemovedIn8Warning
|
||||
:show-inheritance:
|
||||
|
||||
.. autoclass:: pytest.PytestUnhandledCoroutineWarning
|
||||
@@ -1274,19 +1214,6 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
variables, that will be expanded. For more information about cache plugin
|
||||
please refer to :ref:`cache_provider`.
|
||||
|
||||
.. confval:: consider_namespace_packages
|
||||
|
||||
Controls if pytest should attempt to identify `namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages>`__
|
||||
when collecting Python modules. Default is ``False``.
|
||||
|
||||
Set to ``True`` if you are testing namespace packages installed into a virtual environment and it is important for
|
||||
your packages to be imported using their full namespace package name.
|
||||
|
||||
Only `native namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#native-namespace-packages>`__
|
||||
are supported, with no plans to support `legacy namespace packages <https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#legacy-namespace-packages>`__.
|
||||
|
||||
.. versionadded:: 8.1
|
||||
|
||||
.. confval:: console_output_style
|
||||
|
||||
Sets the console output style while running tests:
|
||||
@@ -1686,11 +1613,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
|
||||
@@ -1770,11 +1697,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
|
||||
|
||||
@@ -1791,12 +1713,13 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
|
||||
.. confval:: testpaths
|
||||
|
||||
|
||||
|
||||
Sets list of directories that should be searched for tests when
|
||||
no specific directories, files or test ids are given in the command line when
|
||||
executing pytest from the :ref:`rootdir <rootdir>` directory.
|
||||
File system paths may use shell-style wildcards, including the recursive
|
||||
``**`` pattern.
|
||||
|
||||
Useful when all project tests are in a known location to speed up
|
||||
test collection and to avoid picking up undesired tests by accident.
|
||||
|
||||
@@ -1805,17 +1728,8 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
[pytest]
|
||||
testpaths = testing doc
|
||||
|
||||
This configuration means that executing:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
pytest
|
||||
|
||||
has the same practical effects as executing:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
pytest testing doc
|
||||
This tells pytest to only look for tests in ``testing`` and ``doc``
|
||||
directories when executing from the root directory.
|
||||
|
||||
|
||||
.. confval:: tmp_path_retention_count
|
||||
@@ -1830,7 +1744,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
[pytest]
|
||||
tmp_path_retention_count = 3
|
||||
|
||||
Default: ``3``
|
||||
Default: 3
|
||||
|
||||
|
||||
.. confval:: tmp_path_retention_policy
|
||||
@@ -1849,7 +1763,7 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
[pytest]
|
||||
tmp_path_retention_policy = "all"
|
||||
|
||||
Default: ``all``
|
||||
Default: all
|
||||
|
||||
|
||||
.. confval:: usefixtures
|
||||
@@ -1865,32 +1779,6 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||
clean_db
|
||||
|
||||
|
||||
.. confval:: verbosity_assertions
|
||||
|
||||
Set a verbosity level specifically for assertion related output, overriding the application wide level.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
verbosity_assertions = 2
|
||||
|
||||
Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
|
||||
"auto" can be used to explicitly use the global verbosity level.
|
||||
|
||||
|
||||
.. confval:: verbosity_test_cases
|
||||
|
||||
Set a verbosity level specifically for test case execution related output, overriding the application wide level.
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
verbosity_test_cases = 2
|
||||
|
||||
Defaults to application wide verbosity level (via the ``-v`` command-line option). A special value of
|
||||
"auto" can be used to explicitly use the global verbosity level.
|
||||
|
||||
|
||||
.. confval:: xfail_strict
|
||||
|
||||
If set to ``True``, tests marked with ``@pytest.mark.xfail`` that actually succeed will by default fail the
|
||||
@@ -1964,12 +1852,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
|
||||
@@ -2020,9 +1904,8 @@ 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
|
||||
Load configuration from `FILE` instead of trying to
|
||||
locate one of the implicit configuration files.
|
||||
-c file Load configuration from `file` instead of trying to
|
||||
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
|
||||
@@ -2054,7 +1937,7 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
failure
|
||||
--doctest-glob=pat Doctests file matching pattern, default: test*.txt
|
||||
--doctest-ignore-import-errors
|
||||
Ignore doctest collection errors
|
||||
Ignore doctest ImportErrors
|
||||
--doctest-continue-on-failure
|
||||
For a given doctest, continue to run after the first
|
||||
failure
|
||||
@@ -2103,8 +1986,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
--log-cli-date-format=LOG_CLI_DATE_FORMAT
|
||||
Log date format used by the logging module
|
||||
--log-file=LOG_FILE Path to a file when logging will be written to
|
||||
--log-file-mode={w,a}
|
||||
Log file open mode
|
||||
--log-file-level=LOG_FILE_LEVEL
|
||||
Log file logging level
|
||||
--log-file-format=LOG_FILE_FORMAT
|
||||
@@ -2115,12 +1996,12 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
Auto-indent multiline messages passed to the logging
|
||||
module. Accepts true|on, false|off or an integer.
|
||||
--log-disable=LOGGER_DISABLE
|
||||
Disable a logger by name. Can be passed multiple
|
||||
Disable a logger by name. Can be passed multipe
|
||||
times.
|
||||
|
||||
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:
|
||||
|
||||
markers (linelist): Register new markers for test functions
|
||||
markers (linelist): Markers for test functions
|
||||
empty_parameter_set_mark (string):
|
||||
Default marker for empty parametersets
|
||||
norecursedirs (args): Directory patterns to avoid for recursion
|
||||
@@ -2130,9 +2011,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
Each line specifies a pattern for
|
||||
warnings.filterwarnings. Processed after
|
||||
-W/--pythonwarnings.
|
||||
consider_namespace_packages (bool):
|
||||
Consider namespace packages when resolving module
|
||||
names during import
|
||||
usefixtures (args): List of default fixtures to be used with this
|
||||
project
|
||||
python_files (args): Glob-style file patterns for Python test module
|
||||
@@ -2151,11 +2029,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
progress information ("progress" (percentage) |
|
||||
"count" | "progress-even-when-capture-no" (forces
|
||||
progress even when capture=no)
|
||||
verbosity_test_cases (string):
|
||||
Specify a verbosity level for test case execution,
|
||||
overriding the main level. Higher levels will
|
||||
provide more detailed information about each test
|
||||
case executed.
|
||||
xfail_strict (bool): Default for the strict parameter of xfail markers
|
||||
when not given explicitly (default: False)
|
||||
tmp_path_retention_count (string):
|
||||
@@ -2169,10 +2042,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
enable_assertion_pass_hook (bool):
|
||||
Enables the pytest_assertion_pass hook. Make sure to
|
||||
delete any previously generated pyc cache files.
|
||||
verbosity_assertions (string):
|
||||
Specify a verbosity level for assertions, overriding
|
||||
the main level. Higher levels will provide more
|
||||
detailed explanation when an assertion fails.
|
||||
junit_suite_name (string):
|
||||
Test suite name for JUnit report
|
||||
junit_logging (string):
|
||||
@@ -2203,8 +2072,6 @@ All the command-line flags can be obtained by running ``pytest --help``::
|
||||
log_cli_date_format (string):
|
||||
Default value for --log-cli-date-format
|
||||
log_file (string): Default value for --log-file
|
||||
log_file_mode (string):
|
||||
Default value for --log-file-mode
|
||||
log_file_level (string):
|
||||
Default value for --log-file-level
|
||||
log_file_format (string):
|
||||
|
||||
@@ -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>=7
|
||||
sphinx>=5,<6
|
||||
sphinxcontrib-trio
|
||||
sphinxcontrib-svg2pdfconverter
|
||||
# Pin packaging because it no longer handles 'latest' version, which
|
||||
|
||||
@@ -3,7 +3,6 @@ from pathlib import Path
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
issues_url = "https://api.github.com/repos/pytest-dev/pytest/issues"
|
||||
|
||||
|
||||
|
||||
200
pyproject.toml
200
pyproject.toml
@@ -1,178 +1,14 @@
|
||||
[project]
|
||||
name = "pytest"
|
||||
description = "pytest: simple powerful testing with Python"
|
||||
readme = "README.rst"
|
||||
keywords = [
|
||||
"test",
|
||||
"unittest",
|
||||
]
|
||||
license = {text = "MIT"}
|
||||
authors = [
|
||||
{name = "Holger Krekel"},
|
||||
{name = "Bruno Oliveira"},
|
||||
{name = "Ronny Pfannschmidt"},
|
||||
{name = "Floris Bruynooghe"},
|
||||
{name = "Brianna Laugher"},
|
||||
{name = "Florian Bruhin"},
|
||||
{name = "Others (See AUTHORS)"},
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
classifiers = [
|
||||
"Development Status :: 6 - Mature",
|
||||
"Intended Audience :: Developers",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Operating System :: MacOS",
|
||||
"Operating System :: Microsoft :: Windows",
|
||||
"Operating System :: POSIX",
|
||||
"Operating System :: Unix",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
"Programming Language :: Python :: 3.9",
|
||||
"Programming Language :: Python :: 3.10",
|
||||
"Programming Language :: Python :: 3.11",
|
||||
"Programming Language :: Python :: 3.12",
|
||||
"Topic :: Software Development :: Libraries",
|
||||
"Topic :: Software Development :: Testing",
|
||||
"Topic :: Utilities",
|
||||
]
|
||||
dynamic = [
|
||||
"version",
|
||||
]
|
||||
dependencies = [
|
||||
'colorama; sys_platform == "win32"',
|
||||
'exceptiongroup>=1.0.0rc8; python_version < "3.11"',
|
||||
"iniconfig",
|
||||
"packaging",
|
||||
"pluggy<2.0,>=1.4",
|
||||
'tomli>=1; python_version < "3.11"',
|
||||
]
|
||||
[project.optional-dependencies]
|
||||
testing = [
|
||||
"argcomplete",
|
||||
"attrs>=19.2",
|
||||
"hypothesis>=3.56",
|
||||
"mock",
|
||||
"pygments>=2.7.2",
|
||||
"requests",
|
||||
"setuptools",
|
||||
"xmlschema",
|
||||
]
|
||||
[project.urls]
|
||||
Changelog = "https://docs.pytest.org/en/stable/changelog.html"
|
||||
Homepage = "https://docs.pytest.org/en/latest/"
|
||||
Source = "https://github.com/pytest-dev/pytest"
|
||||
Tracker = "https://github.com/pytest-dev/pytest/issues"
|
||||
Twitter = "https://twitter.com/pytestdotorg"
|
||||
[project.scripts]
|
||||
"py.test" = "pytest:console_main"
|
||||
pytest = "pytest:console_main"
|
||||
|
||||
[build-system]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = [
|
||||
"setuptools>=61",
|
||||
"setuptools-scm[toml]>=6.2.3",
|
||||
# sync with setup.py until we discard non-pep-517/518
|
||||
"setuptools>=45.0",
|
||||
"setuptools-scm[toml]>=6.2.3",
|
||||
]
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
"_pytest" = ["py.typed"]
|
||||
"pytest" = ["py.typed"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
write_to = "src/_pytest/_version.py"
|
||||
|
||||
[tool.black]
|
||||
target-version = ['py38']
|
||||
|
||||
[tool.ruff]
|
||||
src = ["src"]
|
||||
line-length = 88
|
||||
|
||||
[tool.ruff.format]
|
||||
docstring-code-format = true
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"B", # bugbear
|
||||
"D", # pydocstyle
|
||||
"E", # pycodestyle
|
||||
"F", # pyflakes
|
||||
"I", # isort
|
||||
"PYI", # flake8-pyi
|
||||
"UP", # pyupgrade
|
||||
"RUF", # ruff
|
||||
"W", # pycodestyle
|
||||
"PIE", # flake8-pie
|
||||
"PGH004", # pygrep-hooks - Use specific rule codes when using noqa
|
||||
"PLE", # pylint error
|
||||
"PLW", # pylint warning
|
||||
"PLR1714", # Consider merging multiple comparisons
|
||||
]
|
||||
ignore = [
|
||||
# bugbear ignore
|
||||
"B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable.
|
||||
"B007", # Loop control variable `i` not used within loop body
|
||||
"B009", # Do not call `getattr` with a constant attribute value
|
||||
"B010", # [*] Do not call `setattr` with a constant attribute value.
|
||||
"B011", # Do not `assert False` (`python -O` removes these calls)
|
||||
"B028", # No explicit `stacklevel` keyword argument found
|
||||
# pycodestyle ignore
|
||||
# pytest can do weird low-level things, and we usually know
|
||||
# what we're doing when we use type(..) is ...
|
||||
"E721", # Do not compare types, use `isinstance()`
|
||||
# pydocstyle ignore
|
||||
"D100", # Missing docstring in public module
|
||||
"D101", # Missing docstring in public class
|
||||
"D102", # Missing docstring in public method
|
||||
"D103", # Missing docstring in public function
|
||||
"D104", # Missing docstring in public package
|
||||
"D105", # Missing docstring in magic method
|
||||
"D106", # Missing docstring in public nested class
|
||||
"D107", # Missing docstring in `__init__`
|
||||
"D209", # [*] Multi-line docstring closing quotes should be on a separate line
|
||||
"D205", # 1 blank line required between summary line and description
|
||||
"D400", # First line should end with a period
|
||||
"D401", # First line of docstring should be in imperative mood
|
||||
"D402", # First line should not be the function's signature
|
||||
"D404", # First word of the docstring should not be "This"
|
||||
"D415", # First line should end with a period, question mark, or exclamation point
|
||||
# ruff ignore
|
||||
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`
|
||||
# pylint ignore
|
||||
"PLW0603", # Using the global statement
|
||||
"PLW0120", # remove the else and dedent its contents
|
||||
"PLW2901", # for loop variable overwritten by assignment target
|
||||
"PLR5501", # Use `elif` instead of `else` then `if`
|
||||
]
|
||||
|
||||
[tool.ruff.lint.pycodestyle]
|
||||
# In order to be able to format for 88 char in ruff format
|
||||
max-line-length = 120
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "pep257"
|
||||
|
||||
[tool.ruff.lint.isort]
|
||||
force-single-line = true
|
||||
combine-as-imports = true
|
||||
force-sort-within-sections = true
|
||||
order-by-type = false
|
||||
known-local-folder = ["pytest", "_pytest"]
|
||||
lines-after-imports = 2
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"src/_pytest/_py/**/*.py" = ["B", "PYI"]
|
||||
"src/_pytest/_version.py" = ["I001"]
|
||||
"testing/python/approx.py" = ["B015"]
|
||||
|
||||
[tool.check-wheel-contents]
|
||||
# check-wheel-contents is executed by the build-and-inspect-python-package action.
|
||||
# W009: Wheel contains multiple toplevel library entries
|
||||
ignore = "W009"
|
||||
|
||||
[tool.pyproject-fmt]
|
||||
indent = 4
|
||||
|
||||
[tool.pytest.ini_options]
|
||||
minversion = "2.0"
|
||||
addopts = "-rfEX -p pytester --strict-markers"
|
||||
@@ -181,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",
|
||||
@@ -232,6 +63,7 @@ markers = [
|
||||
"uses_pexpect",
|
||||
]
|
||||
|
||||
|
||||
[tool.towncrier]
|
||||
package = "pytest"
|
||||
package_dir = "src"
|
||||
@@ -280,16 +112,10 @@ template = "changelog/_template.rst"
|
||||
name = "Trivial/Internal Changes"
|
||||
showcontent = true
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = ["src"]
|
||||
check_untyped_defs = true
|
||||
disallow_any_generics = true
|
||||
disallow_untyped_defs = true
|
||||
ignore_missing_imports = true
|
||||
show_error_codes = true
|
||||
strict_equality = true
|
||||
warn_redundant_casts = true
|
||||
warn_return_any = true
|
||||
warn_unreachable = true
|
||||
warn_unused_configs = true
|
||||
no_implicit_reexport = true
|
||||
[tool.black]
|
||||
target-version = ['py37']
|
||||
|
||||
# check-wheel-contents is executed by the build-and-inspect-python-package action.
|
||||
[tool.check-wheel-contents]
|
||||
# W009: Wheel contains multiple toplevel library entries
|
||||
ignore = "W009"
|
||||
|
||||
1
scripts/.gitignore
vendored
1
scripts/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
latest-release-notes.md
|
||||
@@ -1,66 +0,0 @@
|
||||
# mypy: disallow-untyped-defs
|
||||
"""
|
||||
Script used to generate a Markdown file containing only the changelog entries of a specific pytest release, which
|
||||
is then published as a GitHub Release during deploy (see workflows/deploy.yml).
|
||||
|
||||
The script requires ``pandoc`` to be previously installed in the system -- we need to convert from RST (the format of
|
||||
our CHANGELOG) into Markdown (which is required by GitHub Releases).
|
||||
|
||||
Requires Python3.6+.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import re
|
||||
import sys
|
||||
from typing import Sequence
|
||||
|
||||
import pypandoc
|
||||
|
||||
|
||||
def extract_changelog_entries_for(version: str) -> str:
|
||||
p = Path(__file__).parent.parent / "doc/en/changelog.rst"
|
||||
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
|
||||
|
||||
title_regex = re.compile(r"pytest (\d\.\d+\.\d+\w*) \(\d{4}-\d{2}-\d{2}\)")
|
||||
consuming_version = False
|
||||
version_lines = []
|
||||
for line in changelog_lines:
|
||||
m = title_regex.match(line)
|
||||
if m:
|
||||
# Found the version we want: start to consume lines until we find the next version title.
|
||||
if m.group(1) == version:
|
||||
consuming_version = True
|
||||
# Found a new version title while parsing the version we want: break out.
|
||||
elif consuming_version:
|
||||
break
|
||||
if consuming_version:
|
||||
version_lines.append(line)
|
||||
|
||||
return "\n".join(version_lines)
|
||||
|
||||
|
||||
def convert_rst_to_md(text: str) -> str:
|
||||
result = pypandoc.convert_text(
|
||||
text, "md", format="rst", extra_args=["--wrap=preserve"]
|
||||
)
|
||||
assert isinstance(result, str), repr(result)
|
||||
return result
|
||||
|
||||
|
||||
def main(argv: Sequence[str]) -> int:
|
||||
if len(argv) != 3:
|
||||
print("Usage: generate-gh-release-notes VERSION FILE")
|
||||
return 2
|
||||
|
||||
version, filename = argv[1:3]
|
||||
print(f"Generating GitHub release notes for version {version}")
|
||||
rst_body = extract_changelog_entries_for(version)
|
||||
md_body = convert_rst_to_md(rst_body)
|
||||
Path(filename).write_text(md_body, encoding="UTF-8")
|
||||
print()
|
||||
print(f"Done: {filename}")
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
@@ -1,4 +1,3 @@
|
||||
# mypy: disallow-untyped-defs
|
||||
"""
|
||||
This script is part of the pytest release process which is triggered manually in the Actions
|
||||
tab of the repository.
|
||||
@@ -14,8 +13,8 @@ After that, it will create a release using the `release` tox environment, and pu
|
||||
`pytest bot <pytestbot@gmail.com>` commit author.
|
||||
"""
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import re
|
||||
from pathlib import Path
|
||||
from subprocess import check_call
|
||||
from subprocess import check_output
|
||||
from subprocess import run
|
||||
@@ -32,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.
|
||||
"""
|
||||
|
||||
|
||||
@@ -79,7 +66,7 @@ def prepare_release_pr(
|
||||
)
|
||||
except InvalidFeatureRelease as e:
|
||||
print(f"{Fore.RED}{e}")
|
||||
raise SystemExit(1) from None
|
||||
raise SystemExit(1)
|
||||
|
||||
print(f"Version: {Fore.CYAN}{version}")
|
||||
|
||||
|
||||
102
scripts/publish-gh-release-notes.py
Normal file
102
scripts/publish-gh-release-notes.py
Normal file
@@ -0,0 +1,102 @@
|
||||
"""
|
||||
Script used to publish GitHub release notes extracted from CHANGELOG.rst.
|
||||
|
||||
This script is meant to be executed after a successful deployment in GitHub actions.
|
||||
|
||||
Uses the following environment variables:
|
||||
|
||||
* GIT_TAG: the name of the tag of the current commit.
|
||||
* GH_RELEASE_NOTES_TOKEN: a personal access token with 'repo' permissions.
|
||||
|
||||
Create one at:
|
||||
|
||||
https://github.com/settings/tokens
|
||||
|
||||
This token should be set in a secret in the repository, which is exposed as an
|
||||
environment variable in the main.yml workflow file.
|
||||
|
||||
The script also requires ``pandoc`` to be previously installed in the system.
|
||||
|
||||
Requires Python3.6+.
|
||||
"""
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import github3
|
||||
import pypandoc
|
||||
|
||||
|
||||
def publish_github_release(slug, token, tag_name, body):
|
||||
github = github3.login(token=token)
|
||||
owner, repo = slug.split("/")
|
||||
repo = github.repository(owner, repo)
|
||||
return repo.create_release(tag_name=tag_name, body=body)
|
||||
|
||||
|
||||
def parse_changelog(tag_name):
|
||||
p = Path(__file__).parent.parent / "doc/en/changelog.rst"
|
||||
changelog_lines = p.read_text(encoding="UTF-8").splitlines()
|
||||
|
||||
title_regex = re.compile(r"pytest (\d\.\d+\.\d+) \(\d{4}-\d{2}-\d{2}\)")
|
||||
consuming_version = False
|
||||
version_lines = []
|
||||
for line in changelog_lines:
|
||||
m = title_regex.match(line)
|
||||
if m:
|
||||
# found the version we want: start to consume lines until we find the next version title
|
||||
if m.group(1) == tag_name:
|
||||
consuming_version = True
|
||||
# found a new version title while parsing the version we want: break out
|
||||
elif consuming_version:
|
||||
break
|
||||
if consuming_version:
|
||||
version_lines.append(line)
|
||||
|
||||
return "\n".join(version_lines)
|
||||
|
||||
|
||||
def convert_rst_to_md(text):
|
||||
return pypandoc.convert_text(
|
||||
text, "md", format="rst", extra_args=["--wrap=preserve"]
|
||||
)
|
||||
|
||||
|
||||
def main(argv):
|
||||
if len(argv) > 1:
|
||||
tag_name = argv[1]
|
||||
else:
|
||||
tag_name = os.environ.get("GITHUB_REF")
|
||||
if not tag_name:
|
||||
print("tag_name not given and $GITHUB_REF not set", file=sys.stderr)
|
||||
return 1
|
||||
if tag_name.startswith("refs/tags/"):
|
||||
tag_name = tag_name[len("refs/tags/") :]
|
||||
|
||||
token = os.environ.get("GH_RELEASE_NOTES_TOKEN")
|
||||
if not token:
|
||||
print("GH_RELEASE_NOTES_TOKEN not set", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
slug = os.environ.get("GITHUB_REPOSITORY")
|
||||
if not slug:
|
||||
print("GITHUB_REPOSITORY not set", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
rst_body = parse_changelog(tag_name)
|
||||
md_body = convert_rst_to_md(rst_body)
|
||||
if not publish_github_release(slug, token, tag_name, md_body):
|
||||
print("Could not publish release notes:", file=sys.stderr)
|
||||
print(md_body, file=sys.stderr)
|
||||
return 5
|
||||
|
||||
print()
|
||||
print(f"Release notes for {tag_name} published successfully:")
|
||||
print(f"https://github.com/{slug}/releases/tag/{tag_name}")
|
||||
print()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main(sys.argv))
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user