Compare commits

..

73 Commits

Author SHA1 Message Date
Pierre Sassoulas b31db4809b Avoid truncation when truncating means longer output (#10446)
Fixes #6267
2022-12-17 10:24:46 -03:00
Bruno Oliveira f6adebb990 Fix tox 4.0 support and docs
Also includes pre-commit autoupdate
2022-12-14 08:57:07 -03:00
Bruno Oliveira b90e7b84d0 Remove reference to explicit pytest tox support
This reference no longer exists in tox 4.0 docs.
2022-12-14 08:38:11 -03:00
Bruno Oliveira 19807ab79a Pin packaging while building docs
Packaging >=22 no longer supports 'latest' versions, which is the version that is assigned when building the docs.

https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045

Also first install pytest, then `docs/en/requirements.txt`:

Installing the requirements last will honor pins of libraries
shared between pytest and the docs build.
2022-12-14 08:38:11 -03:00
Bruno Oliveira 3e52124185 Fix passenv setting for tox.ini
In tox 4.0 `passenv` is required to be one per-line, or comma-separated.
2022-12-14 08:35:39 -03:00
pre-commit-ci[bot] 1eca228bd5 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/psf/black: 22.10.0 → 22.12.0](https://github.com/psf/black/compare/22.10.0...22.12.0)
- [github.com/asottile/pyupgrade: v3.3.0 → v3.3.1](https://github.com/asottile/pyupgrade/compare/v3.3.0...v3.3.1)
2022-12-13 01:28:21 +00:00
Bruno Oliveira cab02e67d7 Merge pull request #10559 from pytest-dev/update-plugin-list/patch-9fbd67dd4
[automated] Update plugin list
2022-12-06 11:17:45 -03:00
Bruno Oliveira 64dbc7a0a1 Merge pull request #10560 from pytest-dev/dependabot/github_actions/peter-evans/create-pull-request-4.2.3
build(deps): Bump peter-evans/create-pull-request from 3.7.0 to 4.2.3
2022-12-06 11:16:54 -03:00
Bruno Oliveira 60d992677d Merge pull request #10563 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-12-06 11:06:16 -03:00
pre-commit-ci[bot] 0079decf29 [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.2.2 → v3.3.0](https://github.com/asottile/pyupgrade/compare/v3.2.2...v3.3.0)
2022-12-06 00:36:28 +00:00
dependabot[bot] 3a58fc2d44 build(deps): Bump peter-evans/create-pull-request from 3.7.0 to 4.2.3
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 3.7.0 to 4.2.3.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/2455e1596942c2902952003bbb574afbbe2ab2e6...2b011faafdcbc9ceb11414d64d0573f37c774b04)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-12-05 03:15:13 +00:00
pytest bot 39b6bb551c [automated] Update plugin list 2022-12-04 00:20:05 +00:00
Marko Pacak 9fbd67dd4b Class methods can now be discovered as tests (#10552)
Fix #10525
2022-12-02 15:53:04 +00:00
Zac Hatfield-Dodds eca93db05b Merge pull request #10550 from danschef/use_os_sep
Use os.sep instead of os.path.sep.
2022-12-02 00:32:12 -08:00
Daniel Scheffler fb701b538c Use os.sep instead of os.path.sep.
Signed-off-by: Daniel Scheffler <danschef@gfz-potsdam.de>
2022-12-01 18:16:02 +01:00
Bruno Oliveira 314e623304 Merge pull request #10549 from yusuke-kadowaki/more_doc_update
Documentation update for the tmp_path configurations
2022-12-01 12:23:43 -03:00
Yusuke Kadowaki 62e75c7d55 Documentation update for tmp_path configurations 2022-12-01 22:29:46 +09:00
github-actions[bot] fd30759d94 [automated] Update plugin list (#10536)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2022-11-29 09:14:38 +00:00
dependabot[bot] eb984a717a build(deps): Bump pytest-rerunfailures in /testing/plugins_integration (#10537)
Bumps [pytest-rerunfailures](https://github.com/pytest-dev/pytest-rerunfailures) from 10.2 to 10.3.
- [Release notes](https://github.com/pytest-dev/pytest-rerunfailures/releases)
- [Changelog](https://github.com/pytest-dev/pytest-rerunfailures/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-rerunfailures/compare/10.2...10.3)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-29 09:55:24 +01:00
Anthony Sottile 54f0fb3c63 Merge pull request #10543 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-11-28 20:27:39 -08:00
pre-commit-ci[bot] 49a4ed14cf [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/pre-commit/pre-commit-hooks: v4.3.0 → v4.4.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.3.0...v4.4.0)
- [github.com/PyCQA/autoflake: v1.7.7 → v2.0.0](https://github.com/PyCQA/autoflake/compare/v1.7.7...v2.0.0)
- [github.com/PyCQA/flake8: 5.0.4 → 6.0.0](https://github.com/PyCQA/flake8/compare/5.0.4...6.0.0)
2022-11-29 02:01:34 +00:00
Yusuke Kadowaki f513d33d5a Modify documentation to use .stash when storing test results. (#10535) 2022-11-27 22:09:56 +02:00
Prerak Patel 857e34ef85 Fix bug where file system root was erroneously be used as rootdir on Windows
Fix #10506
2022-11-23 14:46:00 -03:00
Yusuke Kadowaki 99dfc19fe6 Fix tmp_path_retention_policy crash when skipping from fixture (#10517)
Also uses the stash to save the test status.

Fix #10502
2022-11-23 10:48:29 -03:00
pre-commit-ci[bot] 56544c11b5 [pre-commit.ci] pre-commit autoupdate (#10522)
updates:
- [github.com/pre-commit/mirrors-mypy: v0.990 → v0.991](https://github.com/pre-commit/mirrors-mypy/compare/v0.990...v0.991)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-11-22 08:27:55 -03:00
github-actions[bot] 7710e18b4c [automated] Update plugin list (#10519)
Co-authored-by: pytest bot <pytestbot@users.noreply.github.com>
2022-11-22 08:05:40 -03:00
Daniel Valenzuela 791b51d0fa Show test name when skipping from fixture (#10482)
Fixes #10457
2022-11-18 09:20:38 -03:00
Anthony Sottile bc4e70e048 Merge pull request #10504 from pytest-dev/all-repos_autofix_no-implicit-optional
remove no_implicit_optional
2022-11-17 00:30:51 -05:00
Anthony Sottile b817aa457c remove no_implicit_optional
this is the default in mypy 0.990

Committed via https://github.com/asottile/all-repos
2022-11-16 19:20:16 -05:00
Anthony Sottile 66b28912ac Merge pull request #10497 from pytest-dev/pre-commit-ci-update-config
[pre-commit.ci] pre-commit autoupdate
2022-11-16 13:18:59 -05:00
Yusuke Kadowaki cca029d55e Add configuration options to control how tmp_path directories are kept (#10442)
Close #8141
2022-11-15 09:11:39 -03:00
Bruno Oliveira d5466b3917 Fix typing errors after mypy update 2022-11-15 08:53:23 -03:00
pre-commit-ci[bot] 4fce29f15d [pre-commit.ci] pre-commit autoupdate
updates:
- [github.com/asottile/pyupgrade: v3.2.0 → v3.2.2](https://github.com/asottile/pyupgrade/compare/v3.2.0...v3.2.2)
- [github.com/pre-commit/mirrors-mypy: v0.982 → v0.990](https://github.com/pre-commit/mirrors-mypy/compare/v0.982...v0.990)
2022-11-15 00:17:06 +00:00
Bruno Oliveira 69e3973d86 Merge pull request #10468 from pytest-dev/dependabot/pip/testing/plugins_integration/django-4.1.3
build(deps): Bump django from 4.1.2 to 4.1.3 in /testing/plugins_integration
2022-11-14 09:25:28 -03:00
dependabot[bot] c842893b02 build(deps): Bump django in /testing/plugins_integration
Bumps [django](https://github.com/django/django) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/django/django/releases)
- [Commits](https://github.com/django/django/compare/4.1.2...4.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 11:36:55 +00:00
dependabot[bot] 506b10d295 build(deps): Bump pytest-asyncio in /testing/plugins_integration (#10494)
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.20.1 to 0.20.2.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Changelog](https://github.com/pytest-dev/pytest-asyncio/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.20.1...v0.20.2)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
2022-11-14 11:36:00 +00:00
Bruno Oliveira 05061493cb Merge pull request #10495 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-bdd-6.1.1
build(deps): Bump pytest-bdd from 6.0.1 to 6.1.1 in /testing/plugins_integration
2022-11-14 08:09:07 -03:00
dependabot[bot] f97f3dc3a3 build(deps): Bump pytest-bdd in /testing/plugins_integration
Bumps [pytest-bdd](https://github.com/pytest-dev/pytest-bdd) from 6.0.1 to 6.1.1.
- [Release notes](https://github.com/pytest-dev/pytest-bdd/releases)
- [Changelog](https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst)
- [Commits](https://github.com/pytest-dev/pytest-bdd/compare/6.0.1...6.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-11-14 03:00:43 +00:00
Bruno Oliveira 3c31b0132f Merge pull request #10493 from pytest-dev/update-plugin-list/patch-54d5a63d1
[automated] Update plugin list
2022-11-13 12:02:36 -03:00
pytest bot 593178d909 [automated] Update plugin list 2022-11-13 00:22:21 +00:00
Bruno Oliveira 54d5a63d14 Merge pull request #10488 from DanielVZ96/fix-test-raising-repr
Fix test_raising_repr test
2022-11-09 20:20:05 -03:00
Daniel Valenzuela b55e264a67 Fix test_raising_repr test
Closes #10473

Python <3.11 versions depend on `exceptiongroup>=1.0.0rc8`, and they released version `1.0.1`
6 days ago (2022/11/03) that as a side-effect changed the output of exceptions.
2022-11-09 19:43:10 -03:00
Bruno Oliveira 13d6114c0a Merge pull request #10484 from joukewitteveen/patch-2
scripts/update-plugin-list: Improve requirement detection
2022-11-09 10:07:12 -03:00
Jouke Witteveen b635e16d30 scripts/update-plugin-list: Improve requirement detection
PEP 566 does not require a space after the dependency name.
2022-11-08 19:05:10 +01:00
Bruno Oliveira a092b3ab36 Merge pull request #10481 from pytest-dev/update-plugin-list/patch-aa7e9de91
[automated] Update plugin list
2022-11-08 09:07:00 -03:00
pytest bot a006dabf6e [automated] Update plugin list 2022-11-08 11:13:43 +00:00
Bruno Oliveira aa7e9de91d Merge pull request #10480 from joukewitteveen/patch-1
scripts/update-plugin-list: Be liberal in accepted summaries
2022-11-08 08:11:35 -03:00
Jouke Witteveen 6aec32163d scripts/update-plugin-list: Be liberal in accepted summaries
Misconfigured packages may have a null summary in the PyPI json response.
2022-11-07 21:00:31 +01:00
Florian Bruhin 2f33ea87c8 Remove done trainings (#10471) 2022-11-04 18:38:09 +01:00
Ashish Kurmi 1ada62e237 Update dependabot for GitHub Actions (#10464)
Signed-off-by: Ashish Kurmi <akurmi@stepsecurity.io>

Signed-off-by: Ashish Kurmi <akurmi@stepsecurity.io>
2022-11-04 07:41:34 -03:00
pre-commit-ci[bot] 50b232b0cb [pre-commit.ci] pre-commit autoupdate (#10454)
updates:
- [github.com/asottile/reorder_python_imports: v3.8.5 → v3.9.0](https://github.com/asottile/reorder_python_imports/compare/v3.8.5...v3.9.0)
- [github.com/asottile/pyupgrade: v3.1.0 → v3.2.0](https://github.com/asottile/pyupgrade/compare/v3.1.0...v3.2.0)
- [github.com/asottile/setup-cfg-fmt: v2.1.0 → v2.2.0](https://github.com/asottile/setup-cfg-fmt/compare/v2.1.0...v2.2.0)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-31 21:37:05 -03:00
dependabot[bot] 496196b15c build(deps): Bump pytest-html in /testing/plugins_integration (#10450)
Bumps [pytest-html](https://github.com/pytest-dev/pytest-html) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/pytest-dev/pytest-html/releases)
- [Changelog](https://github.com/pytest-dev/pytest-html/blob/master/docs/changelog.rst)
- [Commits](https://github.com/pytest-dev/pytest-html/compare/v3.1.1...v3.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-31 12:51:34 -03:00
Hugo van Kemenade 0314b50c52 Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12 (#10452) 2022-10-31 12:49:51 -03:00
wim glenn 8e2de91bf8 Merge pull request #10449 from cj81499/patch-1
fix type in python_api.py
2022-10-30 22:26:56 -05:00
Cal Jacobson 692ab1160b add Cal Jacobson to AUTHORS 2022-10-30 22:02:18 -05:00
Cal Jacobson 549839bac5 fix type in python_api.py
excpected -> expected
2022-10-30 21:59:09 -05:00
Florian Bruhin 646a46e5f4 Edit changelog for 7.2.0 (#10423)
Fix some typos, and consolidate two entries for the same feature into one.
2022-10-25 19:47:16 +02:00
Santiago Castro f07017f91b Add the PyPI classifier for Python 3.11 (#10426) 2022-10-25 13:43:59 -03:00
Anthony Sottile a17d3b0c44 Merge pull request #10425 from pytest-dev/asottile-patch-1
upgrade pygments-pytest for 7.2.x coloring
2022-10-25 11:27:36 -04:00
Anthony Sottile bbec1ce67f upgrade pygments-pytest for 7.2.x coloring 2022-10-25 10:52:53 -04:00
Ronny Pfannschmidt 5a040aef97 Merge pull request #10412 from pytest-dev/release-7.2.0 (#10419)
Prepare release 7.2.0

(cherry picked from commit ac4e3cced9)
2022-10-25 13:12:55 +02:00
Bruno Oliveira c1d2168df6 Merge pull request #10417 from nicoddemus/publish-action-pin
Use specific tag in the gh-action-pypi-publish action
2022-10-25 08:11:33 -03:00
pre-commit-ci[bot] bbe7cbae4a [pre-commit.ci] pre-commit autoupdate (#10418)
updates:
- [github.com/PyCQA/autoflake: v1.7.6 → v1.7.7](https://github.com/PyCQA/autoflake/compare/v1.7.6...v1.7.7)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2022-10-24 20:25:15 -03:00
Bruno Oliveira deae8f47f6 Use specific tag in the gh-action-pypi-publish action
Otherwise we see this warning:

Warning:  You are using "pypa/gh-action-pypi-publish@master". The "master" branch of this project has been sunset and will not receive any updates, not even security bug fixes. Please, make sure to use a supported version. If you want to pin to v1 major version, use "pypa/gh-action-pypi-publish@release/v1". If you feel adventurous, you may opt to use use "pypa/gh-action-pypi-publish@unstable/v1" instead. A more general recommendation is to pin to exact tags or commit shas.
2022-10-24 19:23:40 -03:00
Zac Hatfield-Dodds 10f55f79af Merge pull request #10226 from Zac-HD/use-exceptiongroup-for-teardown
Use exceptiongroup for multiple errors during teardown
2022-10-24 08:44:02 -07:00
Bruno Oliveira a6d244343f Merge pull request #10416 from rettinghaus/update-actions
Update used GitHub Actions to latest versions
2022-10-24 10:32:58 -03:00
Klaus Rettinghaus 2b552c2240 update actions 2022-10-24 14:58:03 +02:00
Bruno Oliveira 54d7b9a08e Merge pull request #10415 from pytest-dev/dependabot/pip/testing/plugins_integration/pytest-asyncio-0.20.1
build(deps): Bump pytest-asyncio from 0.19.0 to 0.20.1 in /testing/plugins_integration
2022-10-24 08:12:12 -03:00
Bruno Oliveira 6afc02abca Merge pull request #10414 from pytest-dev/dependabot/pip/testing/plugins_integration/anyio-curiotrio--3.6.2
build(deps): Bump anyio[curio,trio] from 3.6.1 to 3.6.2 in /testing/plugins_integration
2022-10-24 08:11:49 -03:00
dependabot[bot] e75e2d66a0 build(deps): Bump pytest-asyncio in /testing/plugins_integration
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.19.0 to 0.20.1.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Changelog](https://github.com/pytest-dev/pytest-asyncio/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.19.0...v0.20.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 03:04:18 +00:00
dependabot[bot] 66db0b7522 build(deps): Bump anyio[curio,trio] in /testing/plugins_integration
Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 3.6.1 to 3.6.2.
- [Release notes](https://github.com/agronholm/anyio/releases)
- [Changelog](https://github.com/agronholm/anyio/blob/3.6.2/docs/versionhistory.rst)
- [Commits](https://github.com/agronholm/anyio/compare/3.6.1...3.6.2)

---
updated-dependencies:
- dependency-name: anyio[curio,trio]
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-24 03:04:15 +00:00
Zac Hatfield-Dodds 3a68c08426 Use exceptiongroup for teardown errors 2022-10-23 15:45:50 -07:00
Itxaso Aizpurua 9e1804a6ee Add --log-disable CLI option (#10371)
Fixes #7431
2022-10-23 18:23:34 -03:00
74 changed files with 1488 additions and 875 deletions
+6
View File
@@ -9,3 +9,9 @@ updates:
allow:
- dependency-type: direct
- dependency-type: indirect
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
time: "03:00"
open-pull-requests-limit: 10
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: true
+12 -17
View File
@@ -23,34 +23,29 @@ jobs:
contents: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
persist-credentials: false
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v1.5
- name: Download Package
uses: actions/download-artifact@v3
with:
name: Packages
path: dist
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.pypi_token }}
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.7"
- name: Install tox
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install --upgrade tox
pip install --upgrade build tox
- name: Build package
run: |
python -m build
- name: Publish package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.pypi_token }}
- name: Publish GitHub release notes
env:
+2 -2
View File
@@ -27,12 +27,12 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: "3.8"
-12
View File
@@ -18,11 +18,6 @@ on:
env:
PYTEST_ADDOPTS: "--color=yes"
# Cancel running jobs for the same workflow and branch.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Set permissions at the job level.
permissions: {}
@@ -194,10 +189,3 @@ jobs:
fail_ci_if_error: true
files: ./coverage.xml
verbose: true
check-package:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build and Check Package
uses: hynek/build-and-inspect-python-package@v1.5
+3 -3
View File
@@ -20,12 +20,12 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v2
uses: actions/setup-python@v4
with:
python-version: 3.8
@@ -38,7 +38,7 @@ jobs:
run: python scripts/update-plugin-list.py
- name: Create Pull Request
uses: peter-evans/create-pull-request@2455e1596942c2902952003bbb574afbbe2ab2e6
uses: peter-evans/create-pull-request@2b011faafdcbc9ceb11414d64d0573f37c774b04
with:
commit-message: '[automated] Update plugin list'
author: 'pytest bot <pytestbot@users.noreply.github.com>'
+6 -6
View File
@@ -12,7 +12,7 @@ repos:
- id: blacken-docs
additional_dependencies: [black==20.8b1]
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.3.0
rev: v4.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
@@ -23,7 +23,7 @@ repos:
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://github.com/PyCQA/autoflake
rev: v1.7.6
rev: v2.0.0
hooks:
- id: autoflake
name: autoflake
@@ -31,7 +31,7 @@ repos:
language: python
files: \.py$
- repo: https://github.com/PyCQA/flake8
rev: 5.0.4
rev: 6.0.0
hooks:
- id: flake8
language_version: python3
@@ -39,7 +39,7 @@ repos:
- flake8-typing-imports==1.12.0
- flake8-docstrings==1.5.0
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.8.5
rev: v3.9.0
hooks:
- id: reorder-python-imports
args: ['--application-directories=.:src', --py37-plus]
@@ -49,7 +49,7 @@ repos:
- id: pyupgrade
args: [--py37-plus]
- repo: https://github.com/asottile/setup-cfg-fmt
rev: v2.1.0
rev: v2.2.0
hooks:
- id: setup-cfg-fmt
args: ["--max-py-version=3.11", "--include-version-classifiers"]
@@ -58,7 +58,7 @@ repos:
hooks:
- id: python-use-type-annotations
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.982
rev: v0.991
hooks:
- id: mypy
files: ^(src/|testing/)
+8 -8
View File
@@ -43,6 +43,7 @@ Ariel Pillemer
Armin Rigo
Aron Coyle
Aron Curzon
Ashish Kurmi
Aviral Verma
Aviv Palivoda
Babak Keyvani
@@ -57,6 +58,7 @@ Brian Maissy
Brian Okken
Brianna Laugher
Bruno Oliveira
Cal Jacobson
Cal Leeming
Carl Friedrich Bolz
Carlos Jenkins
@@ -183,8 +185,8 @@ Joseph Hunkeler
Josh Karpel
Joshua Bronson
Jurko Gospodnetić
Justyna Janczyszyn
Justice Ndou
Justyna Janczyszyn
Kale Kundert
Kamran Ahmad
Karl O. Pinc
@@ -223,6 +225,7 @@ Marcin Bachry
Marco Gorelli
Mark Abramowitz
Mark Dickinson
Marko Pacak
Markus Unterwaditzer
Martijn Faassen
Martin Altmayer
@@ -236,7 +239,6 @@ Matthias Hafner
Maxim Filipenko
Maximilian Cosmo Sitter
mbyt
Mickey Pashov
Michael Aquilina
Michael Birtwell
Michael Droettboom
@@ -245,6 +247,7 @@ Michael Krebs
Michael Seifert
Michal Wajszczuk
Michał Zięba
Mickey Pashov
Mihai Capotă
Mike Hoyle (hoylemd)
Mike Lundy
@@ -259,9 +262,9 @@ Niclas Olofsson
Nicolas Delaby
Nikolay Kondratyev
Nipunn Koorapati
Olga Matoula
Oleg Pidsadnyi
Oleg Sushchenko
Olga Matoula
Oliver Bestwalter
Omar Kohl
Omer Hadari
@@ -277,6 +280,7 @@ Paweł Adamczak
Pedro Algarvio
Petter Strandmark
Philipp Loose
Pierre Sassoulas
Pieter Mulder
Piotr Banaszkiewicz
Piotr Helm
@@ -286,10 +290,9 @@ Prashant Sharma
Pulkit Goyal
Punyashloka Biswal
Quentin Pradet
q0w
Ralf Schmitt
Ram Rachum
Ralph Giles
Ram Rachum
Ran Benita
Raphael Castaneda
Raphael Pierzina
@@ -312,7 +315,6 @@ Samuel Searles-Bryant
Samuele Pedroni
Sanket Duthade
Sankt Petersbug
Saravanan Padmanaban
Segev Finer
Serhii Mozghovyi
Seth Junot
@@ -344,7 +346,6 @@ Thomas Grainger
Thomas Hisch
Tim Hoffmann
Tim Strazny
TJ Bruno
Tobias Diez
Tom Dalton
Tom Viner
@@ -376,7 +377,6 @@ Xixi Zhao
Xuan Luong
Xuecong Liao
Yoav Caspi
Yuliang Shao
Yusuke Kadowaki
Yuval Shimon
Zac Hatfield-Dodds
+1
View File
@@ -0,0 +1 @@
If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last.
+1
View File
@@ -0,0 +1 @@
Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12.
+1
View File
@@ -0,0 +1 @@
If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location.
+1
View File
@@ -0,0 +1 @@
Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir <rootdir>` on Windows.
+1
View File
@@ -0,0 +1 @@
Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods.
+2
View File
@@ -0,0 +1,2 @@
The full output of a test is no longer truncated if the truncation message would be longer than
the hidden text. The line number shown has also been fixed.
+1
View File
@@ -0,0 +1 @@
``--log-disable`` CLI option added to disable individual loggers.
+2
View File
@@ -0,0 +1,2 @@
Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept.
The default behavior has changed to keep only directories for failed tests, equivalent to `tmp_path_retention_policy="failed"`.
-2
View File
@@ -6,8 +6,6 @@ Release announcements
:maxdepth: 2
release-7.2.2
release-7.2.1
release-7.2.0
release-7.1.3
release-7.1.2
-25
View File
@@ -1,25 +0,0 @@
pytest-7.2.1
=======================================
pytest 7.2.1 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Anthony Sottile
* Bruno Oliveira
* Daniel Valenzuela
* Kadino
* Prerak Patel
* Ronny Pfannschmidt
* Santiago Castro
* s-padmanaban
Happy testing,
The pytest Development Team
-25
View File
@@ -1,25 +0,0 @@
pytest-7.2.2
=======================================
pytest 7.2.2 has just been released to PyPI.
This is a bug-fix release, being a drop-in replacement. To upgrade::
pip install --upgrade pytest
The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
Thanks to all of the contributors to this release:
* Bruno Oliveira
* Garvit Shubham
* Mahesh Vashishtha
* Ramsey
* Ronny Pfannschmidt
* Teejay
* q0w
* vin01
Happy testing,
The pytest Development Team
+2 -2
View File
@@ -22,7 +22,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
cachedir: .pytest_cache
rootdir: /home/sweet/project
collected 0 items
cache -- .../_pytest/cacheprovider.py:509
cache -- .../_pytest/cacheprovider.py:510
Return a cache object that can persist state between testing sessions.
cache.get(key, default)
@@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
For more details: :ref:`doctest_namespace`.
pytestconfig [session scope] -- .../_pytest/fixtures.py:1356
pytestconfig [session scope] -- .../_pytest/fixtures.py:1351
Session-scoped fixture that returns the session's :class:`pytest.Config`
object.
-57
View File
@@ -28,63 +28,6 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start
pytest 7.2.2 (2023-03-03)
=========================
Bug Fixes
---------
- `#10533 <https://github.com/pytest-dev/pytest/issues/10533>`_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`.
- `#10592 <https://github.com/pytest-dev/pytest/issues/10592>`_: Fixed crash if `--cache-show` and `--help` are passed at the same time.
- `#10597 <https://github.com/pytest-dev/pytest/issues/10597>`_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage.
- `#10626 <https://github.com/pytest-dev/pytest/issues/10626>`_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time.
- `#10660 <https://github.com/pytest-dev/pytest/issues/10660>`_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow
:code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'.
Improved Documentation
----------------------
- `#10690 <https://github.com/pytest-dev/pytest/issues/10690>`_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation.
- `#10721 <https://github.com/pytest-dev/pytest/issues/10721>`_: Fixed entry-points declaration in the documentation example using Hatch.
- `#10753 <https://github.com/pytest-dev/pytest/issues/10753>`_: Changed wording of the module level skip to be very explicit
about not collecting tests and not executing the rest of the module.
pytest 7.2.1 (2023-01-13)
=========================
Bug Fixes
---------
- `#10452 <https://github.com/pytest-dev/pytest/issues/10452>`_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12.
- `#10457 <https://github.com/pytest-dev/pytest/issues/10457>`_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location.
- `#10506 <https://github.com/pytest-dev/pytest/issues/10506>`_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir <rootdir>` on Windows.
- `#10607 <https://github.com/pytest-dev/pytest/issues/10607>`_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel.
- `#10641 <https://github.com/pytest-dev/pytest/issues/10641>`_: Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache.
pytest 7.2.0 (2022-10-23)
=========================
@@ -17,7 +17,7 @@ def b(a, order):
@pytest.fixture
def c(b, order):
def c(a, b, order):
order.append("c")
+4 -4
View File
@@ -504,9 +504,9 @@ Running it results in some skips if we don't have all the python interpreters in
. $ pytest -rs -q multipython.py
sssssssssssssssssssssssssss [100%]
========================= short test summary info ==========================
SKIPPED [9] multipython.py:69: 'python3.5' not found
SKIPPED [9] multipython.py:69: 'python3.6' not found
SKIPPED [9] multipython.py:69: 'python3.7' not found
SKIPPED [9] multipython.py:29: 'python3.5' not found
SKIPPED [9] multipython.py:29: 'python3.6' not found
SKIPPED [9] multipython.py:29: 'python3.7' not found
27 skipped in 0.12s
Indirect parametrization of optional implementations/imports
@@ -574,7 +574,7 @@ If you run this with reporting for skips enabled:
test_module.py .s [100%]
========================= short test summary info ==========================
SKIPPED [1] test_module.py:3: could not import 'opt2': No module named 'opt2'
SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2'
======================= 1 passed, 1 skipped in 0.12s =======================
You'll see that we don't have an ``opt2`` module and thus the second test run
+9 -8
View File
@@ -895,6 +895,8 @@ here is a little example implemented via a local plugin:
import pytest
phase_report_key = StashKey[Dict[str, CollectReport]]()
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
@@ -902,10 +904,9 @@ here is a little example implemented via a local plugin:
outcome = yield
rep = outcome.get_result()
# set a report attribute for each phase of a call, which can
# store test results for each phase of a call, which can
# be "setup", "call", "teardown"
setattr(item, "rep_" + rep.when, rep)
item.stash.setdefault(phase_report_key, {})[rep.when] = rep
@pytest.fixture
@@ -913,11 +914,11 @@ here is a little example implemented via a local plugin:
yield
# request.node is an "item" because we use the default
# "function" scope
if request.node.rep_setup.failed:
print("setting up a test failed!", request.node.nodeid)
elif request.node.rep_setup.passed:
if request.node.rep_call.failed:
print("executing test failed", request.node.nodeid)
report = request.node.stash[phase_report_key]
if report["setup"].failed:
print("setting up a test failed or skipped", request.node.nodeid)
elif ("call" not in report) or report["call"].failed:
print("executing test failed or skipped", request.node.nodeid)
if you then have failing tests:
+2 -2
View File
@@ -50,8 +50,8 @@ Conventions for Python test discovery
* In those directories, search for ``test_*.py`` or ``*_test.py`` files, imported by their `test package name`_.
* From those files, collect test items:
* ``test`` prefixed test functions or methods outside of class
* ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method)
* ``test`` prefixed test functions or methods outside of class.
* ``test`` prefixed test functions or methods inside ``Test`` prefixed test classes (without an ``__init__`` method). Methods decorated with ``@staticmethod`` and ``@classmethods`` are also considered.
For examples of how to customize your test discovery :doc:`/example/pythoncollection`.
+3 -3
View File
@@ -16,7 +16,7 @@ import process can be controlled through the ``--import-mode`` command-line flag
these values:
* ``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:`__import__ <__import__>` builtin.
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.
@@ -24,7 +24,7 @@ these values:
This is the classic mechanism, dating back from the time Python 2 was still supported.
* ``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>`.
there, and imported with ``__import__``.
This better allows to run test modules against installed versions of a package even if the
package under test has the same import root. For example:
@@ -43,7 +43,7 @@ these values:
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.
* ``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``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`.
For this reason this doesn't require test module names to be unique.
+1 -1
View File
@@ -22,7 +22,7 @@ Install ``pytest``
.. code-block:: bash
$ pytest --version
pytest 7.2.2
pytest 7.2.0
.. _`simpletest`:
+4 -30
View File
@@ -109,18 +109,6 @@ When a warning matches more than one option in the list, the action for the last
is performed.
.. note::
The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are
similar in structure, but each configuration option interprets its filter
differently. For example, *message* in ``filterwarnings`` is a string containing a
regular expression that the start of the warning message must match,
case-insensitively, while *message* in ``-W`` is a literal string that the start of
the warning message must contain (case-insensitively), ignoring any whitespace at
the start or end of message. Consult the `warning filter`_ documentation for more
details.
.. _`filterwarnings`:
``@pytest.mark.filterwarnings``
@@ -282,34 +270,20 @@ which works in a similar manner to :ref:`raises <assertraises>` (except that
warnings.warn("my warning", UserWarning)
The test will fail if the warning in question is not raised. Use the keyword
argument ``match`` to assert that the warning matches a text or regex.
To match a literal string that may contain regular expression metacharacters like ``(`` or ``.``, the pattern can
first be escaped with ``re.escape``.
argument ``match`` to assert that the warning matches a text or regex::
Some examples:
.. code-block:: pycon
>>> with warns(UserWarning, match="must be 0 or None"):
>>> with warns(UserWarning, match='must be 0 or None'):
... warnings.warn("value must be 0 or None", UserWarning)
...
>>> with warns(UserWarning, match=r"must be \d+$"):
>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("value must be 42", UserWarning)
...
>>> with warns(UserWarning, match=r"must be \d+$"):
>>> with warns(UserWarning, match=r'must be \d+$'):
... warnings.warn("this is not here", UserWarning)
...
Traceback (most recent call last):
...
Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted...
>>> with warns(UserWarning, match=re.escape("issue with foo() func")):
... warnings.warn("issue with foo() func")
...
You can also call :func:`pytest.warns` on a function or code string:
.. code-block:: python
+7
View File
@@ -55,6 +55,13 @@ These options can also be customized through ``pytest.ini`` file:
log_format = %(asctime)s %(levelname)s %(message)s
log_date_format = %Y-%m-%d %H:%M:%S
Specific loggers can be disabled via ``--log-disable={logger_name}``.
This argument can be passed multiple times:
.. code-block:: bash
pytest --log-disable=main --log-disable=testing
Further it is possible to disable reporting of captured content (stdout,
stderr and logs) on failed tests completely with:
+6 -3
View File
@@ -131,10 +131,13 @@ 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
``NUM`` will be incremented with each test run. Moreover, entries older
than 3 temporary directories will be removed.
``NUM`` will be incremented with each test run.
By default, only the directories of failed tests will be kept.
Also only the last 3 directries will remain at most.
This behavior can be configured with :confval:`tmp_path_retention_count` and
:confval:`tmp_path_retention_policy`.
The number of entries currently cannot be changed, but using the ``--basetemp``
Using the ``--basetemp``
option will remove the directory before every run, effectively meaning the temporary directories
of only the most recent run will be kept.
+7 -2
View File
@@ -167,8 +167,13 @@ it in your ``pyproject.toml`` file.
"Framework :: Pytest",
]
[project.entry-points.pytest11]
myproject = "myproject.pluginmodule"
[tool.setuptools]
packages = ["myproject"]
[project.entry_points]
pytest11 = [
"myproject = myproject.pluginmodule",
]
If a package is installed this way, ``pytest`` will load
``myproject.pluginmodule`` as a plugin which can define
+1 -1
View File
@@ -2,7 +2,7 @@
.. sidebar:: Next Open Trainings
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 7th to 9th 2023 (3 day in-depth training), Remote
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany
Also see :doc:`previous talks and blogposts <talks>`.
+1 -1
View File
@@ -335,7 +335,7 @@ For example:
.. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py
If we map out what depends on what, we get something that looks like this:
If we map out what depends on what, we get something that look like this:
.. image:: /example/fixtures/test_fixtures_order_dependencies.*
:align: center
File diff suppressed because it is too large Load Diff
+34 -8
View File
@@ -1047,14 +1047,6 @@ Environment Variables
Environment variables that can be used to change pytest's behavior.
.. envvar:: CI
When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to ``BUILD_NUMBER`` variable.
.. envvar:: BUILD_NUMBER
When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to CI variable.
.. envvar:: PYTEST_ADDOPTS
This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given
@@ -1731,6 +1723,40 @@ passed multiple times. The expected format is ``name=value``. For example::
directories when executing from the root directory.
.. confval:: tmp_path_retention_count
How many sessions should we keep the `tmp_path` directories,
according to `tmp_path_retention_policy`.
.. code-block:: ini
[pytest]
tmp_path_retention_count = 3
Default: 3
.. confval:: tmp_path_retention_policy
Controls which directories created by the `tmp_path` fixture are kept around,
based on test outcome.
* `all`: retains directories for all tests, regardless of the outcome.
* `failed`: retains directories only for tests with outcome `error` or `failed`.
* `none`: directories are always removed after each test ends, regardless of the outcome.
.. code-block:: ini
[pytest]
tmp_path_retention_policy = "all"
Default: failed
.. confval:: usefixtures
List of fixtures that will be applied to all test functions; this is semantically the same to apply
-5
View File
@@ -114,8 +114,3 @@ template = "changelog/_template.rst"
[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"
+4 -2
View File
@@ -78,7 +78,7 @@ def iter_plugins():
requires = "N/A"
if info["requires_dist"]:
for requirement in info["requires_dist"]:
if requirement == "pytest" or "pytest " in requirement:
if re.match(r"pytest(?![-.\w])", requirement):
requires = requirement
break
releases = response.json()["releases"]
@@ -90,7 +90,9 @@ def iter_plugins():
last_release = release_date.strftime("%b %d, %Y")
break
name = f':pypi:`{info["name"]}`'
summary = escape_rst(info["summary"].replace("\n", ""))
summary = ""
if info["summary"]:
summary = escape_rst(info["summary"].replace("\n", ""))
yield {
"name": name,
"summary": summary.strip(),
-1
View File
@@ -96,7 +96,6 @@ mypy_path = src
check_untyped_defs = True
disallow_any_generics = True
ignore_missing_imports = True
no_implicit_optional = True
show_error_codes = True
strict_equality = True
warn_redundant_casts = True
+3 -3
View File
@@ -78,15 +78,15 @@ class FastFilesCompleter:
def __call__(self, prefix: str, **kwargs: Any) -> List[str]:
# Only called on non option completions.
if os.path.sep in prefix[1:]:
prefix_dir = len(os.path.dirname(prefix) + os.path.sep)
if os.sep in prefix[1:]:
prefix_dir = len(os.path.dirname(prefix) + os.sep)
else:
prefix_dir = 0
completion = []
globbed = []
if "*" not in prefix and "?" not in prefix:
# We are on unix, otherwise no bash.
if not prefix or prefix[-1] == os.path.sep:
if not prefix or prefix[-1] == os.sep:
globbed.extend(glob(prefix + ".*"))
prefix += "*"
globbed.extend(glob(prefix))
+8 -7
View File
@@ -24,6 +24,7 @@ from stat import S_ISLNK
from stat import S_ISREG
from typing import Any
from typing import Callable
from typing import cast
from typing import overload
from typing import TYPE_CHECKING
@@ -146,7 +147,7 @@ class Visitor:
self.fil = fil
self.ignore = ignore
self.breadthfirst = bf
self.optsort = sort and sorted or (lambda x: x)
self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x)
def gen(self, path):
try:
@@ -224,7 +225,7 @@ class Stat:
raise NotImplementedError("XXX win32")
import pwd
entry = error.checked_call(pwd.getpwuid, self.uid)
entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined]
return entry[0]
@property
@@ -234,7 +235,7 @@ class Stat:
raise NotImplementedError("XXX win32")
import grp
entry = error.checked_call(grp.getgrgid, self.gid)
entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined]
return entry[0]
def isdir(self):
@@ -252,7 +253,7 @@ def getuserid(user):
import pwd
if not isinstance(user, int):
user = pwd.getpwnam(user)[2]
user = pwd.getpwnam(user)[2] # type:ignore[attr-defined]
return user
@@ -260,7 +261,7 @@ def getgroupid(group):
import grp
if not isinstance(group, int):
group = grp.getgrnam(group)[2]
group = grp.getgrnam(group)[2] # type:ignore[attr-defined]
return group
@@ -795,7 +796,7 @@ class LocalPath:
kw = {"exists": 1}
return Checkers(self)._evaluate(kw)
_patternchars = set("*?[" + os.path.sep)
_patternchars = set("*?[" + os.sep)
def listdir(self, fil=None, sort=None):
"""List directory contents, possibly filter by the given fil func
@@ -1127,7 +1128,7 @@ class LocalPath:
modfile = modfile[:-1]
elif modfile.endswith("$py.class"):
modfile = modfile[:-9] + ".py"
if modfile.endswith(os.path.sep + "__init__.py"):
if modfile.endswith(os.sep + "__init__.py"):
if self.basename != "__init__.py":
modfile = modfile[:-12]
try:
+1 -1
View File
@@ -180,7 +180,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
for initial_path in self.session._initialpaths:
# Make something as c:/projects/my_project/path.py ->
# ['c:', 'projects', 'my_project', 'path.py']
parts = str(initial_path).split(os.path.sep)
parts = str(initial_path).split(os.sep)
# add 'path' to basenames to be checked.
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
+46 -25
View File
@@ -38,9 +38,9 @@ def _truncate_explanation(
"""Truncate given list of strings that makes up the assertion explanation.
Truncates to either 8 lines, or 640 characters - whichever the input reaches
first. The remaining lines will be replaced by a usage message.
first, taking the truncation explanation into account. The remaining lines
will be replaced by a usage message.
"""
if max_lines is None:
max_lines = DEFAULT_MAX_LINES
if max_chars is None:
@@ -48,35 +48,56 @@ def _truncate_explanation(
# Check if truncation required
input_char_count = len("".join(input_lines))
if len(input_lines) <= max_lines and input_char_count <= max_chars:
# The length of the truncation explanation depends on the number of lines
# removed but is at least 68 characters:
# The real value is
# 64 (for the base message:
# '...\n...Full output truncated (1 line hidden), use '-vv' to show")'
# )
# + 1 (for plural)
# + int(math.log10(len(input_lines) - max_lines)) (number of hidden line, at least 1)
# + 3 for the '...' added to the truncated line
# But if there's more than 100 lines it's very likely that we're going to
# truncate, so we don't need the exact value using log10.
tolerable_max_chars = (
max_chars + 70 # 64 + 1 (for plural) + 2 (for '99') + 3 for '...'
)
# The truncation explanation add two lines to the output
tolerable_max_lines = max_lines + 2
if (
len(input_lines) <= tolerable_max_lines
and input_char_count <= tolerable_max_chars
):
return input_lines
# Truncate first to max_lines, and then truncate to max_chars if max_chars
# is exceeded.
# Truncate first to max_lines, and then truncate to max_chars if necessary
truncated_explanation = input_lines[:max_lines]
truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
# Add ellipsis to final line
truncated_explanation[-1] = truncated_explanation[-1] + "..."
# Append useful message to explanation
truncated_line_count = len(input_lines) - len(truncated_explanation)
truncated_line_count += 1 # Account for the part-truncated final line
msg = "...Full output truncated"
if truncated_line_count == 1:
msg += f" ({truncated_line_count} line hidden)"
truncated_char = True
# We reevaluate the need to truncate chars following removal of some lines
if len("".join(truncated_explanation)) > tolerable_max_chars:
truncated_explanation = _truncate_by_char_count(
truncated_explanation, max_chars
)
else:
msg += f" ({truncated_line_count} lines hidden)"
msg += f", {USAGE_MSG}"
truncated_explanation.extend(["", str(msg)])
return truncated_explanation
truncated_char = False
truncated_line_count = len(input_lines) - len(truncated_explanation)
if truncated_explanation[-1]:
# Add ellipsis and take into account part-truncated final line
truncated_explanation[-1] = truncated_explanation[-1] + "..."
if truncated_char:
# It's possible that we did not remove any char from this line
truncated_line_count += 1
else:
# Add proper ellipsis when we were able to fit a full line exactly
truncated_explanation[-1] = "..."
return truncated_explanation + [
"",
f"...Full output truncated ({truncated_line_count} line"
f"{'' if truncated_line_count == 1 else 's'} hidden), {USAGE_MSG}",
]
def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]:
# Check if truncation required
if len("".join(input_lines)) <= max_chars:
return input_lines
# Find point at which input length exceeds total allowed length
iterated_char_count = 0
for iterated_index, input_line in enumerate(input_lines):
+2 -1
View File
@@ -32,6 +32,7 @@ from _pytest.python import Module
from _pytest.python import Package
from _pytest.reports import TestReport
README_CONTENT = """\
# pytest cache directory #
@@ -491,7 +492,7 @@ def pytest_addoption(parser: Parser) -> None:
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
if config.option.cacheshow and not config.option.help:
if config.option.cacheshow:
from _pytest.main import wrap_session
return wrap_session(config, cacheshow)
+2 -4
View File
@@ -998,8 +998,6 @@ class Config:
self.hook.pytest_addoption.call_historic(
kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager)
)
self.args_source = Config.ArgsSource.ARGS
self.args: List[str] = []
if TYPE_CHECKING:
from _pytest.cacheprovider import Cache
@@ -1339,8 +1337,8 @@ class Config:
def parse(self, args: List[str], addopts: bool = True) -> None:
# Parse given cmdline arguments into this config object.
assert (
self.args == []
assert not hasattr(
self, "args"
), "can only parse cmdline args at most once per Config object"
self.hook.pytest_addhooks.call_historic(
kwargs=dict(pluginmanager=self.pluginmanager)
+6 -4
View File
@@ -738,7 +738,7 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No
# -------------------------------------------------------------------------
def pytest_report_header(
def pytest_report_header( # type:ignore[empty-body]
config: "Config", start_path: Path, startdir: "LEGACY_PATH"
) -> Union[str, List[str]]:
"""Return a string or list of strings to be displayed as header info for terminal reporting.
@@ -767,7 +767,7 @@ def pytest_report_header(
"""
def pytest_report_collectionfinish(
def pytest_report_collectionfinish( # type:ignore[empty-body]
config: "Config",
start_path: Path,
startdir: "LEGACY_PATH",
@@ -800,7 +800,7 @@ def pytest_report_collectionfinish(
@hookspec(firstresult=True)
def pytest_report_teststatus(
def pytest_report_teststatus( # type:ignore[empty-body]
report: Union["CollectReport", "TestReport"], config: "Config"
) -> Tuple[str, str, Union[str, Mapping[str, bool]]]:
"""Return result-category, shortletter and verbose word for status
@@ -880,7 +880,9 @@ def pytest_warning_recorded(
# -------------------------------------------------------------------------
def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
def pytest_markeval_namespace( # type:ignore[empty-body]
config: "Config",
) -> Dict[str, Any]:
"""Called when constructing the globals dictionary used for
evaluating string conditions in xfail/skipif markers.
+2 -2
View File
@@ -645,8 +645,8 @@ class LogXML:
def pytest_sessionfinish(self) -> None:
dirname = os.path.dirname(os.path.abspath(self.logfile))
# exist_ok avoids filesystem race conditions between checking path existence and requesting creation
os.makedirs(dirname, exist_ok=True)
if not os.path.isdir(dirname):
os.makedirs(dirname)
with open(self.logfile, "w", encoding="utf-8") as logfile:
suite_stop_time = timing.time()
+16
View File
@@ -297,6 +297,13 @@ def pytest_addoption(parser: Parser) -> None:
default=None,
help="Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.",
)
group.addoption(
"--log-disable",
action="append",
default=[],
dest="logger_disable",
help="Disable a logger by name. Can be passed multipe times.",
)
_HandlerType = TypeVar("_HandlerType", bound=logging.Handler)
@@ -594,6 +601,15 @@ class LoggingPlugin:
get_option_ini(config, "log_auto_indent"),
)
self.log_cli_handler.setFormatter(log_cli_formatter)
self._disable_loggers(loggers_to_disable=config.option.logger_disable)
def _disable_loggers(self, loggers_to_disable: List[str]) -> None:
if not loggers_to_disable:
return
for name in loggers_to_disable:
logger = logging.getLogger(name)
logger.disabled = True
def _create_formatter(self, log_format, log_date_format, auto_indent):
# Color option doesn't exist if terminal plugin is disabled.
+2 -6
View File
@@ -157,12 +157,8 @@ def skip(
The message to show the user as reason for the skip.
:param allow_module_level:
Allows this function to be called at module level.
Raising the skip exception at module level will stop
the execution of the module and prevent the collection of all tests in the module,
even those defined before the `skip` call.
Defaults to False.
Allows this function to be called at module level, skipping the rest
of the module. Defaults to False.
:param msg:
Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.
+20 -7
View File
@@ -335,15 +335,26 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]:
yield path
def cleanup_dead_symlink(root: Path):
for left_dir in root.iterdir():
if left_dir.is_symlink():
if not left_dir.resolve().exists():
left_dir.unlink()
def cleanup_numbered_dir(
root: Path, prefix: str, keep: int, consider_lock_dead_if_created_before: float
) -> None:
"""Cleanup for lock driven numbered directories."""
if not root.exists():
return
for path in cleanup_candidates(root, prefix, keep):
try_cleanup(path, consider_lock_dead_if_created_before)
for path in root.glob("garbage-*"):
try_cleanup(path, consider_lock_dead_if_created_before)
cleanup_dead_symlink(root)
def make_numbered_dir_with_cleanup(
root: Path,
@@ -357,8 +368,10 @@ def make_numbered_dir_with_cleanup(
for i in range(10):
try:
p = make_numbered_dir(root, prefix, mode)
lock_path = create_cleanup_lock(p)
register_cleanup_lock_removal(lock_path)
# Only lock the current dir when keep is not 0
if keep != 0:
lock_path = create_cleanup_lock(p)
register_cleanup_lock_removal(lock_path)
except Exception as exc:
e = exc
else:
@@ -464,14 +477,14 @@ def import_path(
* `mode == ImportMode.prepend`: the directory containing the module (or package, taking
`__init__.py` files into account) will be put at the *start* of `sys.path` before
being imported with `importlib.import_module`.
being imported with `__import__.
* `mode == ImportMode.append`: same as `prepend`, but the directory will be appended
to the end of `sys.path`, if not already in `sys.path`.
* `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib`
to import the module, which avoids having to muck with `sys.path` at all. It effectively
allows having same-named test modules in different places.
to import the module, which avoids having to use `__import__` and muck with `sys.path`
at all. It effectively allows having same-named test modules in different places.
:param root:
Used as an anchor when mode == ImportMode.importlib to obtain
@@ -544,8 +557,8 @@ def import_path(
if module_file.endswith((".pyc", ".pyo")):
module_file = module_file[:-1]
if module_file.endswith(os.path.sep + "__init__.py"):
module_file = module_file[: -(len(os.path.sep + "__init__.py"))]
if module_file.endswith(os.sep + "__init__.py"):
module_file = module_file[: -(len(os.sep + "__init__.py"))]
try:
is_same = _is_same(str(path), module_file)
+5 -5
View File
@@ -403,8 +403,8 @@ class PyCollector(PyobjMixin, nodes.Collector):
def istestfunction(self, obj: object, name: str) -> bool:
if self.funcnamefilter(name) or self.isnosetest(obj):
if isinstance(obj, staticmethod):
# staticmethods need to be unwrapped.
if isinstance(obj, (staticmethod, classmethod)):
# staticmethods and classmethods need to be unwrapped.
obj = safe_getattr(obj, "__func__", False)
return callable(obj) and fixtures.getfixturemarker(obj) is None
else:
@@ -848,7 +848,7 @@ class Class(PyCollector):
other fixtures (#517).
"""
setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",))
teardown_class = getattr(self.obj, "teardown_class", None)
if setup_class is None and teardown_class is None:
return
@@ -885,12 +885,12 @@ class Class(PyCollector):
emit_nose_setup_warning = True
setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
teardown_name = "teardown_method"
teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
teardown_method = getattr(self.obj, teardown_name, None)
emit_nose_teardown_warning = False
if teardown_method is None and has_nose:
teardown_name = "teardown"
emit_nose_teardown_warning = True
teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
teardown_method = getattr(self.obj, teardown_name, None)
if setup_method is None and teardown_method is None:
return
+7 -13
View File
@@ -8,7 +8,7 @@ from types import TracebackType
from typing import Any
from typing import Callable
from typing import cast
from typing import ContextManager
from typing import Generic
from typing import List
from typing import Mapping
from typing import Optional
@@ -269,16 +269,10 @@ class ApproxMapping(ApproxBase):
max_abs_diff = max(
max_abs_diff, abs(approx_value.expected - other_value)
)
if approx_value.expected == 0.0:
max_rel_diff = math.inf
else:
max_rel_diff = max(
max_rel_diff,
abs(
(approx_value.expected - other_value)
/ approx_value.expected
),
)
max_rel_diff = max(
max_rel_diff,
abs((approx_value.expected - other_value) / approx_value.expected),
)
different_ids.append(approx_key)
message_data = [
@@ -807,7 +801,7 @@ def raises( # noqa: F811
r"""Assert that a code block/function call raises an exception.
:param typing.Type[E] | typing.Tuple[typing.Type[E], ...] expected_exception:
The excpected exception type, or a tuple if one of multiple possible
The expected exception type, or a tuple if one of multiple possible
exception types are excepted.
:kwparam str | typing.Pattern[str] | None match:
If specified, a string containing a regular expression,
@@ -963,7 +957,7 @@ raises.Exception = fail.Exception # type: ignore
@final
class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]):
class RaisesContext(Generic[E]):
def __init__(
self,
expected_exception: Union[Type[E], Tuple[Type[E], ...]],
+17 -7
View File
@@ -35,6 +35,9 @@ from _pytest.outcomes import OutcomeException
from _pytest.outcomes import Skipped
from _pytest.outcomes import TEST_OUTCOME
if sys.version_info[:2] < (3, 11):
from exceptiongroup import BaseExceptionGroup
if TYPE_CHECKING:
from typing_extensions import Literal
@@ -512,22 +515,29 @@ class SetupState:
stack is torn down.
"""
needed_collectors = nextitem and nextitem.listchain() or []
exc = None
exceptions: List[BaseException] = []
while self.stack:
if list(self.stack.keys()) == needed_collectors[: len(self.stack)]:
break
node, (finalizers, _) = self.stack.popitem()
these_exceptions = []
while finalizers:
fin = finalizers.pop()
try:
fin()
except TEST_OUTCOME as e:
# XXX Only first exception will be seen by user,
# ideally all should be reported.
if exc is None:
exc = e
if exc:
raise exc
these_exceptions.append(e)
if len(these_exceptions) == 1:
exceptions.extend(these_exceptions)
elif these_exceptions:
msg = f"errors while tearing down {node!r}"
exceptions.append(BaseExceptionGroup(msg, these_exceptions[::-1]))
if len(exceptions) == 1:
raise exceptions[0]
elif exceptions:
raise BaseExceptionGroup("errors during test teardown", exceptions[::-1])
if nextitem is None:
assert not self.stack
-8
View File
@@ -48,10 +48,6 @@ def pytest_configure(config: Config) -> None:
def pytest_sessionfinish(session: Session) -> None:
if not session.config.getoption("stepwise"):
assert session.config.cache is not None
if hasattr(session.config, "workerinput"):
# Do not update cache if this process is a xdist worker to prevent
# race conditions (#10641).
return
# Clear the list of failing tests if the plugin is not active.
session.config.cache.set(STEPWISE_CACHE_DIR, [])
@@ -123,8 +119,4 @@ class StepwisePlugin:
return None
def pytest_sessionfinish(self) -> None:
if hasattr(self.config, "workerinput"):
# Do not update cache if this process is a xdist worker to prevent
# race conditions (#10641).
return
self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed)
+116 -5
View File
@@ -4,21 +4,42 @@ import re
import sys
import tempfile
from pathlib import Path
from shutil import rmtree
from typing import Dict
from typing import Generator
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union
from _pytest.nodes import Item
from _pytest.reports import CollectReport
from _pytest.stash import StashKey
if TYPE_CHECKING:
from typing_extensions import Literal
RetentionType = Literal["all", "failed", "none"]
import attr
from _pytest.config.argparsing import Parser
from .pathlib import LOCK_TIMEOUT
from .pathlib import make_numbered_dir
from .pathlib import make_numbered_dir_with_cleanup
from .pathlib import rm_rf
from .pathlib import cleanup_dead_symlink
from _pytest.compat import final
from _pytest.config import Config
from _pytest.config import ExitCode
from _pytest.config import hookimpl
from _pytest.deprecated import check_ispytest
from _pytest.fixtures import fixture
from _pytest.fixtures import FixtureRequest
from _pytest.monkeypatch import MonkeyPatch
tmppath_result_key = StashKey[Dict[str, bool]]()
@final
@attr.s(init=False)
@@ -31,10 +52,14 @@ class TempPathFactory:
_given_basetemp = attr.ib(type=Optional[Path])
_trace = attr.ib()
_basetemp = attr.ib(type=Optional[Path])
_retention_count = attr.ib(type=int)
_retention_policy = attr.ib(type="RetentionType")
def __init__(
self,
given_basetemp: Optional[Path],
retention_count: int,
retention_policy: "RetentionType",
trace,
basetemp: Optional[Path] = None,
*,
@@ -49,6 +74,8 @@ class TempPathFactory:
# Path.absolute() exists, but it is not public (see https://bugs.python.org/issue25012).
self._given_basetemp = Path(os.path.abspath(str(given_basetemp)))
self._trace = trace
self._retention_count = retention_count
self._retention_policy = retention_policy
self._basetemp = basetemp
@classmethod
@@ -63,9 +90,23 @@ class TempPathFactory:
:meta private:
"""
check_ispytest(_ispytest)
count = int(config.getini("tmp_path_retention_count"))
if count < 0:
raise ValueError(
f"tmp_path_retention_count must be >= 0. Current input: {count}."
)
policy = config.getini("tmp_path_retention_policy")
if policy not in ("all", "failed", "none"):
raise ValueError(
f"tmp_path_retention_policy must be either all, failed, none. Current intput: {policy}."
)
return cls(
given_basetemp=config.option.basetemp,
trace=config.trace.get("tmpdir"),
retention_count=count,
retention_policy=policy,
_ispytest=True,
)
@@ -146,10 +187,13 @@ class TempPathFactory:
)
if (rootdir_stat.st_mode & 0o077) != 0:
os.chmod(rootdir, rootdir_stat.st_mode & ~0o077)
keep = self._retention_count
if self._retention_policy == "none":
keep = 0
basetemp = make_numbered_dir_with_cleanup(
prefix="pytest-",
root=rootdir,
keep=3,
keep=keep,
lock_timeout=LOCK_TIMEOUT,
mode=0o700,
)
@@ -184,6 +228,21 @@ def pytest_configure(config: Config) -> None:
mp.setattr(config, "_tmp_path_factory", _tmp_path_factory, raising=False)
def pytest_addoption(parser: Parser) -> None:
parser.addini(
"tmp_path_retention_count",
help="How many sessions should we keep the `tmp_path` directories, according to `tmp_path_retention_policy`.",
default=3,
)
parser.addini(
"tmp_path_retention_policy",
help="Controls which directories created by the `tmp_path` fixture are kept around, based on test outcome. "
"(all/failed/none)",
default="failed",
)
@fixture(scope="session")
def tmp_path_factory(request: FixtureRequest) -> TempPathFactory:
"""Return a :class:`pytest.TempPathFactory` instance for the test session."""
@@ -200,17 +259,69 @@ def _mk_tmp(request: FixtureRequest, factory: TempPathFactory) -> Path:
@fixture
def tmp_path(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> Path:
def tmp_path(
request: FixtureRequest, tmp_path_factory: TempPathFactory
) -> Generator[Path, None, None]:
"""Return a temporary directory path object which is unique to each test
function invocation, created as a sub directory of the base temporary
directory.
By default, a new base temporary directory is created each test session,
and old bases are removed after 3 sessions, to aid in debugging. If
``--basetemp`` is used then it is cleared each session. See :ref:`base
and only the base of failed session is kept. Also it only keeps the last 3 bases
at most. This can be configured with :confval:`tmp_path_retention_count` and
:confval:`tmp_path_retention_policy`.
If ``--basetemp`` is used then it is cleared each session. See :ref:`base
temporary directory`.
The returned object is a :class:`pathlib.Path` object.
"""
return _mk_tmp(request, tmp_path_factory)
path = _mk_tmp(request, tmp_path_factory)
yield path
# Remove the tmpdir if the policy is "failed" and the test passed.
tmp_path_factory: TempPathFactory = request.session.config._tmp_path_factory # type: ignore
policy = tmp_path_factory._retention_policy
result_dict = request.node.stash[tmppath_result_key]
if policy == "failed" and result_dict.get("call", True):
# We do a "best effort" to remove files, but it might not be possible due to some leaked resource,
# permissions, etc, in which case we ignore it.
rmtree(path, ignore_errors=True)
del request.node.stash[tmppath_result_key]
# remove dead symlink
basetemp = tmp_path_factory._basetemp
if basetemp is None:
return
cleanup_dead_symlink(basetemp)
def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]):
"""After each session, remove base directory if all the tests passed,
the policy is "failed", and the basetemp is not specified by a user.
"""
tmp_path_factory: TempPathFactory = session.config._tmp_path_factory
if tmp_path_factory._basetemp is None:
return
policy = tmp_path_factory._retention_policy
if (
exitstatus == 0
and policy == "failed"
and tmp_path_factory._given_basetemp is None
):
passed_dir = tmp_path_factory._basetemp
if passed_dir.exists():
# We do a "best effort" to remove files, but it might not be possible due to some leaked resource,
# permissions, etc, in which case we ignore it.
rmtree(passed_dir, ignore_errors=True)
@hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item: Item, call):
outcome = yield
result: CollectReport = outcome.get_result()
empty: Dict[str, bool] = {}
item.stash.setdefault(tmppath_result_key, empty)[result.when] = result.passed
+3 -3
View File
@@ -803,7 +803,7 @@ class TestLocalPath(CommonFSTests):
# depending on how the paths are used), but > 4096 (which is the
# Linux' limitation) - the behaviour of paths with names > 4096 chars
# is undetermined
newfilename = "/test" * 60
newfilename = "/test" * 60 # type:ignore[unreachable]
l1 = tmpdir.join(newfilename)
l1.ensure(file=True)
l1.write("foo")
@@ -1344,8 +1344,8 @@ class TestPOSIXLocalPath:
assert realpath.basename == "file"
def test_owner(self, path1, tmpdir):
from pwd import getpwuid
from grp import getgrgid
from pwd import getpwuid # type:ignore[attr-defined]
from grp import getgrgid # type:ignore[attr-defined]
stat = path1.stat()
assert stat.path == path1
+1 -8
View File
@@ -694,14 +694,7 @@ class TestInvocationVariants:
# mixed module and filenames:
monkeypatch.chdir("world")
# pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages.
# While we could change the test to use implicit namespace packages, seems better
# to still ensure the old declaration via declare_namespace still works.
ignore_w = r"-Wignore:Deprecated call to `pkg_resources.declare_namespace"
result = pytester.runpytest(
"--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w
)
result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world")
assert result.ret == 0
result.stdout.fnmatch_lines(
[
-1
View File
@@ -157,7 +157,6 @@ def color_mapping():
"number": "\x1b[94m",
"str": "\x1b[33m",
"print": "\x1b[96m",
"endline": "\x1b[90m\x1b[39;49;00m",
}
RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()}
+1 -1
View File
@@ -254,7 +254,7 @@ class TestTerminalWriterLineWidth:
pytest.param(
True,
True,
"{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n",
"{kw}assert{hl-reset} {number}0{hl-reset}\n",
id="with markup and code_highlight",
),
pytest.param(
+69
View File
@@ -1165,3 +1165,72 @@ def test_log_file_cli_subdirectories_are_successfully_created(
result = pytester.runpytest("--log-file=foo/bar/logf.log")
assert "logf.log" in os.listdir(expected)
assert result.ret == ExitCode.OK
def test_disable_loggers(testdir):
testdir.makepyfile(
"""
import logging
import os
disabled_log = logging.getLogger('disabled')
test_log = logging.getLogger('test')
def test_logger_propagation(caplog):
with caplog.at_level(logging.DEBUG):
disabled_log.warning("no log; no stderr")
test_log.debug("Visible text!")
assert caplog.record_tuples == [('test', 10, 'Visible text!')]
"""
)
result = testdir.runpytest("--log-disable=disabled", "-s")
assert result.ret == ExitCode.OK
assert not result.stderr.lines
def test_disable_loggers_does_not_propagate(testdir):
testdir.makepyfile(
"""
import logging
import os
parent_logger = logging.getLogger("parent")
child_logger = parent_logger.getChild("child")
def test_logger_propagation_to_parent(caplog):
with caplog.at_level(logging.DEBUG):
parent_logger.warning("some parent logger message")
child_logger.warning("some child logger message")
assert len(caplog.record_tuples) == 1
assert caplog.record_tuples[0][0] == "parent"
assert caplog.record_tuples[0][2] == "some parent logger message"
"""
)
result = testdir.runpytest("--log-disable=parent.child", "-s")
assert result.ret == ExitCode.OK
assert not result.stderr.lines
def test_log_disabling_works_with_log_cli(testdir):
testdir.makepyfile(
"""
import logging
disabled_log = logging.getLogger('disabled')
test_log = logging.getLogger('test')
def test_log_cli_works(caplog):
test_log.info("Visible text!")
disabled_log.warning("This string will be suppressed.")
"""
)
result = testdir.runpytest(
"--log-cli-level=DEBUG",
"--log-disable=disabled",
)
assert result.ret == ExitCode.OK
result.stdout.fnmatch_lines(
"INFO test:test_log_disabling_works_with_log_cli.py:6 Visible text!"
)
result.stdout.no_fnmatch_line(
"WARNING disabled:test_log_disabling_works_with_log_cli.py:7 This string will be suppressed."
)
assert not result.stderr.lines
+6 -6
View File
@@ -1,13 +1,13 @@
anyio[curio,trio]==3.6.1
django==4.1.2
pytest-asyncio==0.19.0
pytest-bdd==6.0.1
anyio[curio,trio]==3.6.2
django==4.1.3
pytest-asyncio==0.20.2
pytest-bdd==6.1.1
pytest-cov==4.0.0
pytest-django==4.5.2
pytest-flakes==4.0.5
pytest-html==3.1.1
pytest-html==3.2.0
pytest-mock==3.10.0
pytest-rerunfailures==10.2
pytest-rerunfailures==10.3
pytest-sugar==0.9.5
pytest-trio==0.7.0
pytest-twisted==1.14.0
-13
View File
@@ -630,19 +630,6 @@ class TestApprox:
def test_dict_vs_other(self):
assert 1 != approx({"a": 0})
def test_dict_for_div_by_zero(self, assert_approx_raises_regex):
assert_approx_raises_regex(
{"foo": 42.0},
{"foo": 0.0},
[
r" comparison failed. Mismatched elements: 1 / 1:",
rf" Max absolute difference: {SOME_FLOAT}",
r" Max relative difference: inf",
r" Index \| Obtained\s+\| Expected ",
rf" foo | {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}",
],
)
def test_numpy_array(self):
np = pytest.importorskip("numpy")
-4
View File
@@ -3338,10 +3338,6 @@ class TestShowFixtures:
config = pytester.parseconfigure("--funcargs")
assert config.option.showfixtures
def test_show_help(self, pytester: Pytester) -> None:
result = pytester.runpytest("--fixtures", "--help")
assert not result.ret
def test_show_fixtures(self, pytester: Pytester) -> None:
result = pytester.runpytest("--fixtures")
result.stdout.fnmatch_lines(
+4 -4
View File
@@ -416,7 +416,7 @@ def test_function_instance(pytester: Pytester) -> None:
def test_static(): pass
"""
)
assert len(items) == 3
assert len(items) == 4
assert isinstance(items[0], Function)
assert items[0].name == "test_func"
assert items[0].instance is None
@@ -424,6 +424,6 @@ def test_function_instance(pytester: Pytester) -> None:
assert items[1].name == "test_method"
assert items[1].instance is not None
assert items[1].instance.__class__.__name__ == "TestIt"
assert isinstance(items[2], Function)
assert items[2].name == "test_static"
assert items[2].instance is None
assert isinstance(items[3], Function)
assert items[3].name == "test_static"
assert items[3].instance is None
+37 -12
View File
@@ -807,9 +807,9 @@ class TestAssert_reprcompare_dataclass:
"E ['field_b']",
"E ",
"E Drill down into differing attribute field_b:",
"E field_b: 'b' != 'c'...",
"E ",
"E ...Full output truncated (3 lines hidden), use '-vv' to show",
"E field_b: 'b' != 'c'",
"E - c",
"E + b",
],
consecutive=True,
)
@@ -827,7 +827,7 @@ class TestAssert_reprcompare_dataclass:
"E Drill down into differing attribute g:",
"E g: S(a=10, b='ten') != S(a=20, b='xxx')...",
"E ",
"E ...Full output truncated (52 lines hidden), use '-vv' to show",
"E ...Full output truncated (51 lines hidden), use '-vv' to show",
],
consecutive=True,
)
@@ -1188,30 +1188,55 @@ class TestTruncateExplanation:
def test_truncates_at_8_lines_when_given_list_of_empty_strings(self) -> None:
expl = ["" for x in range(50)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100)
assert len(result) != len(expl)
assert result != expl
assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "43 lines hidden" in result[-1]
assert "42 lines hidden" in result[-1]
last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_truncates_at_8_lines_when_first_8_lines_are_LT_max_chars(self) -> None:
expl = ["a" for x in range(100)]
total_lines = 100
expl = ["a" for x in range(total_lines)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
assert result != expl
assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "93 lines hidden" in result[-1]
assert f"{total_lines - 8} lines hidden" in result[-1]
last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
def test_truncates_at_8_lines_when_there_is_one_line_to_remove(self) -> None:
"""The number of line in the result is 9, the same number as if we truncated."""
expl = ["a" for x in range(9)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
assert result == expl
assert "truncated" not in result[-1]
def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_chars(
self,
) -> None:
line = "a" * 10
expl = [line, line]
result = truncate._truncate_explanation(expl, max_lines=10, max_chars=10)
assert result == [line, line]
def test_truncates_edgecase_when_truncation_message_makes_the_result_longer_for_lines(
self,
) -> None:
line = "a" * 10
expl = [line, line]
result = truncate._truncate_explanation(expl, max_lines=1, max_chars=100)
assert result == [line, line]
def test_truncates_at_8_lines_when_first_8_lines_are_EQ_max_chars(self) -> None:
expl = ["a" * 80 for x in range(16)]
expl = [chr(97 + x) * 80 for x in range(16)]
result = truncate._truncate_explanation(expl, max_lines=8, max_chars=8 * 80)
assert result != expl
assert len(result) == 8 + self.LINES_IN_TRUNCATION_MSG
assert len(result) == 16 - 8 + self.LINES_IN_TRUNCATION_MSG
assert "Full output truncated" in result[-1]
assert "9 lines hidden" in result[-1]
assert "8 lines hidden" in result[-1]
last_line_before_trunc_msg = result[-self.LINES_IN_TRUNCATION_MSG - 1]
assert last_line_before_trunc_msg.endswith("...")
@@ -1240,7 +1265,7 @@ class TestTruncateExplanation:
line_count = 7
line_len = 100
expected_truncated_lines = 2
expected_truncated_lines = 1
pytester.makepyfile(
r"""
def test_many_lines():
@@ -1261,7 +1286,7 @@ class TestTruncateExplanation:
"*+ 1*",
"*+ 3*",
"*+ 5*",
"*truncated (%d lines hidden)*use*-vv*" % expected_truncated_lines,
"*truncated (%d line hidden)*use*-vv*" % expected_truncated_lines,
]
)
-5
View File
@@ -1249,8 +1249,3 @@ def test_cachedir_tag(pytester: Pytester) -> None:
cache.set("foo", "bar")
cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG")
assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT
def test_clioption_with_cacheshow_and_help(pytester: Pytester) -> None:
result = pytester.runpytest("--cache-show", "--help")
assert result.ret == 0
+14
View File
@@ -735,6 +735,20 @@ class Test_genitems:
assert s.endswith("test_example_items1.testone")
print(s)
def test_classmethod_is_discovered(self, pytester: Pytester) -> None:
"""Test that classmethods are discovered"""
p = pytester.makepyfile(
"""
class TestCase:
@classmethod
def test_classmethod(cls) -> None:
pass
"""
)
items, reprec = pytester.inline_genitems(p)
ids = [x.getmodpath() for x in items] # type: ignore[attr-defined]
assert ids == ["TestCase.test_classmethod"]
def test_class_and_functions_discovery_using_glob(self, pytester: Pytester) -> None:
"""Test that Python_classes and Python_functions config options work
as prefixes and glob-like patterns (#600)."""
+1 -4
View File
@@ -92,7 +92,7 @@ class TestSetattrWithImportPath:
mp.delattr("os.path.abspath")
assert not hasattr(os.path, "abspath")
mp.undo()
assert os.path.abspath
assert os.path.abspath # type:ignore[truthy-function]
def test_delattr() -> None:
@@ -425,9 +425,6 @@ def test_context_classmethod() -> None:
assert A.x == 1
@pytest.mark.filterwarnings(
"ignore:Deprecated call to `pkg_resources.declare_namespace"
)
def test_syspath_prepend_with_namespace_packages(
pytester: Pytester, monkeypatch: MonkeyPatch
) -> None:
-21
View File
@@ -496,24 +496,3 @@ def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None:
)
result = pytester.runpytest(p, "-p", "nose")
assert result.ret == 0
@pytest.mark.parametrize("fixture_name", ("teardown", "teardown_class"))
def test_teardown_fixture_not_called_directly(fixture_name, pytester: Pytester) -> None:
"""Regression test for #10597."""
p = pytester.makepyfile(
f"""
import pytest
class TestHello:
@pytest.fixture
def {fixture_name}(self):
yield
def test_hello(self, {fixture_name}):
assert True
"""
)
result = pytester.runpytest(p, "-p", "nose")
assert result.ret == 0
+30 -4
View File
@@ -2,6 +2,7 @@ import inspect
import os
import sys
import types
from functools import partial
from pathlib import Path
from typing import Dict
from typing import List
@@ -19,6 +20,9 @@ from _pytest.monkeypatch import MonkeyPatch
from _pytest.outcomes import OutcomeException
from _pytest.pytester import Pytester
if sys.version_info[:2] < (3, 11):
from exceptiongroup import ExceptionGroup
class TestSetupState:
def test_setup(self, pytester: Pytester) -> None:
@@ -77,8 +81,6 @@ class TestSetupState:
assert r == ["fin3", "fin1"]
def test_teardown_multiple_fail(self, pytester: Pytester) -> None:
# Ensure the first exception is the one which is re-raised.
# Ideally both would be reported however.
def fin1():
raise Exception("oops1")
@@ -90,9 +92,14 @@ class TestSetupState:
ss.setup(item)
ss.addfinalizer(fin1, item)
ss.addfinalizer(fin2, item)
with pytest.raises(Exception) as err:
with pytest.raises(ExceptionGroup) as err:
ss.teardown_exact(None)
assert err.value.args == ("oops2",)
# Note that finalizers are run LIFO, but because FIFO is more intuitive for
# users we reverse the order of messages, and see the error from fin1 first.
err1, err2 = err.value.exceptions
assert err1.args == ("oops1",)
assert err2.args == ("oops2",)
def test_teardown_multiple_scopes_one_fails(self, pytester: Pytester) -> None:
module_teardown = []
@@ -113,6 +120,25 @@ class TestSetupState:
ss.teardown_exact(None)
assert module_teardown == ["fin_module"]
def test_teardown_multiple_scopes_several_fail(self, pytester) -> None:
def raiser(exc):
raise exc
item = pytester.getitem("def test_func(): pass")
mod = item.listchain()[-2]
ss = item.session._setupstate
ss.setup(item)
ss.addfinalizer(partial(raiser, KeyError("from module scope")), mod)
ss.addfinalizer(partial(raiser, TypeError("from function scope 1")), item)
ss.addfinalizer(partial(raiser, ValueError("from function scope 2")), item)
with pytest.raises(ExceptionGroup, match="errors during test teardown") as e:
ss.teardown_exact(None)
mod, func = e.value.exceptions
assert isinstance(mod, KeyError)
assert isinstance(func.exceptions[0], TypeError) # type: ignore
assert isinstance(func.exceptions[1], ValueError) # type: ignore
class BaseFunctionalTests:
def test_passfunction(self, pytester: Pytester) -> None:
-77
View File
@@ -1,10 +1,6 @@
from pathlib import Path
import pytest
from _pytest.cacheprovider import Cache
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Pytester
from _pytest.stepwise import STEPWISE_CACHE_DIR
@pytest.fixture
@@ -282,76 +278,3 @@ def test_stepwise_skip_is_independent(pytester: Pytester) -> None:
def test_sw_skip_help(pytester: Pytester) -> None:
result = pytester.runpytest("-h")
result.stdout.fnmatch_lines("*Implicitly enables --stepwise.")
def test_stepwise_xdist_dont_store_lastfailed(pytester: Pytester) -> None:
pytester.makefile(
ext=".ini",
pytest=f"[pytest]\ncache_dir = {pytester.path}\n",
)
pytester.makepyfile(
conftest="""
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config) -> None:
config.workerinput = True
"""
)
pytester.makepyfile(
test_one="""
def test_one():
assert False
"""
)
result = pytester.runpytest("--stepwise")
assert result.ret == pytest.ExitCode.INTERRUPTED
stepwise_cache_file = (
pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR
)
assert not Path(stepwise_cache_file).exists()
def test_disabled_stepwise_xdist_dont_clear_cache(pytester: Pytester) -> None:
pytester.makefile(
ext=".ini",
pytest=f"[pytest]\ncache_dir = {pytester.path}\n",
)
stepwise_cache_file = (
pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR
)
stepwise_cache_dir = stepwise_cache_file.parent
stepwise_cache_dir.mkdir(exist_ok=True, parents=True)
stepwise_cache_file_relative = f"{Cache._CACHE_PREFIX_VALUES}/{STEPWISE_CACHE_DIR}"
expected_value = '"test_one.py::test_one"'
content = {f"{stepwise_cache_file_relative}": expected_value}
pytester.makefile(ext="", **content)
pytester.makepyfile(
conftest="""
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config) -> None:
config.workerinput = True
"""
)
pytester.makepyfile(
test_one="""
def test_one():
assert True
"""
)
result = pytester.runpytest()
assert result.ret == 0
assert Path(stepwise_cache_file).exists()
with stepwise_cache_file.open() as file_handle:
observed_value = file_handle.readlines()
assert [expected_value] == observed_value
+12 -12
View File
@@ -1265,14 +1265,14 @@ def test_color_yes(pytester: Pytester, color_mapping) -> None:
"=*= FAILURES =*=",
"{red}{bold}_*_ test_this _*_{reset}",
"",
" {kw}def{hl-reset} {function}test_this{hl-reset}():{endline}",
"> fail(){endline}",
" {kw}def{hl-reset} {function}test_this{hl-reset}():",
"> fail()",
"",
"{bold}{red}test_color_yes.py{reset}:5: ",
"_ _ * _ _*",
"",
" {kw}def{hl-reset} {function}fail{hl-reset}():{endline}",
"> {kw}assert{hl-reset} {number}0{hl-reset}{endline}",
" {kw}def{hl-reset} {function}fail{hl-reset}():",
"> {kw}assert{hl-reset} {number}0{hl-reset}",
"{bold}{red}E assert 0{reset}",
"",
"{bold}{red}test_color_yes.py{reset}:2: AssertionError",
@@ -1292,9 +1292,9 @@ def test_color_yes(pytester: Pytester, color_mapping) -> None:
"=*= FAILURES =*=",
"{red}{bold}_*_ test_this _*_{reset}",
"{bold}{red}test_color_yes.py{reset}:5: in test_this",
" fail(){endline}",
" fail()",
"{bold}{red}test_color_yes.py{reset}:2: in fail",
" {kw}assert{hl-reset} {number}0{hl-reset}{endline}",
" {kw}assert{hl-reset} {number}0{hl-reset}",
"{bold}{red}E assert 0{reset}",
"{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}",
]
@@ -2472,8 +2472,8 @@ class TestCodeHighlight:
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(
[
" {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}",
"> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}",
" {kw}def{hl-reset} {function}test_foo{hl-reset}():",
"> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}",
"{bold}{red}E assert 1 == 10{reset}",
]
)
@@ -2494,9 +2494,9 @@ class TestCodeHighlight:
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(
[
" {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}",
" {kw}def{hl-reset} {function}test_foo{hl-reset}():",
" {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}",
"> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}",
"> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}",
"{bold}{red}E assert 0{reset}",
]
)
@@ -2517,8 +2517,8 @@ class TestCodeHighlight:
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(
[
" {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}",
"> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}",
" {kw}def{hl-reset} {function}test_foo{hl-reset}():",
"> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}",
"{bold}{red}E assert 1 == 10{reset}",
]
)
+129 -5
View File
@@ -42,6 +42,14 @@ class FakeConfig:
def get(self, key):
return lambda *k: None
def getini(self, name):
if name == "tmp_path_retention_count":
return 3
elif name == "tmp_path_retention_policy":
return "failed"
else:
assert False
@property
def option(self):
return self
@@ -84,6 +92,117 @@ class TestConfigTmpPath:
assert mytemp.exists()
assert not mytemp.joinpath("hello").exists()
def test_policy_failed_removes_only_passed_dir(self, pytester: Pytester) -> None:
p = pytester.makepyfile(
"""
def test_1(tmp_path):
assert 0 == 0
def test_2(tmp_path):
assert 0 == 1
"""
)
pytester.inline_run(p)
root = pytester._test_tmproot
for child in root.iterdir():
base_dir = list(
filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
)
assert len(base_dir) == 1
test_dir = list(
filter(
lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir()
)
)
# Check only the failed one remains
assert len(test_dir) == 1
assert test_dir[0].name == "test_20"
def test_policy_failed_removes_basedir_when_all_passed(
self, pytester: Pytester
) -> None:
p = pytester.makepyfile(
"""
def test_1(tmp_path):
assert 0 == 0
"""
)
pytester.inline_run(p)
root = pytester._test_tmproot
for child in root.iterdir():
# This symlink will be deleted by cleanup_numbered_dir **after**
# the test finishes because it's triggered by atexit.
# So it has to be ignored here.
base_dir = filter(lambda x: not x.is_symlink(), child.iterdir())
# Check the base dir itself is gone
assert len(list(base_dir)) == 0
# issue #10502
def test_policy_failed_removes_dir_when_skipped_from_fixture(
self, pytester: Pytester
) -> None:
p = pytester.makepyfile(
"""
import pytest
@pytest.fixture
def fixt(tmp_path):
pytest.skip()
def test_fixt(fixt):
pass
"""
)
pytester.inline_run(p)
# Check if the whole directory is removed
root = pytester._test_tmproot
for child in root.iterdir():
base_dir = list(
filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
)
assert len(base_dir) == 0
# issue #10502
def test_policy_all_keeps_dir_when_skipped_from_fixture(
self, pytester: Pytester
) -> None:
p = pytester.makepyfile(
"""
import pytest
@pytest.fixture
def fixt(tmp_path):
pytest.skip()
def test_fixt(fixt):
pass
"""
)
pytester.makepyprojecttoml(
"""
[tool.pytest.ini_options]
tmp_path_retention_policy = "all"
"""
)
pytester.inline_run(p)
# Check if the whole directory is kept
root = pytester._test_tmproot
for child in root.iterdir():
base_dir = list(
filter(lambda x: x.is_dir() and not x.is_symlink(), child.iterdir())
)
assert len(base_dir) == 1
test_dir = list(
filter(
lambda x: x.is_dir() and not x.is_symlink(), base_dir[0].iterdir()
)
)
assert len(test_dir) == 1
testdata = [
("mypath", True),
@@ -275,12 +394,12 @@ class TestNumberedDir:
assert not lock.exists()
def _do_cleanup(self, tmp_path: Path) -> None:
def _do_cleanup(self, tmp_path: Path, keep: int = 2) -> None:
self.test_make(tmp_path)
cleanup_numbered_dir(
root=tmp_path,
prefix=self.PREFIX,
keep=2,
keep=keep,
consider_lock_dead_if_created_before=0,
)
@@ -289,6 +408,11 @@ class TestNumberedDir:
a, b = (x for x in tmp_path.iterdir() if not x.is_symlink())
print(a, b)
def test_cleanup_keep_0(self, tmp_path: Path):
self._do_cleanup(tmp_path, 0)
dir_num = len(list(tmp_path.iterdir()))
assert dir_num == 0
def test_cleanup_locked(self, tmp_path):
p = make_numbered_dir(root=tmp_path, prefix=self.PREFIX)
@@ -446,7 +570,7 @@ def test_tmp_path_factory_create_directory_with_safe_permissions(
"""Verify that pytest creates directories under /tmp with private permissions."""
# Use the test's tmp_path as the system temproot (/tmp).
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
tmp_factory = TempPathFactory(None, 3, "failed", lambda *args: None, _ispytest=True)
basetemp = tmp_factory.getbasetemp()
# No world-readable permissions.
@@ -466,14 +590,14 @@ def test_tmp_path_factory_fixes_up_world_readable_permissions(
"""
# Use the test's tmp_path as the system temproot (/tmp).
monkeypatch.setenv("PYTEST_DEBUG_TEMPROOT", str(tmp_path))
tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
tmp_factory = TempPathFactory(None, 3, "failed", lambda *args: None, _ispytest=True)
basetemp = tmp_factory.getbasetemp()
# Before - simulate bad perms.
os.chmod(basetemp.parent, 0o777)
assert (basetemp.parent.stat().st_mode & 0o077) != 0
tmp_factory = TempPathFactory(None, lambda *args: None, _ispytest=True)
tmp_factory = TempPathFactory(None, 3, "failed", lambda *args: None, _ispytest=True)
basetemp = tmp_factory.getbasetemp()
# After - fixed.
-11
View File
@@ -3,11 +3,6 @@
This file is not executed, it is only checked by mypy to ensure that
none of the code triggers any mypy errors.
"""
import contextlib
from typing import Optional
from typing_extensions import assert_type
import pytest
@@ -27,9 +22,3 @@ def check_fixture_ids_callable() -> None:
@pytest.mark.parametrize("func", [str, int], ids=lambda x: str(x.__name__))
def check_parametrize_ids_callable(func) -> None:
pass
def check_raises_is_a_context_manager(val: bool) -> None:
with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo:
pass
assert_type(excinfo, Optional[pytest.ExceptionInfo[RuntimeError]])
+1 -1
View File
@@ -104,7 +104,7 @@ deps =
PyYAML
regendoc>=0.8.1
sphinx
allowlist_externals =
whitelist_externals =
make
commands =
make regen