diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 507789bf5..294b13743 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -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 diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index f7840d537..aa90b51e2 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -22,7 +22,7 @@ jobs: pull-requests: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: fetch-depth: 0 persist-credentials: true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 54c74863f..252809946 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -23,30 +23,34 @@ 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@v2 + uses: actions/setup-python@v4 with: python-version: "3.7" - - name: Install dependencies + - name: Install tox run: | python -m pip install --upgrade pip - pip install --upgrade build tox - - - name: Build package - run: | - python -m build - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_token }} + pip install --upgrade tox - name: Publish GitHub release notes env: diff --git a/.github/workflows/prepare-release-pr.yml b/.github/workflows/prepare-release-pr.yml index 429834b3f..76bf14d7e 100644 --- a/.github/workflows/prepare-release-pr.yml +++ b/.github/workflows/prepare-release-pr.yml @@ -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" diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000..3f8ca186f --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,23 @@ +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@v8 + 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 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3f3e7e535..cf5027223 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,11 @@ 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: {} @@ -38,6 +43,7 @@ jobs: "windows-py39", "windows-py310", "windows-py311", + "windows-py312", "ubuntu-py37", "ubuntu-py37-pluggy", @@ -46,12 +52,13 @@ jobs: "ubuntu-py39", "ubuntu-py310", "ubuntu-py311", + "ubuntu-py312", "ubuntu-pypy3", "macos-py37", - "macos-py38", "macos-py39", "macos-py310", + "macos-py312", "docs", "doctesting", @@ -66,7 +73,7 @@ jobs: - name: "windows-py37-pluggy" python: "3.7" os: windows-latest - tox_env: "py37-pluggymain-xdist" + tox_env: "py37-pluggymain-pylib-xdist" - name: "windows-py38" python: "3.8" os: windows-latest @@ -81,9 +88,13 @@ jobs: os: windows-latest tox_env: "py310-xdist" - name: "windows-py311" - python: "3.11-dev" + python: "3.11" 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" @@ -93,7 +104,7 @@ jobs: - name: "ubuntu-py37-pluggy" python: "3.7" os: ubuntu-latest - tox_env: "py37-pluggymain-xdist" + tox_env: "py37-pluggymain-pylib-xdist" - name: "ubuntu-py37-freeze" python: "3.7" os: ubuntu-latest @@ -111,10 +122,15 @@ jobs: os: ubuntu-latest tox_env: "py310-xdist" - name: "ubuntu-py311" - python: "3.11-dev" + python: "3.11" 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.7" os: ubuntu-latest @@ -124,19 +140,19 @@ jobs: 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.9" @@ -163,6 +179,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + check-latest: ${{ endsWith(matrix.python, '-dev') }} - name: Install dependencies run: | @@ -184,7 +201,15 @@ jobs: - name: Upload coverage to Codecov if: "matrix.use_coverage" 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 diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 193469072..e7cbc4e96 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -11,7 +11,7 @@ on: permissions: {} jobs: - createPullRequest: + update-plugin-list: if: github.repository_owner == 'pytest-dev' runs-on: ubuntu-latest permissions: @@ -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@153407881ec5c347639a548ade7d8ad1d6740e38 with: commit-message: '[automated] Update plugin list' author: 'pytest bot ' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19329cb21..b5089e129 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,18 +1,16 @@ -default_language_version: - python: "3.10" repos: - repo: https://github.com/psf/black - rev: 22.8.0 + rev: 23.3.0 hooks: - id: black args: [--safe, --quiet] - repo: https://github.com/asottile/blacken-docs - rev: v1.12.1 + rev: 1.14.0 hooks: - id: blacken-docs - additional_dependencies: [black==20.8b1] + additional_dependencies: [black==23.1.0] - 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 +21,7 @@ repos: exclude: _pytest/(debugging|hookspec).py language_version: python3 - repo: https://github.com/PyCQA/autoflake - rev: v1.6.1 + rev: v2.1.1 hooks: - id: autoflake name: autoflake @@ -31,41 +29,40 @@ 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 additional_dependencies: - flake8-typing-imports==1.12.0 - flake8-docstrings==1.5.0 -- repo: https://github.com/asottile/reorder_python_imports - rev: v3.8.3 +- repo: https://github.com/asottile/reorder-python-imports + rev: v3.10.0 hooks: - id: reorder-python-imports args: ['--application-directories=.:src', --py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v2.38.2 + rev: v3.7.0 hooks: - id: pyupgrade args: [--py37-plus] - repo: https://github.com/asottile/setup-cfg-fmt - rev: v2.0.0 + rev: v2.3.0 hooks: - id: setup-cfg-fmt - args: ["--max-py-version=3.10", "--include-version-classifiers"] + args: ["--max-py-version=3.12", "--include-version-classifiers"] - repo: https://github.com/pre-commit/pygrep-hooks - rev: v1.9.0 + rev: v1.10.0 hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.981 + rev: v1.3.0 hooks: - id: mypy files: ^(src/|testing/) args: [] additional_dependencies: - iniconfig>=1.1.0 - - py>=1.8.2 - attrs>=19.2.0 - packaging - tomli diff --git a/.readthedocs.yml b/.readthedocs.yml index bc44d38b4..b506c5f40 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,9 +2,12 @@ version: 2 python: install: - - requirements: doc/en/requirements.txt - - method: pip - path: . + # Install pytest first, then doc/en/requirements.txt. + # This order is important to honor any pins in doc/en/requirements.txt + # when the pinned library is also a dependency of pytest. + - method: pip + path: . + - requirements: doc/en/requirements.txt build: os: ubuntu-20.04 diff --git a/AUTHORS b/AUTHORS index 55b0237ea..ee4ef203f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,10 +8,14 @@ Abdeali JK Abdelrahman Elbehery Abhijeet Kasurde Adam Johnson +Adam Stewart Adam Uhlir Ahn Ki-Wook Akiomi Kamakura Alan Velasco +Alessio Izzo +Alex Jones +Alex Lambson Alexander Johnson Alexander King Alexei Kozlenok @@ -43,6 +47,7 @@ Ariel Pillemer Armin Rigo Aron Coyle Aron Curzon +Ashish Kurmi Aviral Verma Aviv Palivoda Babak Keyvani @@ -53,10 +58,12 @@ Benjamin Peterson Bernard Pratz Bob Ippolito Brian Dorsey +Brian Larsen Brian Maissy Brian Okken Brianna Laugher Bruno Oliveira +Cal Jacobson Cal Leeming Carl Friedrich Bolz Carlos Jenkins @@ -65,6 +72,7 @@ Charles Cloud Charles Machalow Charnjit SiNGH (CCSJ) Cheuk Ting Ho +Chris Mahoney Chris Lamb Chris NeJame Chris Rose @@ -88,6 +96,7 @@ Daniel Grana Daniel Hahler Daniel Nuri Daniel Sánchez Castelló +Daniel Valenzuela Zenteno Daniel Wandschneider Daniele Procida Danielle Jenkins @@ -122,13 +131,16 @@ Eric Siegerman Erik Aronesty Erik M. Bray Evan Kepner +Evgeny Seliverstov Fabien Zarifian Fabio Zadrozny +Felix Hofstätter Felix Nieuwenhuizen Feng Ma Florian Bruhin Florian Dahlitz Floris Bruynooghe +Gabriel Landau Gabriel Reis Garvit Shubham Gene Wood @@ -157,6 +169,7 @@ Ionuț Turturică Itxaso Aizpurua Iwan Briquemont Jaap Broekhuizen +Jake VanderPlas Jakob van Santen Jakub Mitoraj James Bourbeau @@ -181,10 +194,11 @@ Joseph Hunkeler Josh Karpel Joshua Bronson Jurko Gospodnetić -Justyna Janczyszyn Justice Ndou +Justyna Janczyszyn Kale Kundert Kamran Ahmad +Kenny Y Karl O. Pinc Karthikeyan Singaravelan Katarzyna Jachim @@ -221,6 +235,7 @@ Marcin Bachry Marco Gorelli Mark Abramowitz Mark Dickinson +Marko Pacak Markus Unterwaditzer Martijn Faassen Martin Altmayer @@ -234,7 +249,6 @@ Matthias Hafner Maxim Filipenko Maximilian Cosmo Sitter mbyt -Mickey Pashov Michael Aquilina Michael Birtwell Michael Droettboom @@ -243,6 +257,7 @@ Michael Krebs Michael Seifert Michal Wajszczuk Michał Zięba +Mickey Pashov Mihai Capotă Mike Hoyle (hoylemd) Mike Lundy @@ -257,9 +272,9 @@ Niclas Olofsson Nicolas Delaby Nikolay Kondratyev Nipunn Koorapati -Olga Matoula Oleg Pidsadnyi Oleg Sushchenko +Olga Matoula Oliver Bestwalter Omar Kohl Omer Hadari @@ -275,6 +290,7 @@ Paweł Adamczak Pedro Algarvio Petter Strandmark Philipp Loose +Pierre Sassoulas Pieter Mulder Piotr Banaszkiewicz Piotr Helm @@ -284,15 +300,18 @@ Prashant Sharma Pulkit Goyal Punyashloka Biswal Quentin Pradet +q0w Ralf Schmitt -Ram Rachum Ralph Giles +Ram Rachum Ran Benita Raphael Castaneda Raphael Pierzina +Rafal Semik Raquel Alegre Ravi Chandra Robert Holt +Roberto Aldera Roberto Polli Roland Puntaier Romain Dorgueil @@ -309,6 +328,7 @@ Samuel Searles-Bryant Samuele Pedroni Sanket Duthade Sankt Petersbug +Saravanan Padmanaban Segev Finer Serhii Mozghovyi Seth Junot @@ -322,6 +342,7 @@ Srinivas Reddy Thatiparthy Stefan Farmbauer Stefan Scherfke Stefan Zimmermann +Stefanie Molin Stefano Taschini Steffen Allner Stephan Obermann @@ -340,6 +361,7 @@ Thomas Grainger Thomas Hisch Tim Hoffmann Tim Strazny +TJ Bruno Tobias Diez Tom Dalton Tom Viner @@ -355,6 +377,7 @@ Victor Maryama Victor Rodriguez Victor Uriarte Vidar T. Fauske +Vijay Arora Virgil Dupras Vitaly Lashmanov Vivaan Verma @@ -370,7 +393,10 @@ Wouter van Ackooy Xixi Zhao Xuan Luong Xuecong Liao +Yannick Péroux Yoav Caspi +Yuliang Shao +Yusuke Kadowaki Yuval Shimon Zac Hatfield-Dodds Zachary Kneupper diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 22c3ecefb..791f98830 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -244,6 +244,11 @@ Here is a simple overview, with pytest-specific bits: be released in micro releases whereas features will be released in minor releases and incompatible changes in major releases. + You will need the tags to test locally, so be sure you have the tags from the main repository. If you suspect you don't, set the main repository as upstream and fetch the tags:: + + $ git remote add upstream https://github.com/pytest-dev/pytest + $ git fetch upstream --tags + If you need some help with Git, follow this quick start guide: https://git.wiki.kernel.org/index.php/QuickStart @@ -380,7 +385,7 @@ them. Backporting bug fixes for the next patch release ------------------------------------------------ -Pytest makes feature release every few weeks or months. In between, patch releases +Pytest makes a feature release every few weeks or months. In between, patch releases are made to the previous feature release, containing bug fixes only. The bug fixes usually fix regressions, but may be any change that should reach users before the next feature release. @@ -389,7 +394,7 @@ Suppose for example that the latest release was 1.2.3, and you want to include a bug fix in 1.2.4 (check https://github.com/pytest-dev/pytest/releases for the actual latest release). The procedure for this is: -#. First, make sure the bug is fixed the ``main`` branch, with a regular pull +#. First, make sure the bug is fixed in the ``main`` branch, with a regular pull request, as described above. An exception to this is if the bug fix is not applicable to ``main`` anymore. diff --git a/changelog/10012.deprecation.rst b/changelog/10012.deprecation.rst deleted file mode 100644 index 66681263e..000000000 --- a/changelog/10012.deprecation.rst +++ /dev/null @@ -1 +0,0 @@ -Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8. diff --git a/changelog/10150.bugfix.rst b/changelog/10150.bugfix.rst deleted file mode 100644 index 24c7ccd35..000000000 --- a/changelog/10150.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -:data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled. diff --git a/changelog/10196.trivial.rst b/changelog/10196.trivial.rst deleted file mode 100644 index edf458f84..000000000 --- a/changelog/10196.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -:class:`~pytest.PytestReturnNotNoneWarning` is now a subclass of :class:`~pytest.PytestRemovedIn8Warning`: the plan is to make returning non-``None`` from tests an error in the future. diff --git a/changelog/10218.improvement.rst b/changelog/10218.improvement.rst deleted file mode 100644 index e00325e25..000000000 --- a/changelog/10218.improvement.rst +++ /dev/null @@ -1,5 +0,0 @@ -``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names, -instead of just ``list[str]`` and ``tuple[str, ...]``. - -(Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a -comma-delimited name list, as before). diff --git a/changelog/10313.trivial.rst b/changelog/10313.trivial.rst deleted file mode 100644 index 8203b5802..000000000 --- a/changelog/10313.trivial.rst +++ /dev/null @@ -1,3 +0,0 @@ -Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for -type check and runtime purposes. Made `_pytest.doctest` use internal APIs -to avoid circular imports. diff --git a/changelog/10344.doc.rst b/changelog/10344.doc.rst deleted file mode 100644 index 1c7885edc..000000000 --- a/changelog/10344.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``. diff --git a/changelog/3426.improvement.rst b/changelog/3426.improvement.rst deleted file mode 100644 index e232d56aa..000000000 --- a/changelog/3426.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expresed instead. diff --git a/changelog/4562.deprecation.rst b/changelog/4562.deprecation.rst deleted file mode 100644 index d459801d4..000000000 --- a/changelog/4562.deprecation.rst +++ /dev/null @@ -1,4 +0,0 @@ -Deprecate configuring hook specs/impls using attributes/marks. - -Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`. -For more details, see the :ref:`docs `. diff --git a/changelog/7337.improvement.rst b/changelog/7337.improvement.rst deleted file mode 100644 index 74d98d9b6..000000000 --- a/changelog/7337.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -A warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. diff --git a/changelog/7363.breaking.rst b/changelog/7363.breaking.rst new file mode 100644 index 000000000..93d87b1b1 --- /dev/null +++ b/changelog/7363.breaking.rst @@ -0,0 +1,22 @@ +**PytestRemovedIn8Warning deprecation warnings are now errors by default.** + +Following our plan to remove deprecated features with as little disruption as +possible, all warnings of type ``PytestRemovedIn8Warning`` now generate errors +instead of warning messages by default. + +**The affected features will be effectively removed in pytest 8.1**, so please consult the +:ref:`deprecations` section in the docs for directions on how to update existing code. + +In the pytest ``8.0.X`` series, it is possible to change the errors back into warnings as a +stopgap measure by adding this to your ``pytest.ini`` file: + +.. code-block:: ini + + [pytest] + filterwarnings = + ignore::pytest.PytestRemovedIn8Warning + +But this will stop working when pytest ``8.1`` is released. + +**If you have concerns** about the removal of a specific feature, please add a +comment to :issue:`7363`. diff --git a/changelog/8508.improvement.rst b/changelog/8508.improvement.rst deleted file mode 100644 index 36fb94582..000000000 --- a/changelog/8508.improvement.rst +++ /dev/null @@ -1,2 +0,0 @@ -Introduce multiline display for warning matching via :py:func:`pytest.warns` and -enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`. diff --git a/changelog/8646.improvement.rst b/changelog/8646.improvement.rst deleted file mode 100644 index 9691c1434..000000000 --- a/changelog/8646.improvement.rst +++ /dev/null @@ -1,2 +0,0 @@ -Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing -error. We now raise immediately with a more helpful message. diff --git a/changelog/8976.breaking.rst b/changelog/8976.breaking.rst new file mode 100644 index 000000000..bd9a63982 --- /dev/null +++ b/changelog/8976.breaking.rst @@ -0,0 +1,5 @@ +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`. diff --git a/changelog/9159.bugfix.rst b/changelog/9159.bugfix.rst deleted file mode 100644 index 5e7f0a301..000000000 --- a/changelog/9159.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``. diff --git a/changelog/9248.doc.rst b/changelog/9248.doc.rst deleted file mode 100644 index 755e7b0f1..000000000 --- a/changelog/9248.doc.rst +++ /dev/null @@ -1 +0,0 @@ -The documentation is now built using Sphinx 5.x (up from 3.x previously). diff --git a/changelog/9291.doc.rst b/changelog/9291.doc.rst deleted file mode 100644 index fad8467dc..000000000 --- a/changelog/9291.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`. diff --git a/changelog/9741.improvement.rst b/changelog/9741.improvement.rst deleted file mode 100644 index 1f9ab3ead..000000000 --- a/changelog/9741.improvement.rst +++ /dev/null @@ -1,3 +0,0 @@ -On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML. - -:mod:`tomli`` is no longer a dependency on Python 3.11. diff --git a/changelog/9742.improvement.rst b/changelog/9742.improvement.rst deleted file mode 100644 index a1abfc274..000000000 --- a/changelog/9742.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Display assertion message without escaped newline characters with ``-vv``. diff --git a/changelog/9823.improvement.rst b/changelog/9823.improvement.rst deleted file mode 100644 index 0a14cab67..000000000 --- a/changelog/9823.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Improved error message that is shown when no collector is found for a given file. diff --git a/changelog/9873.improvement.rst b/changelog/9873.improvement.rst deleted file mode 100644 index 431f9861b..000000000 --- a/changelog/9873.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Some coloring has been added to the short test summary. diff --git a/changelog/9877.bugfix.rst b/changelog/9877.bugfix.rst deleted file mode 100644 index 1d2264c61..000000000 --- a/changelog/9877.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``. diff --git a/changelog/9883.improvement.rst b/changelog/9883.improvement.rst deleted file mode 100644 index 180784b5d..000000000 --- a/changelog/9883.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Normalize the help description of all command-line options. diff --git a/changelog/9886.deprecation.rst b/changelog/9886.deprecation.rst deleted file mode 100644 index 94f51decf..000000000 --- a/changelog/9886.deprecation.rst +++ /dev/null @@ -1,10 +0,0 @@ -The functionality for running tests written for ``nose`` has been officially deprecated. - -This includes: - -* Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support. -* Setup/teardown using the `@with_setup `_ decorator. - -For more details, consult the :ref:`deprecation docs `. - -.. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup diff --git a/changelog/9897.feature.rst b/changelog/9897.feature.rst deleted file mode 100644 index 7464067bf..000000000 --- a/changelog/9897.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added shell-style wildcard support to ``testpaths``. diff --git a/changelog/9906.trivial.rst b/changelog/9906.trivial.rst deleted file mode 100644 index 8911b7190..000000000 --- a/changelog/9906.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers. diff --git a/changelog/9910.trivial.rst b/changelog/9910.trivial.rst deleted file mode 100644 index 4b1a4683c..000000000 --- a/changelog/9910.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -Fix default encoding warning (``EncodingWarning``) in ``cacheprovider`` diff --git a/changelog/9920.improvement.rst b/changelog/9920.improvement.rst deleted file mode 100644 index ed47e872c..000000000 --- a/changelog/9920.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Display full crash messages in ``short test summary info``, when runng in a CI environment. diff --git a/changelog/9984.trivial.rst b/changelog/9984.trivial.rst deleted file mode 100644 index cca817062..000000000 --- a/changelog/9984.trivial.rst +++ /dev/null @@ -1,4 +0,0 @@ -Improve the error message when we attempt to access a fixture that has been -torn down. -Add an additional sentence to the docstring explaining when it's not a good -idea to call getfixturevalue. diff --git a/changelog/9987.improvement.rst b/changelog/9987.improvement.rst deleted file mode 100644 index cb1ae17ad..000000000 --- a/changelog/9987.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 142425cde..914e763bd 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,13 @@ Release announcements :maxdepth: 2 + release-7.4.0 + release-7.3.2 + release-7.3.1 + release-7.3.0 + release-7.2.2 + release-7.2.1 + release-7.2.0 release-7.1.3 release-7.1.2 release-7.1.1 diff --git a/doc/en/announce/release-7.2.0.rst b/doc/en/announce/release-7.2.0.rst new file mode 100644 index 000000000..eca84aeb6 --- /dev/null +++ b/doc/en/announce/release-7.2.0.rst @@ -0,0 +1,93 @@ +pytest-7.2.0 +======================================= + +The pytest team is proud to announce the 7.2.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in the changelog: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Aaron Berdy +* Adam Turner +* Albert Villanova del Moral +* Alice Purcell +* Anthony Sottile +* Anton Yakutovich +* Babak Keyvani +* Brandon Chinn +* Bruno Oliveira +* Chanvin Xiao +* Cheuk Ting Ho +* Chris Wheeler +* EmptyRabbit +* Ezio Melotti +* Florian Best +* Florian Bruhin +* Fredrik Berndtsson +* Gabriel Landau +* Gergely Kalmár +* Hugo van Kemenade +* James Gerity +* John Litborn +* Jon Parise +* Kevin C +* Kian Eliasi +* MatthewFlamm +* Miro Hrončok +* Nate Meyvis +* Neil Girdhar +* Nhieuvu1802 +* Nipunn Koorapati +* Ofek Lev +* Paul Müller +* Paul Reece +* Pax +* Pete Baughman +* Peyman Salehi +* Philipp A +* Ran Benita +* Robert O'Shea +* Ronny Pfannschmidt +* Rowin +* Ruth Comer +* Samuel Colvin +* Samuel Gaist +* Sandro Tosi +* Shantanu +* Simon K +* Stephen Rosen +* Sviatoslav Sydorenko +* Tatiana Ovary +* Thierry Moisan +* Thomas Grainger +* Tim Hoffmann +* Tobias Diez +* Tony Narlock +* Vivaan Verma +* Wolfremium +* Zac Hatfield-Dodds +* Zach OBrien +* aizpurua23a +* gresm +* holesch +* itxasos23 +* johnkangw +* skhomuti +* sommersoft +* wodny +* zx.qiu + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.2.1.rst b/doc/en/announce/release-7.2.1.rst new file mode 100644 index 000000000..80ac7aff0 --- /dev/null +++ b/doc/en/announce/release-7.2.1.rst @@ -0,0 +1,25 @@ +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 diff --git a/doc/en/announce/release-7.2.2.rst b/doc/en/announce/release-7.2.2.rst new file mode 100644 index 000000000..b34a6ff5c --- /dev/null +++ b/doc/en/announce/release-7.2.2.rst @@ -0,0 +1,25 @@ +pytest-7.2.2 +======================================= + +pytest 7.2.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Bruno Oliveira +* Garvit Shubham +* Mahesh Vashishtha +* Ramsey +* Ronny Pfannschmidt +* Teejay +* q0w +* vin01 + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.3.0.rst b/doc/en/announce/release-7.3.0.rst new file mode 100644 index 000000000..33258daba --- /dev/null +++ b/doc/en/announce/release-7.3.0.rst @@ -0,0 +1,130 @@ +pytest-7.3.0 +======================================= + +The pytest team is proud to announce the 7.3.0 release! + +This release contains new features, improvements, and bug fixes, +the full list of changes is available in the changelog: + + https://docs.pytest.org/en/stable/changelog.html + +For complete documentation, please visit: + + https://docs.pytest.org/en/stable/ + +As usual, you can upgrade from PyPI via: + + pip install -U pytest + +Thanks to all of the contributors to this release: + +* Aaron Berdy +* Adam Turner +* Albert Villanova del Moral +* Alessio Izzo +* Alex Hadley +* Alice Purcell +* Anthony Sottile +* Anton Yakutovich +* Ashish Kurmi +* Babak Keyvani +* Billy +* Brandon Chinn +* Bruno Oliveira +* Cal Jacobson +* Chanvin Xiao +* Cheuk Ting Ho +* Chris Wheeler +* Daniel Garcia Moreno +* Daniel Scheffler +* Daniel Valenzuela +* EmptyRabbit +* Ezio Melotti +* Felix Hofstätter +* Florian Best +* Florian Bruhin +* Fredrik Berndtsson +* Gabriel Landau +* Garvit Shubham +* Gergely Kalmár +* HTRafal +* Hugo van Kemenade +* Ilya Konstantinov +* Itxaso Aizpurua +* James Gerity +* Jay +* John Litborn +* Jon Parise +* Jouke Witteveen +* Kadino +* Kevin C +* Kian Eliasi +* Klaus Rettinghaus +* Kodi Arfer +* Mahesh Vashishtha +* Manuel Jacob +* Marko Pacak +* MatthewFlamm +* Miro Hrončok +* Nate Meyvis +* Neil Girdhar +* Nhieuvu1802 +* Nipunn Koorapati +* Ofek Lev +* Paul Kehrer +* Paul Müller +* Paul Reece +* Pax +* Pete Baughman +* Peyman Salehi +* Philipp A +* Pierre Sassoulas +* Prerak Patel +* Ramsey +* Ran Benita +* Robert O'Shea +* Ronny Pfannschmidt +* Rowin +* Ruth Comer +* Samuel Colvin +* Samuel Gaist +* Sandro Tosi +* Santiago Castro +* Shantanu +* Simon K +* Stefanie Molin +* Stephen Rosen +* Sviatoslav Sydorenko +* Tatiana Ovary +* Teejay +* Thierry Moisan +* Thomas Grainger +* Tim Hoffmann +* Tobias Diez +* Tony Narlock +* Vivaan Verma +* Wolfremium +* Yannick PÉROUX +* Yusuke Kadowaki +* Zac Hatfield-Dodds +* Zach OBrien +* aizpurua23a +* bitzge +* bluthej +* gresm +* holesch +* itxasos23 +* johnkangw +* q0w +* rdb +* s-padmanaban +* skhomuti +* sommersoft +* vin01 +* wim glenn +* wodny +* zx.qiu + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.3.1.rst b/doc/en/announce/release-7.3.1.rst new file mode 100644 index 000000000..e920fa8af --- /dev/null +++ b/doc/en/announce/release-7.3.1.rst @@ -0,0 +1,18 @@ +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 diff --git a/doc/en/announce/release-7.3.2.rst b/doc/en/announce/release-7.3.2.rst new file mode 100644 index 000000000..b3b112f0d --- /dev/null +++ b/doc/en/announce/release-7.3.2.rst @@ -0,0 +1,21 @@ +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 diff --git a/doc/en/announce/release-7.4.0.rst b/doc/en/announce/release-7.4.0.rst new file mode 100644 index 000000000..5a0d18267 --- /dev/null +++ b/doc/en/announce/release-7.4.0.rst @@ -0,0 +1,49 @@ +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 diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index 64bcbf5bd..ea0c6a71a 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -92,3 +92,5 @@ pytest version min. Python version 5.0 - 6.1 3.5+ 3.3 - 4.6 2.7, 3.4+ ============== =================== + +`Status of Python Versions `__. diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 7e2200224..53305eecd 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -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:510 + cache -- .../_pytest/cacheprovider.py:528 Return a cache object that can persist state between testing sessions. cache.get(key, default) @@ -33,25 +33,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Values can be any object handled by the json stdlib module. - capsys -- .../_pytest/capture.py:878 - Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. - - The captured output is made available via ``capsys.readouterr()`` method - calls, which return a ``(out, err)`` namedtuple. - ``out`` and ``err`` will be ``text`` objects. - - Returns an instance of :class:`CaptureFixture[str] `. - - Example: - - .. code-block:: python - - def test_output(capsys): - print("hello") - captured = capsys.readouterr() - assert captured.out == "hello\n" - - capsysbinary -- .../_pytest/capture.py:906 + capsysbinary -- .../_pytest/capture.py:1001 Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` @@ -69,7 +51,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capsysbinary.readouterr() assert captured.out == b"hello\n" - capfd -- .../_pytest/capture.py:934 + capfd -- .../_pytest/capture.py:1029 Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -87,7 +69,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capfd.readouterr() assert captured.out == "hello\n" - capfdbinary -- .../_pytest/capture.py:962 + capfdbinary -- .../_pytest/capture.py:1057 Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -105,7 +87,25 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capfdbinary.readouterr() assert captured.out == b"hello\n" - doctest_namespace [session scope] -- .../_pytest/doctest.py:735 + capsys -- .../_pytest/capture.py:973 + Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. + + The captured output is made available via ``capsys.readouterr()`` method + calls, which return a ``(out, err)`` namedtuple. + ``out`` and ``err`` will be ``text`` objects. + + Returns an instance of :class:`CaptureFixture[str] `. + + Example: + + .. code-block:: python + + def test_output(capsys): + print("hello") + captured = capsys.readouterr() + assert captured.out == "hello\n" + + doctest_namespace [session scope] -- .../_pytest/doctest.py:737 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. @@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a For more details: :ref:`doctest_namespace`. - pytestconfig [session scope] -- .../_pytest/fixtures.py:1344 + pytestconfig [session scope] -- .../_pytest/fixtures.py:1353 Session-scoped fixture that returns the session's :class:`pytest.Config` object. @@ -163,7 +163,10 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a record_testsuite_property("ARCH", "PPC") record_testsuite_property("STORAGE_TYPE", "CEPH") - ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. + :param name: + The property name. + :param value: + The property value. Will be converted to a string. .. warning:: @@ -193,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:487 + caplog -- .../_pytest/logging.py:570 Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -204,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:29 + monkeypatch -- .../_pytest/monkeypatch.py:30 A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -228,23 +231,25 @@ 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() `. - recwarn -- .../_pytest/recwarn.py:29 + recwarn -- .../_pytest/recwarn.py:30 Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. - See https://docs.python.org/library/how-to/capture-warnings.html for information + 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:184 + tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:245 Return a :class:`pytest.TempPathFactory` instance for the test session. - tmp_path -- .../_pytest/tmpdir.py:199 + tmp_path -- .../_pytest/tmpdir.py:260 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. By default, a new base temporary directory is created each test session, - and old bases are removed after 3 sessions, to aid in debugging. If - ``--basetemp`` is used then it is cleared each session. See :ref:`base + and old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See :ref:`base temporary directory`. The returned object is a :class:`pathlib.Path` object. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index ef3a241cd..391721df3 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,421 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.4.0 (2023-06-23) +========================= + +Features +-------- + +- `#10901 `_: Added :func:`ExceptionInfo.from_exception() `, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception. + This can replace :func:`ExceptionInfo.from_exc_info() ` for most uses. + + + +Improvements +------------ + +- `#10872 `_: Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook. + + +- `#10907 `_: When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown: + + "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.". + + Previously, the last frame of the traceback was shown, even though it was hidden. + + +- `#10940 `_: Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output. + + Added ``TerminalReporter.wrap_write()`` as a helper for that. + + +- `#10991 `_: Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``. + + +- `#11005 `_: Added the underlying exception to the cache provider's path creation and write warning messages. + + +- `#11013 `_: Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory. + + +- `#11043 `_: When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir `. + Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem. + If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`. + + +- `#11081 `_: The :confval:`norecursedirs` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it. + + If after updating to this version you see that your `norecursedirs` setting is not being respected, + it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation. + Most likely, your hook returns `False` for paths it does not want to ignore, + which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path. + The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore. + + +- `#8711 `_: :func:`caplog.set_level() ` and :func:`caplog.at_level() ` + will temporarily enable the requested ``level`` if ``level`` was disabled globally via + ``logging.disable(LEVEL)``. + + + +Bug Fixes +--------- + +- `#10831 `_: Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``. + + +- `#11068 `_: Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files `. + + +- `#11104 `_: Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests, + even when it was not utilized (e.g. when explicit paths were given on the command line). + Now the ``testpaths`` are only considered when they are in use. + + +- `#1904 `_: Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message). + + +- `#7781 `_: Fix writing non-encodable text to log file when using ``--debug``. + + + +Improved Documentation +---------------------- + +- `#9146 `_: Improved documentation for :func:`caplog.set_level() `. + + + +Trivial/Internal Changes +------------------------ + +- `#11031 `_: Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file. + + +pytest 7.3.2 (2023-06-10) +========================= + +Bug Fixes +--------- + +- `#10169 `_: Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems. + + +- `#10894 `_: Support for Python 3.12 (beta at the time of writing). + + +- `#10987 `_: :confval:`testpaths` is now honored to load root ``conftests``. + + +- `#10999 `_: The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments. + + +- `#11028 `_: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call. + + +- `#11054 `_: Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files). + + +pytest 7.3.1 (2023-04-14) +========================= + +Improvements +------------ + +- `#10875 `_: Python 3.12 support: fixed ``RuntimeError: TestResult has no addDuration method`` when running ``unittest`` tests. + + +- `#10890 `_: Python 3.12 support: fixed ``shutil.rmtree(onerror=...)`` deprecation warning when using :fixture:`tmp_path`. + + + +Bug Fixes +--------- + +- `#10896 `_: Fixed performance regression related to :fixture:`tmp_path` and the new :confval:`tmp_path_retention_policy` option. + + +- `#10903 `_: Fix crash ``INTERNALERROR IndexError: list index out of range`` which happens when displaying an exception where all entries are hidden. + This reverts the change "Correctly handle ``__tracebackhide__`` for chained exceptions." introduced in version 7.3.0. + + +pytest 7.3.0 (2023-04-08) +========================= + +Features +-------- + +- `#10525 `_: Test methods decorated with ``@classmethod`` can now be discovered as tests, following the same rules as normal methods. This fills the gap that static methods were discoverable as tests but not class methods. + + +- `#10755 `_: :confval:`console_output_style` now supports ``progress-even-when-capture-no`` to force the use of the progress output even when capture is disabled. This is useful in large test suites where capture may have significant performance impact. + + +- `#7431 `_: ``--log-disable`` CLI option added to disable individual loggers. + + +- `#8141 `_: Added :confval:`tmp_path_retention_count` and :confval:`tmp_path_retention_policy` configuration options to control how directories created by the :fixture:`tmp_path` fixture are kept. + + + +Improvements +------------ + +- `#10226 `_: If multiple errors are raised in teardown, we now re-raise an ``ExceptionGroup`` of them instead of discarding all but the last. + + +- `#10658 `_: Allow ``-p`` arguments to include spaces (eg: ``-p no:logging`` instead of + ``-pno:logging``). Mostly useful in the ``addopts`` section of the configuration + file. + + +- `#10710 `_: Added ``start`` and ``stop`` timestamps to ``TestReport`` objects. + + +- `#10727 `_: Split the report header for ``rootdir``, ``config file`` and ``testpaths`` so each has its own line. + + +- `#10840 `_: pytest should no longer crash on AST with pathological position attributes, for example testing AST produced by `Hylang __`. + + +- `#6267 `_: The full output of a test is no longer truncated if the truncation message would be longer than + the hidden text. The line number shown has also been fixed. + + + +Bug Fixes +--------- + +- `#10743 `_: The assertion rewriting mechanism now works correctly when assertion expressions contain the walrus operator. + + +- `#10765 `_: Fixed :fixture:`tmp_path` fixture always raising :class:`OSError` on ``emscripten`` platform due to missing :func:`os.getuid`. + + +- `#1904 `_: Correctly handle ``__tracebackhide__`` for chained exceptions. + NOTE: This change was reverted in version 7.3.1. + + + +Improved Documentation +---------------------- + +- `#10782 `_: Fixed the minimal example in :ref:`goodpractices`: ``pip install -e .`` requires a ``version`` entry in ``pyproject.toml`` to run successfully. + + + +Trivial/Internal Changes +------------------------ + +- `#10669 `_: pytest no longer directly depends on the `attrs `__ package. While + we at pytest all love the package dearly and would like to thank the ``attrs`` team for many years of cooperation and support, + it makes sense for ``pytest`` to have as little external dependencies as possible, as this helps downstream projects. + With that in mind, we have replaced the pytest's limited internal usage to use the standard library's ``dataclasses`` instead. + + Nice diffs for ``attrs`` classes are still supported though. + + +pytest 7.2.2 (2023-03-03) +========================= + +Bug Fixes +--------- + +- `#10533 `_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`. + + +- `#10592 `_: Fixed crash if `--cache-show` and `--help` are passed at the same time. + + +- `#10597 `_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage. + + +- `#10626 `_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time. + + +- `#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 `_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation. + + +- `#10721 `_: Fixed entry-points declaration in the documentation example using Hatch. + + +- `#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 `_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12. + + +- `#10457 `_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location. + + +- `#10506 `_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir ` on Windows. + + +- `#10607 `_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel. + + +- `#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) +========================= + +Deprecations +------------ + +- `#10012 `_: Update :class:`pytest.PytestUnhandledCoroutineWarning` to a deprecation; it will raise an error in pytest 8. + + +- `#10396 `_: pytest no longer depends on the ``py`` library. ``pytest`` provides a vendored copy of ``py.error`` and ``py.path`` modules but will use the ``py`` library if it is installed. If you need other ``py.*`` modules, continue to install the deprecated ``py`` library separately, otherwise it can usually be removed as a dependency. + + +- `#4562 `_: Deprecate configuring hook specs/impls using attributes/marks. + + Instead use :py:func:`pytest.hookimpl` and :py:func:`pytest.hookspec`. + For more details, see the :ref:`docs `. + + +- `#9886 `_: The functionality for running tests written for ``nose`` has been officially deprecated. + + This includes: + + * Plain ``setup`` and ``teardown`` functions and methods: this might catch users by surprise, as ``setup()`` and ``teardown()`` are not pytest idioms, but part of the ``nose`` support. + * Setup/teardown using the `@with_setup `_ decorator. + + For more details, consult the :ref:`deprecation docs `. + + .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup + +- `#7337 `_: A deprecation warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. The plan is to make returning non-`None` from tests an error in the future. + + +Features +-------- + +- `#9897 `_: Added shell-style wildcard support to ``testpaths``. + + + +Improvements +------------ + +- `#10218 `_: ``@pytest.mark.parametrize()`` (and similar functions) now accepts any ``Sequence[str]`` for the argument names, + instead of just ``list[str]`` and ``tuple[str, ...]``. + + (Note that ``str``, which is itself a ``Sequence[str]``, is still treated as a + comma-delimited name list, as before). + + +- `#10381 `_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``. + + +- `#3426 `_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expressed instead. + + +- `#8508 `_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and + enhance match comparison for :py:func:`_pytest._code.ExceptionInfo.match` as returned by :py:func:`pytest.raises`. + + +- `#8646 `_: Improve :py:func:`pytest.raises`. Previously passing an empty tuple would give a confusing + error. We now raise immediately with a more helpful message. + + +- `#9741 `_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML. + + :mod:`tomli` is no longer a dependency on Python 3.11. + + +- `#9742 `_: Display assertion message without escaped newline characters with ``-vv``. + + +- `#9823 `_: Improved error message that is shown when no collector is found for a given file. + + +- `#9873 `_: Some coloring has been added to the short test summary. + + +- `#9883 `_: Normalize the help description of all command-line options. + + +- `#9920 `_: Display full crash messages in ``short test summary info``, when running in a CI environment. + + +- `#9987 `_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``. + + + +Bug Fixes +--------- + +- `#10150 `_: :data:`sys.stdin` now contains all expected methods of a file-like object when capture is enabled. + + +- `#10382 `_: Do not break into pdb when ``raise unittest.SkipTest()`` appears top-level in a file. + + +- `#7792 `_: Marks are now inherited according to the full MRO in test classes. Previously, if a test class inherited from two or more classes, only marks from the first super-class would apply. + + When inheriting marks from super-classes, marks from the sub-classes are now ordered before marks from the super-classes, in MRO order. Previously it was the reverse. + + When inheriting marks from super-classes, the `pytestmark` attribute of the sub-class now only contains the marks directly applied to it. Previously, it also contained marks from its super-classes. Please note that this attribute should not normally be accessed directly; use :func:`pytest.Node.iter_markers` instead. + + +- `#9159 `_: Showing inner exceptions by forcing native display in ``ExceptionGroups`` even when using display options other than ``--tb=native``. A temporary step before full implementation of pytest-native display for inner exceptions in ``ExceptionGroups``. + + +- `#9877 `_: Ensure ``caplog.get_records(when)`` returns current/correct data after invoking ``caplog.clear()``. + + + +Improved Documentation +---------------------- + +- `#10344 `_: Update information on writing plugins to use ``pyproject.toml`` instead of ``setup.py``. + + +- `#9248 `_: The documentation is now built using Sphinx 5.x (up from 3.x previously). + + +- `#9291 `_: Update documentation on how :func:`pytest.warns` affects :class:`DeprecationWarning`. + + + +Trivial/Internal Changes +------------------------ + +- `#10313 `_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for + type check and runtime purposes. Made `_pytest.doctest` use internal APIs + to avoid circular imports. + + +- `#9906 `_: Made ``_pytest.compat`` re-export ``importlib_metadata`` in the eyes of type checkers. + + +- `#9910 `_: Fix default encoding warning (``EncodingWarning``) in ``cacheprovider`` + + +- `#9984 `_: Improve the error message when we attempt to access a fixture that has been + torn down. + Add an additional sentence to the docstring explaining when it's not a good + idea to call ``getfixturevalue``. + + pytest 7.1.3 (2022-08-31) ========================= @@ -268,7 +683,7 @@ Breaking Changes - `#7259 `_: The :ref:`Node.reportinfo() ` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`. Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation. - Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted. + Since `py.path.local` is an `os.PathLike[str]`, these plugins are unaffacted. Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`. Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead. @@ -3768,7 +4183,7 @@ Removals See our :ref:`docs ` on information on how to update your code. -- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check. +- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than an existence check. Use ``Node.get_closest_marker(name)`` as a replacement. diff --git a/doc/en/conf.py b/doc/en/conf.py index 5184ee7b1..32f508219 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -341,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 a ISBN number +# The unique identifier of the text. This can be an ISBN number # or the project homepage. # epub_identifier = '' diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 17055dfb9..3e15f892e 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -486,12 +486,26 @@ The ``yield_fixture`` function/decorator It has been so for a very long time, so can be search/replaced safely. -Removed Features ----------------- +Removed Features and Breaking Changes +------------------------------------- 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. + + +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 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1071,7 +1085,7 @@ that are then turned into proper test methods. Example: .. code-block:: python def check(x, y): - assert x ** x == y + assert x**x == y def test_squared(): @@ -1086,7 +1100,7 @@ This form of test function doesn't support fixtures properly, and users should s @pytest.mark.parametrize("x, y", [(2, 4), (3, 9)]) def test_squared(x, y): - assert x ** x == y + assert x**x == y .. _internal classes accessed through node deprecated: diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py index b3512c2a6..e76e3f93c 100644 --- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -17,7 +17,7 @@ def b(a, order): @pytest.fixture -def c(a, b, order): +def c(b, order): order.append("c") diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 34d96c2bc..55fd1f576 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -246,9 +246,9 @@ You can ask which markers exist for your test suite - the list includes our just @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures - @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead. - @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead. For an example on how to add and work with markers from a plugin, see @@ -438,9 +438,9 @@ The ``--markers`` option always gives you a list of available markers: @pytest.mark.usefixtures(fixturename1, fixturename2, ...): mark tests as needing all of the specified fixtures. see https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures - @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. + @pytest.mark.tryfirst: mark a hook implementation function such that the plugin machinery will try to call it first/as early as possible. DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead. - @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. + @pytest.mark.trylast: mark a hook implementation function such that the plugin machinery will try to call it last/as late as possible. DEPRECATED, use @pytest.hookimpl(trylast=True) instead. .. _`passing callables to custom markers`: @@ -611,7 +611,7 @@ then you will see two tests skipped and two executed tests as expected: test_plat.py s.s. [100%] ========================= short test summary info ========================== - SKIPPED [2] conftest.py:12: cannot run on platform linux + SKIPPED [2] conftest.py:13: cannot run on platform linux ======================= 2 passed, 2 skipped in 0.12s ======================= Note that if you specify a platform via the marker-command line option like this: diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index bc39a1f6b..dd1ebe88d 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -38,6 +38,7 @@ 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}" diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index f81476839..df2859b14 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -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:29: 'python3.5' not found - SKIPPED [9] multipython.py:29: 'python3.6' not found - SKIPPED [9] multipython.py:29: 'python3.7' not found + 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 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] conftest.py:12: could not import 'opt2': No module named 'opt2' + SKIPPED [1] test_module.py:3: 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 diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index b9c2386ac..2451e3cab 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -148,7 +148,8 @@ The test collection would look like this: $ pytest --collect-only =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + rootdir: /home/sweet/project + configfile: pytest.ini collected 2 items @@ -209,7 +210,8 @@ You can always peek at the collection tree without running tests like this: . $ pytest --collect-only pythoncollection.py =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + rootdir: /home/sweet/project + configfile: pytest.ini collected 3 items @@ -290,7 +292,8 @@ file will be left out: $ pytest --collect-only =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-7.x.y, pluggy-1.x.y - rootdir: /home/sweet/project, configfile: pytest.ini + rootdir: /home/sweet/project + configfile: pytest.ini collected 0 items ======================= no tests collected in 0.12s ======================== diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst index 4c1ae1c05..cb59c4b42 100644 --- a/doc/en/example/reportingdemo.rst +++ b/doc/en/example/reportingdemo.rst @@ -144,7 +144,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: E 1 E 1... E - E ...Full output truncated (7 lines hidden), use '-vv' to show + E ...Full output truncated (6 lines hidden), use '-vv' to show failure_demo.py:60: AssertionError _________________ TestSpecialisedExplanations.test_eq_list _________________ @@ -184,9 +184,8 @@ Here is a nice run of several failures and how ``pytest`` presents things: E Left contains 1 more item: E {'c': 0} E Right contains 1 more item: - E {'d': 0}... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E {'d': 0} + E Use -v to get more diff failure_demo.py:71: AssertionError _________________ TestSpecialisedExplanations.test_eq_set __________________ @@ -195,16 +194,15 @@ Here is a nice run of several failures and how ``pytest`` presents things: def test_eq_set(self): > assert {0, 10, 11, 12} == {0, 20, 21} - E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21} + E assert {0, 10, 11, 12} == {0, 20, 21} E Extra items in the left set: E 10 E 11 E 12 E Extra items in the right set: E 20 - E 21... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E 21 + E Use -v to get more diff failure_demo.py:74: AssertionError _____________ TestSpecialisedExplanations.test_eq_longer_list ______________ @@ -241,9 +239,8 @@ Here is a nice run of several failures and how ``pytest`` presents things: E which E includes foo E ? +++ - E and a... - E - E ...Full output truncated (2 lines hidden), use '-vv' to show + E and a + E tail failure_demo.py:84: AssertionError ___________ TestSpecialisedExplanations.test_not_in_text_single ____________ @@ -307,9 +304,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: E ['b'] E E Drill down into differing attribute b: - E b: 'b' != 'c'... - E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E b: 'b' != 'c' + E - c + E + b failure_demo.py:108: AssertionError ________________ TestSpecialisedExplanations.test_eq_attrs _________________ @@ -334,9 +331,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: E ['b'] E E Drill down into differing attribute b: - E b: 'b' != 'c'... - E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E b: 'b' != 'c' + E - c + E + b failure_demo.py:120: AssertionError ______________________________ test_attribute ______________________________ @@ -673,7 +670,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list - asser... FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_list_long - ... FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_dict - Asser... - FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - Assert... + FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_set - assert... FAILED failure_demo.py::TestSpecialisedExplanations::test_eq_longer_list FAILED failure_demo.py::TestSpecialisedExplanations::test_in_list - asser... FAILED failure_demo.py::TestSpecialisedExplanations::test_not_in_text_multiline diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index ba1acdfd1..97a6dd9f4 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -661,8 +661,7 @@ If we run this: test_step.py:11: AssertionError ========================= short test summary info ========================== - XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) + XFAIL test_step.py::TestUserHandling::test_deletion - reason: previous test failed (test_modification) ================== 1 failed, 2 passed, 1 xfailed in 0.12s ================== We'll see that ``test_deletion`` was not executed because ``test_modification`` @@ -692,7 +691,7 @@ Here is an example for making a ``db`` fixture available in a directory: pass - @pytest.fixture(scope="session") + @pytest.fixture(scope="package") def db(): return DB() @@ -893,8 +892,11 @@ here is a little example implemented via a local plugin: .. code-block:: python # content of conftest.py - + from typing import Dict import pytest + from pytest import StashKey, CollectReport + + phase_report_key = StashKey[Dict[str, CollectReport]]() @pytest.hookimpl(tryfirst=True, hookwrapper=True) @@ -903,10 +905,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 @@ -914,11 +915,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: @@ -956,8 +957,8 @@ and run it: rootdir: /home/sweet/project collected 3 items - test_module.py Esetting up a test failed! test_module.py::test_setup_fails - Fexecuting test failed test_module.py::test_call_fails + test_module.py Esetting up a test failed or skipped test_module.py::test_setup_fails + Fexecuting test failed or skipped test_module.py::test_call_fails F ================================== ERRORS ================================== diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 5277c2ac5..efde420cd 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -22,10 +22,11 @@ The first few lines should look like this: requires = ["hatchling"] build-backend = "hatchling.build" - [metadata] + [project] name = "PACKAGENAME" + version = "PACKAGEVERSION" -where ``PACKAGENAME`` is the name of your package. +where ``PACKAGENAME`` and ``PACKAGEVERSION`` are the name and version of your package respectively. You can then install your package in "editable" mode by running from the same directory: @@ -50,8 +51,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`. @@ -270,8 +271,8 @@ tox Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into :doc:`tox `, the -virtualenv test automation tool and its :doc:`pytest support `. -tox helps you to setup virtualenv environments with pre-defined +virtualenv test automation tool. +``tox`` helps you to setup virtualenv environments with pre-defined dependencies and then executing a pre-configured test command with options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging @@ -293,3 +294,20 @@ See also `pypa/setuptools#1684 ` setuptools intends to `remove the test command `_. + +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 `_ 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 `_. + +.. 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. diff --git a/doc/en/explanation/pythonpath.rst b/doc/en/explanation/pythonpath.rst index e1d240b49..5b533f47f 100644 --- a/doc/en/explanation/pythonpath.rst +++ b/doc/en/explanation/pythonpath.rst @@ -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:`__import__ <__import__>` builtin. + of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module ` function. 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 ``__import__``. + there, and imported with :func:`importlib.import_module `. 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 :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 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`. For this reason this doesn't require test module names to be unique. diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index ce8ce8152..e295c1804 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 7.1.3 + pytest 7.4.0 .. _`simpletest`: diff --git a/doc/en/how-to/cache.rst b/doc/en/how-to/cache.rst index 529d97d53..8554a984c 100644 --- a/doc/en/how-to/cache.rst +++ b/doc/en/how-to/cache.rst @@ -233,7 +233,7 @@ If you run this command for the first time, you can see the print statement: > assert mydata == 23 E assert 42 == 23 - test_caching.py:20: AssertionError + test_caching.py:19: AssertionError -------------------------- Captured stdout setup --------------------------- running expensive computation... ========================= short test summary info ========================== @@ -256,7 +256,7 @@ the cache and nothing will be printed: > assert mydata == 23 E assert 42 == 23 - test_caching.py:20: AssertionError + test_caching.py:19: AssertionError ========================= short test summary info ========================== FAILED test_caching.py::test_function - assert 42 == 23 1 failed in 0.12s diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index 91565002c..0390230b8 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -109,6 +109,18 @@ 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`` @@ -270,20 +282,34 @@ which works in a similar manner to :ref:`raises ` (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:: +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``. - >>> with warns(UserWarning, match='must be 0 or None'): +Some examples: + +.. code-block:: pycon + + + >>> 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 diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 5c9bad086..e9f7f2d4b 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1237,7 +1237,6 @@ If the data created by the factory requires managing, the fixture can take care @pytest.fixture def make_customer_record(): - created_records = [] def _make_customer_record(name): diff --git a/doc/en/how-to/logging.rst b/doc/en/how-to/logging.rst index c99835a2b..9957a9bb8 100644 --- a/doc/en/how-to/logging.rst +++ b/doc/en/how-to/logging.rst @@ -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: diff --git a/doc/en/how-to/monkeypatch.rst b/doc/en/how-to/monkeypatch.rst index 81edd00bd..a9504dcb3 100644 --- a/doc/en/how-to/monkeypatch.rst +++ b/doc/en/how-to/monkeypatch.rst @@ -135,10 +135,10 @@ This can be done in our test file by defining a class to represent ``r``. # this is the previous code block example import app + # custom class to be the mock return value # will override the requests.Response returned from requests.get class MockResponse: - # mock json() method always returns a specific testing dictionary @staticmethod def json(): @@ -146,7 +146,6 @@ This can be done in our test file by defining a class to represent ``r``. def test_get_json(monkeypatch): - # Any arguments may be passed and mock_get() will always return our # mocked object, which only has the .json() method. def mock_get(*args, **kwargs): @@ -181,6 +180,7 @@ This mock can be shared across tests using a ``fixture``: # app.py that includes the get_json() function import app + # custom class to be the mock return value of requests.get() class MockResponse: @staticmethod @@ -358,7 +358,6 @@ For testing purposes we can patch the ``DEFAULT_CONFIG`` dictionary to specific def test_connection(monkeypatch): - # Patch the values of DEFAULT_CONFIG to specific # testing values only for this test. monkeypatch.setitem(app.DEFAULT_CONFIG, "user", "test_user") @@ -383,7 +382,6 @@ You can use the :py:meth:`monkeypatch.delitem ` to remove v def test_missing_user(monkeypatch): - # patch the DEFAULT_CONFIG t be missing the 'user' key monkeypatch.delitem(app.DEFAULT_CONFIG, "user", raising=False) @@ -404,6 +402,7 @@ separate fixtures for each potential mock and reference them in the needed tests # app.py with the connection string function import app + # all of the mocks are moved into separated fixtures @pytest.fixture def mock_test_user(monkeypatch): @@ -425,7 +424,6 @@ separate fixtures for each potential mock and reference them in the needed tests # tests reference only the fixture mocks that are needed def test_connection(mock_test_user, mock_test_database): - expected = "User Id=test_user; Location=test_db;" result = app.create_connection_string() @@ -433,7 +431,6 @@ separate fixtures for each potential mock and reference them in the needed tests def test_missing_user(mock_missing_default_user): - with pytest.raises(KeyError): _ = app.create_connection_string() diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index dc3a86ba2..e8e9af0c7 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -12,8 +12,9 @@ Examples for modifying traceback printing: .. code-block:: bash - pytest --showlocals # show local variables in tracebacks - pytest -l # show local variables (shortcut) + pytest --showlocals # show local variables in tracebacks + pytest -l # show local variables (shortcut) + pytest --no-showlocals # hide local variables (if addopts enables them) pytest --tb=auto # (default) 'long' tracebacks for the first and last # entry, but 'short' style for the other entries @@ -166,9 +167,9 @@ Now we can increase pytest's verbosity: E Right contains 4 more items: E {'10': 10, '20': 20, '30': 30, '40': 40} E Full diff: - E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40}... - E - E ...Full output truncated (3 lines hidden), use '-vv' to show + E - {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} + E ? - - - - - - - - + E + {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} test_verbosity_example.py:14: AssertionError ___________________________ test_long_text_fail ____________________________ @@ -348,8 +349,7 @@ Example: test_example.py:14: AssertionError ========================= short test summary info ========================== SKIPPED [1] test_example.py:22: skipping this test - XFAIL test_example.py::test_xfail - reason: xfailing this test + XFAIL test_example.py::test_xfail - reason: xfailing this test XPASS test_example.py::test_xpass always xfail ERROR test_example.py::test_error - assert 0 FAILED test_example.py::test_fail - assert 0 diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst index a2f936d33..792933dd8 100644 --- a/doc/en/how-to/tmp_path.rst +++ b/doc/en/how-to/tmp_path.rst @@ -131,10 +131,12 @@ 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, entries older than 3 temporary directories will be removed. +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. diff --git a/doc/en/how-to/unittest.rst b/doc/en/how-to/unittest.rst index 658c69a14..37caf6e9f 100644 --- a/doc/en/how-to/unittest.rst +++ b/doc/en/how-to/unittest.rst @@ -157,7 +157,7 @@ the ``self.db`` values in the traceback: E AssertionError: .DummyDB object at 0xdeadbeef0001> E assert 0 - test_unittest_db.py:10: AssertionError + test_unittest_db.py:11: AssertionError ___________________________ MyTest.test_method2 ____________________________ self = @@ -167,7 +167,7 @@ the ``self.db`` values in the traceback: E AssertionError: .DummyDB object at 0xdeadbeef0001> E assert 0 - test_unittest_db.py:13: AssertionError + test_unittest_db.py:14: AssertionError ========================= short test summary info ========================== FAILED test_unittest_db.py::MyTest::test_method1 - AssertionError: `__ (German), `October 28th `__ (sold out) and `November 4th `__, online - - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany + - `pytest tips and tricks for a better testsuite `_, at `Europython 2023 `_, July 18th (3h), Prague/Remote + - `Professional Testing with Python `_, via `Python Academy `_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote Also see :doc:`previous talks and blogposts `. diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst index d25979ab9..01f825222 100644 --- a/doc/en/reference/fixtures.rst +++ b/doc/en/reference/fixtures.rst @@ -335,7 +335,7 @@ For example: .. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py -If we map out what depends on what, we get something that look like this: +If we map out what depends on what, we get something that looks like this: .. image:: /example/fixtures/test_fixtures_order_dependencies.* :align: center diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 3dff88e59..c882130b0 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -5,1142 +5,1307 @@ Plugin List =========== PyPI projects that match "pytest-\*" are considered plugins and are listed -automatically. Packages classified as inactive are excluded. +automatically together with a manually-maintained list in `the source +code `_. +Packages classified as inactive are excluded. .. The following conditional uses a different format for this list when creating a PDF, because otherwise the table gets far too wide for the page. -This list contains 1118 plugins. +This list contains 1267 plugins. .. only:: not latex - =============================================== ============================================================================================================================================================================ ============== ===================== ================================================ - name summary last release status requires - =============================================== ============================================================================================================================================================================ ============== ===================== ================================================ - :pypi:`pytest-abstracts` A contextmanager pytest fixture for handling multiple mock abstracts May 25, 2022 N/A N/A - :pypi:`pytest-accept` A pytest-plugin for updating doctest outputs Jan 07, 2022 N/A pytest (>=6,<8) - :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Jun 07, 2022 N/A pytest (>=5.4.0) - :pypi:`pytest-addons-test` 用于测试pytest的插件 Aug 02, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-adf` Pytest plugin for writing Azure Data Factory integration tests May 10, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-adf-azure-identity` Pytest plugin for writing Azure Data Factory integration tests Mar 06, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-agent` Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way. Nov 25, 2021 N/A N/A - :pypi:`pytest-aggreport` pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Mar 07, 2021 4 - Beta pytest (>=6.2.2) - :pypi:`pytest-aio` Pytest plugin for testing async python code Oct 20, 2021 4 - Beta pytest - :pypi:`pytest-aiofiles` pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A - :pypi:`pytest-aiohttp` Pytest plugin for aiohttp support Feb 12, 2022 4 - Beta pytest (>=6.1.0) - :pypi:`pytest-aiohttp-client` Pytest \`client\` fixture for the Aiohttp Nov 01, 2020 N/A pytest (>=6) - :pypi:`pytest-aiomoto` pytest-aiomoto Jul 10, 2022 N/A pytest (>=7.0,<8.0) - :pypi:`pytest-aioresponses` py.test integration for aioresponses Jul 29, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-aioworkers` A plugin to test aioworkers project with pytest Dec 04, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-airflow` pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) - :pypi:`pytest-airflow-utils` Nov 15, 2021 N/A N/A - :pypi:`pytest-alembic` A pytest plugin for verifying alembic migrations. Aug 03, 2022 N/A pytest (>=1.0) - :pypi:`pytest-allclose` Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest - :pypi:`pytest-allure-adaptor` Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) - :pypi:`pytest-allure-adaptor2` Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) - :pypi:`pytest-allure-collection` pytest plugin to collect allure markers without running any tests Sep 08, 2022 N/A pytest - :pypi:`pytest-allure-dsl` pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest - :pypi:`pytest-allure-spec-coverage` The pytest plugin aimed to display test coverage of the specs(requirements) in Allure Oct 26, 2021 N/A pytest - :pypi:`pytest-alphamoon` Static code checks used at Alphamoon Dec 30, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest - :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Jul 31, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Jun 07, 2022 3 - Alpha pytest (<8.0.0,>=3.2.0) - :pypi:`pytest-ansible` Plugin for py.test to simplify calling ansible modules from tests or fixtures May 25, 2021 5 - Production/Stable N/A - :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A - :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) - :pypi:`pytest-ansible-units` A pytest plugin for running unit tests within an ansible collection Apr 14, 2022 N/A N/A - :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Jul 05, 2022 5 - Production/Stable pytest - :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest - :pypi:`pytest-anything` Pytest fixtures to assert anything and something Feb 18, 2021 N/A N/A - :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Nov 23, 2021 N/A pytest ; extra == 'test' - :pypi:`pytest-aoreporter` pytest report Jun 27, 2022 N/A N/A - :pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0) - :pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A - :pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A - :pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A - :pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A - :pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest May 08, 2022 4 - Beta pytest (>=7.0.1) - :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) - :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Jan 13, 2022 4 - Beta pytest (>=4.6) - :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) - :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A - :pypi:`pytest-assertcount` Plugin to count actual number of asserts in pytest May 22, 2022 N/A N/A - :pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A - :pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A - :pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A - :pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7) - :pypi:`pytest-assurka` A pytest plugin for Assurka Studio Aug 04, 2022 N/A N/A - :pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A - :pypi:`pytest-asteroid` PyTest plugin for docker-based testing on database images Aug 15, 2022 N/A pytest (>=6.2.5,<8.0.0) - :pypi:`pytest-astropy` Meta-package containing dependencies for testing Apr 12, 2022 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) - :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest - :pypi:`pytest-asyncio` Pytest support for asyncio Jul 15, 2022 4 - Beta pytest (>=6.1.0) - :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Jul 11, 2022 N/A N/A - :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) - :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) - :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A - :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A - :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A - :pypi:`pytest-autocap` automatically capture test & fixture stdout/stderr to files May 15, 2022 N/A pytest (<7.2,>=7.1.2) - :pypi:`pytest-autochecklog` automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A - :pypi:`pytest-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. May 20, 2022 N/A pytest (>=7.0.0) - :pypi:`pytest-automock` Pytest plugin for automatical mocks creation Aug 04, 2022 N/A pytest ; extra == 'dev' - :pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A - :pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest - :pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A - :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A - :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) - :pypi:`pytest-azure-devops` Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. Jun 20, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-azurepipelines` Formatting PyTest output for Azure Pipelines UI Mar 16, 2022 4 - Beta pytest (>=5.0.0) - :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-base-url` pytest plugin for URL based testing Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) - :pypi:`pytest-bdd` BDD for pytest Jul 07, 2022 6 - Mature pytest (>=5.0) - :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Jul 25, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) - :pypi:`pytest-bdd-ng` BDD for pytest Jul 24, 2022 4 - Beta pytest (>=5.0) - :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) - :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest - :pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A - :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A - :pypi:`pytest-benchmark` A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. Apr 17, 2021 5 - Production/Stable pytest (>=3.8) - :pypi:`pytest-bg-process` Pytest plugin to initialize background process Jan 24, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Jan 24, 2022 4 - Beta N/A - :pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Aug 05, 2021 N/A pytest (>=5.0) - :pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A - :pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' - :pypi:`pytest-blame` A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) - :pypi:`pytest-blender` Blender Pytest plugin. Jun 16, 2022 N/A pytest ; extra == 'dev' - :pypi:`pytest-blink1` Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A - :pypi:`pytest-blockage` Disable network requests during a test run. Dec 21, 2021 N/A pytest - :pypi:`pytest-blocker` pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A - :pypi:`pytest-blue` A pytest plugin that adds a \`blue\` fixture for printing stuff in blue. Sep 05, 2022 N/A N/A - :pypi:`pytest-board` Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A - :pypi:`pytest-bootstrap` Mar 04, 2022 N/A N/A - :pypi:`pytest-bpdb` A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A - :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Feb 15, 2022 N/A N/A - :pypi:`pytest-breakword` Use breakword with pytest Aug 04, 2021 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-breed-adapter` A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-briefcase` A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-browser` A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A - :pypi:`pytest-browsermob-proxy` BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A - :pypi:`pytest-browserstack-local` \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. Feb 09, 2018 N/A N/A - :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Apr 13, 2022 5 - Production/Stable pytest (>=6.2.0) - :pypi:`pytest-bugtong-tag` pytest-bugtong-tag is a plugin for pytest Jan 16, 2022 N/A N/A - :pypi:`pytest-bugzilla` py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A - :pypi:`pytest-bugzilla-notifier` A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) - :pypi:`pytest-buildkite` Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-builtin-types` Nov 17, 2021 N/A pytest - :pypi:`pytest-bwrap` Run your tests in Bubblewrap sandboxes Oct 26, 2018 3 - Alpha N/A - :pypi:`pytest-cache` pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A - :pypi:`pytest-cache-assert` Cache assertion data to simplify regression testing of complex serializable data May 02, 2022 4 - Beta pytest (>=5.0.0) - :pypi:`pytest-cagoule` Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A - :pypi:`pytest-cairo` Pytest support for cairo-lang and starknet Apr 17, 2022 N/A pytest - :pypi:`pytest-camel-collect` Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) - :pypi:`pytest-canonical-data` A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) - :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A - :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A - :pypi:`pytest-capture-warnings` pytest plugin to capture all warnings and put them in one file of your choice May 03, 2022 N/A pytest - :pypi:`pytest-cases` Separate test code from test cases in pytest. May 20, 2022 5 - Production/Stable N/A - :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A - :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) - :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest May 06, 2021 N/A N/A - :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A - :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A - :pypi:`pytest-change-demo` turn . into √,turn F into x Mar 02, 2022 N/A pytest - :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest - :pypi:`pytest-change-xds` turn . into √,turn F into x Apr 16, 2022 N/A pytest - :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) - :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Aug 25, 2022 5 - Production/Stable N/A - :pypi:`pytest-checkdocs` check the README when running tests Jul 31, 2021 5 - Production/Stable pytest (>=4.6) ; extra == 'testing' - :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) - :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A - :pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A - :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest (>=7.0) - :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest - :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) - :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A - :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Mar 26, 2019 N/A N/A - :pypi:`pytest-circleci-parallelized-rjp` Parallelize pytest across CircleCI workers. Jun 21, 2022 N/A pytest - :pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest - :pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A - :pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest May 06, 2019 N/A N/A - :pypi:`pytest-click` Pytest plugin for Click Feb 11, 2022 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-cli-fixtures` Automatically register fixtures for custom CLI arguments Jul 28, 2022 N/A pytest (~=7.0) - :pypi:`pytest-clld` Jul 06, 2022 N/A pytest (>=3.6) - :pypi:`pytest-cloud` Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A - :pypi:`pytest-cloudflare-worker` pytest plugin for testing cloudflare workers Mar 30, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-cloudist` Distribute tests to cloud machines without fuss Sep 02, 2022 4 - Beta pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-cobra` PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) - :pypi:`pytest-codecarbon` Pytest plugin for measuring carbon emissions Jun 15, 2022 N/A pytest - :pypi:`pytest-codecheckers` pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A - :pypi:`pytest-codecov` Pytest plugin for uploading pytest-cov results to codecov.io Apr 12, 2022 4 - Beta pytest (>=4.6.0) - :pypi:`pytest-codegen` Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-codeowners` Pytest plugin for selecting tests by GitHub CODEOWNERS. Mar 30, 2022 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-codestyle` pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A - :pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A - :pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A - :pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0) - :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A - :pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4) - :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method May 15, 2022 N/A pytest (>=3.6,<8) - :pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A - :pypi:`pytest-confluence-report` Package stands for pytest plugin to upload results into Confluence page. Apr 17, 2022 N/A N/A - :pypi:`pytest-console-scripts` Pytest plugin for testing console scripts Mar 18, 2022 4 - Beta N/A - :pypi:`pytest-consul` pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest - :pypi:`pytest-container` Pytest fixtures for writing container based tests Feb 01, 2022 3 - Alpha pytest (>=3.10) - :pypi:`pytest-contextfixture` Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A - :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A - :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 May 24, 2021 5 - Production/Stable pytest (>=3.3.0) - :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A - :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A - :pypi:`pytest-cov` Pytest plugin for measuring coverage. Oct 04, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-cover` Pytest plugin for measuring coverage. Forked from \`pytest-cov\`. Aug 01, 2015 5 - Production/Stable N/A - :pypi:`pytest-coverage` Jun 17, 2015 N/A N/A - :pypi:`pytest-coverage-context` Coverage dynamic context support for PyTest, including sub-processes Jan 04, 2021 4 - Beta pytest (>=6.1.0) - :pypi:`pytest-cov-exclude` Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' - :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Aug 22, 2022 5 - Production/Stable pytest (>=7.0) - :pypi:`pytest-cppython` A pytest plugin that imports CPPython testing types Sep 10, 2022 N/A N/A - :pypi:`pytest-cqase` Custom qase pytest plugin Aug 22, 2022 N/A pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-cram` Run cram tests with pytest. Aug 08, 2020 N/A N/A - :pypi:`pytest-crate` Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) - :pypi:`pytest-cricri` A Cricri plugin for pytest. Jan 27, 2018 N/A pytest - :pypi:`pytest-crontab` add crontab task in crontab Dec 09, 2019 N/A N/A - :pypi:`pytest-csv` CSV output for pytest. Apr 22, 2021 N/A pytest (>=6.0) - :pypi:`pytest-csv-params` Pytest plugin for Test Case Parametrization with CSV files Aug 28, 2022 5 - Production/Stable pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-curio` Pytest support for curio. Oct 07, 2020 N/A N/A - :pypi:`pytest-curl-report` pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A - :pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A - :pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) - :pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A - :pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest - :pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A - :pypi:`pytest-cython` A plugin for testing Cython extension modules Mar 26, 2022 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-cython-collect` Jun 17, 2022 N/A pytest - :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' - :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A - :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest - :pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 22, 2019 5 - Production/Stable pytest (>=2.7.0) - :pypi:`pytest-datadir-mgr` Manager for test data: downloads, artifact caching, and a tmpdir context. Aug 16, 2022 5 - Production/Stable pytest (>=7.1) - :pypi:`pytest-datadir-ng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest - :pypi:`pytest-data-extractor` A pytest plugin to extract relevant metadata about tests into an external file (currently only json support) Jul 19, 2022 N/A pytest (>=7.0.1) - :pypi:`pytest-data-file` Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A - :pypi:`pytest-datafiles` py.test plugin to create a 'tmpdir' containing predefined files/directories. May 01, 2022 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-datafixtures` Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A - :pypi:`pytest-data-from-files` pytest plugin to provide data from files loaded automatically Oct 13, 2021 4 - Beta pytest - :pypi:`pytest-dataplugin` A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A - :pypi:`pytest-datarecorder` A py.test plugin recording and comparing test output. Apr 20, 2020 5 - Production/Stable pytest - :pypi:`pytest-dataset` Plugin for loading different datasets for pytest by prefix from json or yaml files Sep 05, 2022 5 - Production/Stable N/A - :pypi:`pytest-data-suites` Class-based pytest parametrization Jul 24, 2022 N/A pytest (>=6.0,<8.0) - :pypi:`pytest-datatest` A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) - :pypi:`pytest-db` Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A - :pypi:`pytest-dbfixtures` Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A - :pypi:`pytest-db-plugin` Nov 27, 2021 N/A pytest (>=5.0) - :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) - :pypi:`pytest-dbt-conventions` A pytest plugin for linting a dbt project's conventions Mar 02, 2022 N/A pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-dbt-core` Pytest extension for dbt. Jul 22, 2022 N/A pytest (>=6.2.5) ; extra == 'test' - :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A - :pypi:`pytest-deadfixtures` A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A - :pypi:`pytest-defer` Aug 24, 2021 N/A N/A - :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A - :pypi:`pytest-dependency` Manage dependencies of tests Feb 14, 2020 4 - Beta N/A - :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) - :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A - :pypi:`pytest-describe` Describe-style plugin for pytest Nov 13, 2021 4 - Beta pytest (>=4.0.0) - :pypi:`pytest-describe-it` plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest - :pypi:`pytest-devpi-server` DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-dhos` Common fixtures for pytest in DHOS services and libraries Sep 07, 2022 N/A N/A - :pypi:`pytest-diamond` pytest plugin for diamond Aug 31, 2015 4 - Beta N/A - :pypi:`pytest-dicom` pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest - :pypi:`pytest-dictsdiff` Jul 26, 2019 N/A N/A - :pypi:`pytest-diff` A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-diff-selector` Get tests affected by code changes (using git) Feb 24, 2022 4 - Beta pytest (>=6.2.2) ; extra == 'all' - :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A - :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Mar 27, 2022 4 - Beta pytest (!=6.0.0,<8,>=3.3.2) - :pypi:`pytest-django` A Django plugin for pytest. Dec 07, 2021 5 - Production/Stable pytest (>=5.4.0) - :pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) - :pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. Aug 04, 2021 4 - Beta N/A - :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A - :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A - :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A - :pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2 - :pypi:`pytest-django-gcir` A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A - :pypi:`pytest-django-haystack` Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) - :pypi:`pytest-django-ifactory` A model instance factory for pytest-django Feb 09, 2022 3 - Alpha N/A - :pypi:`pytest-django-lite` The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A - :pypi:`pytest-django-liveserver-ssl` Jan 20, 2022 3 - Alpha N/A - :pypi:`pytest-django-model` A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A - :pypi:`pytest-django-ordering` A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) - :pypi:`pytest-django-queries` Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A - :pypi:`pytest-djangorestframework` A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A - :pypi:`pytest-django-rq` A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A - :pypi:`pytest-django-sqlcounts` py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A - :pypi:`pytest-django-testing-postgresql` Use a temporary PostgreSQL database with pytest-django Jan 31, 2022 4 - Beta N/A - :pypi:`pytest-doc` A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A - :pypi:`pytest-docfiles` pytest plugin to test codeblocks in your documentation. Dec 22, 2021 4 - Beta pytest (>=3.7.0) - :pypi:`pytest-docgen` An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A - :pypi:`pytest-docker` Simple pytest fixtures for Docker and docker-compose based tests Jul 27, 2022 N/A pytest (<8.0,>=4.0) - :pypi:`pytest-docker-apache-fixtures` Pytest fixtures for testing with apache2 (httpd). Feb 16, 2022 4 - Beta pytest - :pypi:`pytest-docker-butla` Jun 16, 2019 3 - Alpha N/A - :pypi:`pytest-dockerc` Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-docker-compose` Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) - :pypi:`pytest-docker-db` A plugin to use docker databases for pytests Mar 20, 2021 5 - Production/Stable pytest (>=3.1.1) - :pypi:`pytest-docker-fixtures` pytest docker fixtures Jul 06, 2022 3 - Alpha N/A - :pypi:`pytest-docker-git-fixtures` Pytest fixtures for testing with git scm. Feb 09, 2022 4 - Beta pytest - :pypi:`pytest-docker-haproxy-fixtures` Pytest fixtures for testing with haproxy. Feb 09, 2022 4 - Beta pytest - :pypi:`pytest-docker-pexpect` pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest - :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) - :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Apr 08, 2022 4 - Beta pytest - :pypi:`pytest-docker-service` pytest plugin to start docker container Mar 21, 2022 3 - Alpha pytest - :pypi:`pytest-docker-squid-fixtures` Pytest fixtures for testing with squid. Feb 09, 2022 4 - Beta pytest - :pypi:`pytest-docker-tools` Docker integration tests for pytest Feb 17, 2022 4 - Beta pytest (>=6.0.1) - :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-docstyle` pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A - :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A - :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A - :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Feb 25, 2022 3 - Alpha pytest (>=4.6) - :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) - :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-dotenv` A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) - :pypi:`pytest-drf` A Django REST framework plugin for pytest. Jul 12, 2022 5 - Production/Stable pytest (>=3.7) - :pypi:`pytest-drivings` Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A - :pypi:`pytest-drop-dup-tests` A Pytest plugin to drop duplicated tests during collection May 23, 2020 4 - Beta pytest (>=2.7) - :pypi:`pytest-dummynet` A py.test plugin providing access to a dummynet. Dec 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-dump2json` A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A - :pypi:`pytest-duration-insights` Jun 25, 2021 N/A N/A - :pypi:`pytest-durations` Pytest plugin reporting fixtures and test functions execution time. Apr 22, 2022 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-dynamicrerun` A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A - :pypi:`pytest-dynamodb` DynamoDB fixtures for pytest Jun 03, 2021 5 - Production/Stable pytest - :pypi:`pytest-easy-addoption` pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A - :pypi:`pytest-easy-api` Simple API testing with pytest Mar 26, 2018 N/A N/A - :pypi:`pytest-easyMPI` Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A - :pypi:`pytest-easyread` pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A - :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" - :pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A - :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A - :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A - :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Mar 01, 2022 5 - Production/Stable pytest (>=6.2.0) - :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) - :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) - :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest - :pypi:`pytest-embedded` pytest embedded plugin Aug 23, 2022 N/A pytest (>=7.0) - :pypi:`pytest-embedded-arduino` pytest embedded plugin for Arduino projects Aug 23, 2022 N/A N/A - :pypi:`pytest-embedded-idf` pytest embedded plugin for esp-idf project Aug 23, 2022 N/A N/A - :pypi:`pytest-embedded-jtag` pytest embedded plugin for testing with jtag Aug 23, 2022 N/A N/A - :pypi:`pytest-embedded-qemu` pytest embedded plugin for qemu, not target chip Aug 23, 2022 N/A N/A - :pypi:`pytest-embedded-serial` pytest embedded plugin for testing serial ports Aug 23, 2022 N/A N/A - :pypi:`pytest-embedded-serial-esp` pytest embedded plugin for testing espressif boards via serial ports Aug 23, 2022 N/A N/A - :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Aug 27, 2022 N/A pytest (>=7.0,<8.0) - :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) - :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 12, 2022 4 - Beta pytest (==7.0.1) - :pypi:`pytest-enabler` Enable installed pytest plugins Jun 22, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' - :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A - :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest - :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A - :pypi:`pytest-env` py.test plugin that allows you to add environment variables. Jun 16, 2017 4 - Beta N/A - :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A - :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-envvars` Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-env-yaml` Apr 02, 2019 N/A N/A - :pypi:`pytest-eradicate` pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) - :pypi:`pytest-error-for-skips` Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) - :pypi:`pytest-eth` PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A - :pypi:`pytest-ethereum` pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' - :pypi:`pytest-eucalyptus` Pytest Plugin for BDD Jun 28, 2022 N/A pytest (>=4.2.0) - :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' - :pypi:`pytest-excel` pytest plugin for generating excel reports Jan 31, 2022 5 - Production/Stable N/A - :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A - :pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest - :pypi:`pytest-executable` pytest plugin for testing executables Nov 10, 2021 4 - Beta pytest (<6.3,>=4.3) - :pypi:`pytest-execution-timer` A timer for the phases of Pytest's execution. Dec 24, 2021 4 - Beta N/A - :pypi:`pytest-expect` py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A - :pypi:`pytest-expecter` Better testing with expecter and pytest. Jan 10, 2022 5 - Production/Stable N/A - :pypi:`pytest-expectr` This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) - :pypi:`pytest-experiments` A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments. Dec 13, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-explicit` A Pytest plugin to ignore certain marked tests by default Jun 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-exploratory` Interactive console for pytest. Feb 21, 2022 N/A pytest (>=6.2) - :pypi:`pytest-extensions` A collection of helpers for pytest to ease testing Aug 17, 2022 4 - Beta pytest ; extra == 'testing' - :pypi:`pytest-external-blockers` a special outcome for tests that are blocked for external reasons Oct 05, 2021 N/A pytest - :pypi:`pytest-extra-durations` A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-fabric` Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A - :pypi:`pytest-factory` Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) - :pypi:`pytest-factoryboy` Factory Boy support for pytest. Jun 14, 2022 6 - Mature pytest (>=5.0.0) - :pypi:`pytest-factoryboy-fixtures` Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A - :pypi:`pytest-factoryboy-state` Simple factoryboy random state management Mar 22, 2022 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-failed-screenshot` Test case fails,take a screenshot,save it,attach it to the allure Apr 21, 2021 N/A N/A - :pypi:`pytest-failed-to-verify` A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) - :pypi:`pytest-fail-slow` Fail tests that take too long to run Aug 13, 2022 4 - Beta pytest (>=6.0) - :pypi:`pytest-faker` Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A - :pypi:`pytest-falcon` Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A - :pypi:`pytest-falcon-client` Pytest \`client\` fixture for the Falcon Framework Mar 19, 2019 N/A N/A - :pypi:`pytest-fantasy` Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A - :pypi:`pytest-fastapi` Dec 27, 2020 N/A N/A - :pypi:`pytest-fastapi-deps` A fixture which allows easy replacement of fastapi dependencies for testing Jul 20, 2022 5 - Production/Stable pytest - :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Mar 05, 2020 N/A N/A - :pypi:`pytest-fast-first` Pytest plugin that runs fast tests first Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-faulthandler` py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) - :pypi:`pytest-fauxfactory` Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) - :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A - :pypi:`pytest-filecov` A pytest plugin to detect unused files Jun 27, 2021 4 - Beta pytest - :pypi:`pytest-filedata` easily load data from files Jan 17, 2019 4 - Beta N/A - :pypi:`pytest-filemarker` A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest - :pypi:`pytest-filter-case` run test cases filter by mark Nov 05, 2020 N/A N/A - :pypi:`pytest-filter-subpackage` Pytest plugin for filtering based on sub-packages Jan 09, 2020 3 - Alpha pytest (>=3.0) - :pypi:`pytest-find-dependencies` A pytest plugin to find dependencies between tests Apr 09, 2022 4 - Beta pytest (>=4.3.0) - :pypi:`pytest-finer-verdicts` A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) - :pypi:`pytest-firefox` pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) - :pypi:`pytest-fixture-config` Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-fixture-maker` Pytest plugin to load fixtures from YAML files Sep 21, 2021 N/A N/A - :pypi:`pytest-fixture-marker` A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A - :pypi:`pytest-fixture-order` pytest plugin to control fixture evaluation order May 16, 2022 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-fixture-rtttg` Warn or fail on fixture name clash Feb 23, 2022 N/A pytest (>=7.0.1,<8.0.0) - :pypi:`pytest-fixtures` Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-fixture-tools` Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest - :pypi:`pytest-fixture-typecheck` A pytest plugin to assert type annotations at runtime. Aug 24, 2021 N/A pytest - :pypi:`pytest-flake8` pytest plugin to check FLAKE8 requirements Mar 18, 2022 4 - Beta pytest (>=7.0) - :pypi:`pytest-flake8-path` A pytest fixture for testing flake8 plugins. May 11, 2022 5 - Production/Stable pytest - :pypi:`pytest-flake8-v2` pytest plugin to check FLAKE8 requirements Mar 01, 2022 5 - Production/Stable pytest (>=7.0) - :pypi:`pytest-flakefinder` Runs tests multiple times to expose flakiness. Jul 28, 2020 4 - Beta pytest (>=2.7.1) - :pypi:`pytest-flakes` pytest plugin to check source code with pyflakes Dec 02, 2021 5 - Production/Stable pytest (>=5) - :pypi:`pytest-flaptastic` Flaptastic py.test plugin Mar 17, 2019 N/A N/A - :pypi:`pytest-flask` A set of py.test fixtures to test Flask applications. Feb 27, 2021 5 - Production/Stable pytest (>=5.2) - :pypi:`pytest-flask-ligand` Pytest fixtures and helper functions to use for testing flask-ligand microservices. Aug 18, 2022 4 - Beta pytest (~=7.1) - :pypi:`pytest-flask-sqlalchemy` A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 30, 2022 4 - Beta pytest (>=3.2.1) - :pypi:`pytest-flask-sqlalchemy-transactions` Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) - :pypi:`pytest-fluent` A pytest plugin in order to provide logs via fluentd Jul 12, 2022 4 - Beta pytest - :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest - :pypi:`pytest-focus` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest - :pypi:`pytest-forcefail` py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A - :pypi:`pytest-forward-compatability` A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A - :pypi:`pytest-forward-compatibility` A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A - :pypi:`pytest-freezegun` Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A - :pypi:`pytest-frozen-uuids` Deterministically frozen UUID's for your tests Apr 17, 2022 N/A pytest (>=3.0) - :pypi:`pytest-func-cov` Pytest plugin for measuring function coverage Apr 15, 2021 3 - Alpha pytest (>=5) - :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 - :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A - :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A - :pypi:`pytest-fzf` fzf-based test selector for pytest Aug 17, 2022 1 - Planning pytest (>=7.1.2) - :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) - :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A - :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A - :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest - :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) - :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest - :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A - :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Aug 23, 2022 N/A N/A - :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-git-fixtures` Pytest fixtures for testing with git. Mar 11, 2021 4 - Beta pytest - :pypi:`pytest-github` Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A - :pypi:`pytest-github-actions-annotate-failures` pytest plugin to annotate failed tests with a workflow command for GitHub Actions Jul 02, 2022 N/A pytest (>=4.0.0) - :pypi:`pytest-github-report` Generate a GitHub report using pytest in GitHub Workflows Jun 03, 2022 4 - Beta N/A - :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A - :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Jul 22, 2022 4 - Beta pytest - :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest - :pypi:`pytest-golden` Plugin for pytest that offloads expected outputs to data files Jul 18, 2022 N/A pytest (>=6.1.2) - :pypi:`pytest-google-chat` Notify google chat channel for test results Mar 27, 2022 4 - Beta pytest - :pypi:`pytest-graphql-schema` Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A - :pypi:`pytest-greendots` Green progress dots Feb 08, 2014 3 - Alpha N/A - :pypi:`pytest-growl` Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A - :pypi:`pytest-grpc` pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) - :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest - :pypi:`pytest-harvest` Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Jun 10, 2022 5 - Production/Stable N/A - :pypi:`pytest-helm-chart` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) - :pypi:`pytest-helm-charts` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Aug 03, 2022 4 - Beta pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A - :pypi:`pytest-helpers` pytest helpers May 17, 2020 N/A pytest - :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Dec 29, 2021 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) - :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest - :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest - :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A - :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Sep 09, 2022 3 - Alpha pytest (==7.1.2) - :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A - :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Mar 28, 2022 N/A pytest (>=5.0) - :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Feb 22, 2022 5 - Production/Stable N/A - :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Aug 27, 2021 4 - Beta pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-html` pytest plugin for generating HTML reports Dec 13, 2020 5 - Production/Stable pytest (!=6.0.0,>=5.0) - :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-html-merger` Pytest HTML reports merging utility Apr 03, 2022 N/A N/A - :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Mar 04, 2022 5 - Production/Stable N/A - :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Feb 13, 2022 N/A N/A - :pypi:`pytest-html-report-merger` Aug 31, 2022 N/A N/A - :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A - :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A - :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin Mar 16, 2022 5 - Production/Stable pytest ; extra == 'test' - :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A - :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A - :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest Jul 29, 2022 3 - Alpha N/A - :pypi:`pytest-httpx` Send responses to httpx. May 24, 2022 5 - Production/Stable pytest (<8.*,>=6.*) - :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Nov 16, 2021 N/A pytest (>=6.2.5) - :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A - :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest - :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A - :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta N/A - :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Aug 09, 2022 4 - Beta N/A - :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A - :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Sep 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Jul 25, 2022 N/A N/A - :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 23, 2021 5 - Production/Stable N/A - :pypi:`pytest-image-diff` Jun 08, 2022 3 - Alpha pytest - :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A - :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A - :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A - :pypi:`pytest-informative-node` display more node ininformation. Apr 25, 2019 4 - Beta N/A - :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A - :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A - :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Sep 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-inmanta-extensions` Inmanta tests package Aug 10, 2022 5 - Production/Stable N/A - :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Sep 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Jun 16, 2022 4 - Beta N/A - :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A - :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Feb 28, 2022 N/A pytest (>=6.0.2) - :pypi:`pytest-instafail` pytest plugin to show failures instantly Jun 14, 2020 4 - Beta pytest (>=2.9) - :pypi:`pytest-instrument` pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) - :pypi:`pytest-integration` Organizing pytests by integration or not Apr 16, 2020 N/A N/A - :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest Jul 19, 2021 N/A pytest (>=5.2,<7.0) - :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A - :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) - :pypi:`pytest-invenio` "Pytest fixtures for Invenio." Aug 09, 2022 5 - Production/Stable pytest (<7,>=6) - :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Sep 02, 2014 2 - Pre-Alpha N/A - :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A - :pypi:`pytest-isort` py.test plugin to check import ordering using isort Feb 08, 2022 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-is-running` pytest plugin providing a function to check if pytest is running. Aug 19, 2022 5 - Production/Stable N/A - :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A - :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A - :pypi:`pytest-iters` A contextmanager pytest fixture for handling multiple mock iters May 24, 2022 N/A N/A - :pypi:`pytest-jasmine` Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A - :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) - :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Apr 07, 2022 3 - Alpha N/A - :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Aug 25, 2022 4 - Beta pytest - :pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest - :pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) - :pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A - :pypi:`pytest-json-fixtures` JSON output for the --fixtures flag Aug 09, 2022 4 - Beta pytest (>=7.1.0) - :pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A - :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0) - :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Aug 24, 2021 N/A pytest - :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest - :pypi:`pytest-kexi` Apr 29, 2022 N/A pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-kind` Kubernetes test support with KIND for pytest Sep 08, 2022 5 - Production/Stable N/A - :pypi:`pytest-kivy` Kivy GUI tests fixtures using pytest Jul 06, 2021 4 - Beta pytest (>=3.6) - :pypi:`pytest-knows` A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A - :pypi:`pytest-konira` Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A - :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A - :pypi:`pytest-kwparametrize` Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) - :pypi:`pytest-lambda` Define pytest fixtures with lambda functions. Aug 20, 2022 3 - Alpha pytest (>=3.6,<8) - :pypi:`pytest-lamp` Jan 06, 2017 3 - Alpha N/A - :pypi:`pytest-launchable` Launchable Pytest Plugin Jun 14, 2022 N/A pytest (>=4.2.0) - :pypi:`pytest-layab` Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A - :pypi:`pytest-lazy-fixture` It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) - :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest - :pypi:`pytest-leak-finder` Find the previous test that makes another to fail Apr 18, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A - :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest - :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Jul 11, 2022 4 - Beta N/A - :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A - :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest - :pypi:`pytest-line-profiler` Profile code executed by pytest May 03, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-line-profiler-apn` Profile code executed by pytest Aug 14, 2022 4 - Beta N/A - :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest - :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Aug 25, 2021 5 - Production/Stable pytest - :pypi:`pytest-localserver` pytest plugin to test server connections locally. Aug 30, 2022 4 - Beta N/A - :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Aug 22, 2019 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-lockable` lockable resource plugin for pytest Jul 20, 2022 5 - Production/Stable pytest - :pypi:`pytest-locker` Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Oct 29, 2021 N/A pytest (>=5.4) - :pypi:`pytest-log` print log Aug 15, 2021 N/A pytest (>=3.8) - :pypi:`pytest-logbook` py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) - :pypi:`pytest-logdog` Pytest plugin to test logging Jun 15, 2021 1 - Planning pytest (>=6.2.0) - :pypi:`pytest-logfest` Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) - :pypi:`pytest-logging` Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A - :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A - :pypi:`pytest-loguru` Pytest Loguru Apr 12, 2022 5 - Production/Stable N/A - :pypi:`pytest-loop` pytest plugin for looping tests Jul 22, 2022 5 - Production/Stable pytest (>=6) - :pypi:`pytest-lsp` Pytest plugin for end-to-end testing of language servers Jul 18, 2022 3 - Alpha pytest - :pypi:`pytest-manual-marker` pytest marker for marking manual tests Aug 04, 2022 3 - Alpha N/A - :pypi:`pytest-markdoctest` A pytest plugin to doctest your markdown files Jul 22, 2022 4 - Beta pytest (>=6) - :pypi:`pytest-markdown` Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) - :pypi:`pytest-markdown-docs` Run markdown code fences through pytest Sep 02, 2022 N/A N/A - :pypi:`pytest-marker-bugzilla` py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A - :pypi:`pytest-markers-presence` A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) - :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A - :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest - :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A - :pypi:`pytest-matcher` Match test output against patterns stored in files Dec 10, 2021 5 - Production/Stable N/A - :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) - :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A - :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) - :pypi:`pytest-maybe-raises` Pytest fixture for optional exception testing. May 27, 2022 N/A pytest ; extra == 'dev' - :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) - :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) - :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. Aug 06, 2022 4 - Beta pytest (!=6.0.0,<8,>=3.3.2) - :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A - :pypi:`pytest-memray` A simple plugin to use with pytest Aug 21, 2022 N/A N/A - :pypi:`pytest-menu` A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-mercurial` pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A - :pypi:`pytest-mesh` pytest_mesh插件 Aug 05, 2022 N/A pytest (==7.1.2) - :pypi:`pytest-message` Pytest plugin for sending report message of marked tests execution Aug 04, 2022 N/A pytest (>=6.2.5) - :pypi:`pytest-messenger` Pytest to Slack reporting plugin Feb 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-metadata` pytest plugin for test session metadata Jul 15, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) - :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest - :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) - :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) - :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A - :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-mock` Thin-wrapper around the mock package for easier use with pytest Jul 05, 2022 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-mock-api` A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) - :pypi:`pytest-mock-generator` A pytest fixture wrapper for https://pypi.org/project/mock-generator May 16, 2022 5 - Production/Stable N/A - :pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest - :pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A - :pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A - :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Aug 24, 2022 N/A pytest (>=1.0) - :pypi:`pytest-mock-server` Mock server plugin for pytest Jan 09, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) - :pypi:`pytest-modified-env` Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards. Jan 29, 2022 4 - Beta N/A - :pypi:`pytest-modifyjunit` Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A - :pypi:`pytest-modifyscope` pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest - :pypi:`pytest-molecule` PyTest Molecule Plugin :: discover and run molecule tests Mar 29, 2022 5 - Production/Stable pytest (>=7.0.0) - :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Jun 07, 2021 5 - Production/Stable pytest - :pypi:`pytest-mongodb` pytest plugin for MongoDB fixtures Dec 07, 2019 5 - Production/Stable pytest (>=2.5.2) - :pypi:`pytest-monitor` Pytest plugin for analyzing resource usage. May 18, 2022 5 - Production/Stable pytest - :pypi:`pytest-monkeyplus` pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A - :pypi:`pytest-monkeytype` pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A - :pypi:`pytest-moto` Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A - :pypi:`pytest-motor` A pytest plugin for motor, the non-blocking MongoDB driver. Jul 21, 2021 3 - Alpha pytest - :pypi:`pytest-mp` A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest - :pypi:`pytest-mpi` pytest plugin to collect information from tests Jan 08, 2022 3 - Alpha pytest - :pypi:`pytest-mpl` pytest plugin to help with testing figures output from Matplotlib Jul 23, 2022 4 - Beta pytest - :pypi:`pytest-mproc` low-startup-overhead, scalable, distributed-testing pytest plugin Sep 04, 2022 4 - Beta pytest (>=6) - :pypi:`pytest-multi-check` Pytest-плагин, реализует возможность мульти проверок и мягких проверок Jul 12, 2022 N/A pytest - :pypi:`pytest-multihost` Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A - :pypi:`pytest-multilog` Multi-process logs handling and other helpers for pytest Jun 10, 2021 N/A N/A - :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Aug 12, 2021 N/A pytest (>=3.6) - :pypi:`pytest-multithreading-allure` pytest_multithreading_allure Mar 22, 2022 N/A N/A - :pypi:`pytest-mutagen` Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) - :pypi:`pytest-mypy` Mypy static type checker plugin for Pytest Feb 07, 2022 4 - Beta pytest (>=6.2) ; python_version >= "3.10" - :pypi:`pytest-mypyd` Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" - :pypi:`pytest-mypy-plugins` pytest plugin for writing tests for mypy plugins Aug 15, 2022 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-mypy-plugins-shim` Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Apr 12, 2021 N/A N/A - :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. May 30, 2022 N/A N/A - :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Feb 15, 2022 5 - Production/Stable pytest (>=6.2) - :pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) - :pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Jan 08, 2022 3 - Alpha pytest (>=6.2.0) - :pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A - :pypi:`pytest-network-endpoints` Network endpoints plugin for pytest Mar 06, 2022 N/A pytest - :pypi:`pytest-never-sleep` pytest plugin helps to avoid adding tests without mock \`time.sleep\` May 05, 2021 3 - Alpha pytest (>=3.5.1) - :pypi:`pytest-nginx` nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A - :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest - :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) - :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Aug 23, 2022 N/A pytest (==6.2.5) - :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest - :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A - :pypi:`pytest-nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Jul 07, 2021 N/A N/A - :pypi:`pytest-nocustom` Run all tests without custom markers Jul 07, 2021 5 - Production/Stable N/A - :pypi:`pytest-nodev` Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-nogarbage` Ensure a test produces no garbage Aug 29, 2021 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-notice` Send pytest execution result email Nov 05, 2020 N/A N/A - :pypi:`pytest-notification` A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) - :pypi:`pytest-notifier` A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest - :pypi:`pytest-notimplemented` Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) - :pypi:`pytest-notion` A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A - :pypi:`pytest-nunit` A pytest plugin for generating NUnit3 test result XML output Jul 26, 2022 4 - Beta N/A - :pypi:`pytest-object-getter` Import any object from a 3rd party module while mocking its namespace on demand. Jul 31, 2022 5 - Production/Stable pytest - :pypi:`pytest-ochrus` pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A - :pypi:`pytest-odoo` py.test plugin to run Odoo tests Feb 08, 2022 4 - Beta N/A - :pypi:`pytest-odoo-fixtures` Project description Jun 25, 2019 N/A N/A - :pypi:`pytest-oerp` pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A - :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A - :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jun 14, 2022 5 - Production/Stable pytest (<7.1); python_version <= "3.6" - :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A - :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) - :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Sep 07, 2022 N/A pytest - :pypi:`pytest-opentmi` pytest plugin for publish results to opentmi Jun 02, 2022 5 - Production/Stable pytest (>=5.0) - :pypi:`pytest-operator` Fixtures for Operators Aug 17, 2022 N/A pytest - :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A - :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) - :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A - :pypi:`pytest-order` pytest plugin to run your tests in a specific order Jan 09, 2022 4 - Beta pytest (>=5.0) ; python_version < "3.10" - :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest - :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A - :pypi:`pytest-otel` pytest-otel report OpenTelemetry traces about test executed May 26, 2022 N/A N/A - :pypi:`pytest-owner` Add owner mark for tests Apr 25, 2022 N/A N/A - :pypi:`pytest-pact` A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A - :pypi:`pytest-pahrametahrize` Parametrize your tests with a Boston accent. Nov 24, 2021 4 - Beta pytest (>=6.0,<7.0) - :pypi:`pytest-parallel` a pytest plugin for parallel and concurrent testing Oct 10, 2021 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-parallel-39` a pytest plugin for parallel and concurrent testing Jul 12, 2021 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-param` pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-paramark` Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) - :pypi:`pytest-parametrization` Simpler PyTest parametrization May 22, 2022 5 - Production/Stable N/A - :pypi:`pytest-parametrize-cases` A more user-friendly way to write parametrized tests. Mar 13, 2022 N/A pytest (>=6.1.2) - :pypi:`pytest-parametrized` Pytest plugin for parametrizing tests with default iterables. Oct 19, 2020 5 - Production/Stable pytest - :pypi:`pytest-parawtf` Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-pass` Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A - :pypi:`pytest-passrunner` Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A - :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A - :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) - :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A - :pypi:`pytest-pep8` pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A - :pypi:`pytest-percent` Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) - :pypi:`pytest-perf` pytest-perf Jun 23, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' - :pypi:`pytest-performance` A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) - :pypi:`pytest-persistence` Pytest tool for persistent objects Jun 27, 2022 N/A N/A - :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker Jun 07, 2022 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-pgsql` Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-phmdoctest` pytest plugin to test Python examples in Markdown using phmdoctest. Apr 15, 2022 4 - Beta pytest (>=5.4.3) - :pypi:`pytest-picked` Run the tests related to the changed files Dec 23, 2020 N/A pytest (>=3.5.0) - :pypi:`pytest-pigeonhole` Jun 25, 2018 5 - Production/Stable pytest (>=3.4) - :pypi:`pytest-pikachu` Show surprise when tests are passing Aug 05, 2021 5 - Production/Stable pytest - :pypi:`pytest-pilot` Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A - :pypi:`pytest-pings` 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) - :pypi:`pytest-pinned` A simple pytest plugin for pinning tests Sep 17, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pinpoint` A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) - :pypi:`pytest-pipeline` Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A - :pypi:`pytest-platform-markers` Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-play` pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A - :pypi:`pytest-playbook` Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Mar 16, 2022 N/A pytest - :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A - :pypi:`pytest-playwright-snapshot` A pytest wrapper for snapshot testing with playwright Aug 19, 2021 N/A N/A - :pypi:`pytest-playwright-visual` A pytest fixture for visual testing with Playwright Apr 28, 2022 N/A N/A - :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest - :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Mar 19, 2020 5 - Production/Stable pytest (>=3.50) - :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Oct 14, 2021 N/A N/A - :pypi:`pytest-polarion-cfme` pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A - :pypi:`pytest-polarion-collect` pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest - :pypi:`pytest-polecat` Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A - :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) - :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) - :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A - :pypi:`pytest-pop` A pytest plugin to help with testing pop projects Aug 18, 2022 5 - Production/Stable pytest - :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest - :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. Mar 11, 2022 5 - Production/Stable pytest (>=6.2.0) - :pypi:`pytest-power` pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) - :pypi:`pytest-prefer-nested-dup-tests` A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. Apr 27, 2022 4 - Beta pytest (>=7.1.1,<8.0.0) - :pypi:`pytest-pretty-terminal` pytest plugin for generating prettier terminal output Jan 31, 2022 N/A pytest (>=3.4.1) - :pypi:`pytest-pride` Minitest-style test colors Apr 02, 2016 3 - Alpha N/A - :pypi:`pytest-print` pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Dec 28, 2021 5 - Production/Stable pytest (>=6) - :pypi:`pytest-profiles` pytest plugin for configuration profiles Dec 09, 2021 4 - Beta pytest (>=3.7.0) - :pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-progress` pytest plugin for instant test progress status Jan 31, 2022 5 - Production/Stable N/A - :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A - :pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 01, 2022 5 - Production/Stable pytest - :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A - :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) - :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-pudb` Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) - :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A - :pypi:`pytest-pycharm` Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-pycodestyle` pytest plugin to run pycodestyle Mar 13, 2022 3 - Alpha N/A - :pypi:`pytest-pydev` py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A - :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Mar 13, 2022 3 - Alpha N/A - :pypi:`pytest-pylint` pytest plugin to check source code with pylint Sep 10, 2022 5 - Production/Stable pytest (>=5.4) - :pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A - :pypi:`pytest-pyodide` "Pytest plugin for testing applications that use Pyodide" Sep 08, 2022 N/A pytest - :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A - :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) - :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-pyq` Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-pyramid` pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Oct 15, 2021 5 - Production/Stable pytest - :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Aug 16, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) - :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Feb 10, 2022 5 - Production/Stable pytest (<7,>=2.5.2) - :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest - :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) - :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Jun 26, 2021 4 - Beta pytest (>=6.2.0) - :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Jun 26, 2022 5 - Production/Stable pytest (>=6.2.3) - :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A - :pypi:`pytest-qt` pytest support for PyQt and PySide applications Jun 23, 2022 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A - :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 15, 2020 4 - Beta pytest (<6.0.0,>=4.0) - :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Feb 11, 2022 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-race` Race conditions tester for pytest Jun 07, 2022 4 - Beta N/A - :pypi:`pytest-rage` pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A - :pypi:`pytest-rail` pytest plugin for creating TestRail runs and adding results May 02, 2022 N/A pytest (>=3.6) - :pypi:`pytest-railflow-testrail-reporter` Generate json reports along with specified metadata defined in test markers. Jun 29, 2022 5 - Production/Stable pytest - :pypi:`pytest-raises` An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) - :pypi:`pytest-raisesregexp` Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A - :pypi:`pytest-raisin` Plugin enabling the use of exception instances with pytest.raises Feb 06, 2022 N/A pytest - :pypi:`pytest-random` py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A - :pypi:`pytest-randomly` Pytest plugin to randomly order tests and control random.seed. May 11, 2022 5 - Production/Stable pytest - :pypi:`pytest-randomness` Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A - :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A - :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A - :pypi:`pytest-reana` Pytest fixtures for REANA. May 16, 2022 3 - Alpha N/A - :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jun 20, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A - :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Feb 10, 2022 5 - Production/Stable pytest (>=6.2.0) - :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest - :pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A - :pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reference-formatter` Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A - :pypi:`pytest-regex-dependency` Management of Pytest dependencies via regex patterns Jun 12, 2022 N/A pytest - :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Jan 19, 2022 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-regtest` pytest plugin for regression tests Jul 08, 2022 N/A N/A - :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A - :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest Jun 14, 2019 5 - Production/Stable pytest (<5,>=3) - :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-remotedata` Pytest plugin for controlling remote data access. Dec 21, 2021 3 - Alpha pytest (>=4.6) - :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Jun 05, 2022 4 - Beta pytest (>=4.6) - :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Mar 04, 2020 4 - Beta pytest - :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest - :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 31, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jun 09, 2021 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Dec 16, 2021 3 - Alpha pytest - :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A - :pypi:`pytest-reporter` Generate Pytest reports with templates Jul 22, 2021 4 - Beta pytest - :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Jun 08, 2021 4 - Beta N/A - :pypi:`pytest-reportinfra` Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A - :pypi:`pytest-reporting` A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility Dec 11, 2020 3 - Alpha pytest (>=5.2) - :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest - :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) - :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Jun 24, 2022 N/A pytest (>=3.8.0) - :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) - :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-requestselapsed` collect and show http requests elapsed time Aug 14, 2022 N/A N/A - :pypi:`pytest-requests-futures` Pytest Plugin to Mock Requests Futures Jul 06, 2022 5 - Production/Stable pytest - :pypi:`pytest-requires` A pytest plugin to elegantly skip tests with optional requirements Dec 21, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Jun 17, 2021 5 - Production/Stable pytest (>=4.6) - :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) - :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Sep 17, 2021 5 - Production/Stable pytest (>=5.3) - :pypi:`pytest-rerunfailures-all-logs` pytest plugin to re-run tests to eliminate flaky failures Mar 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest. Aug 12, 2022 N/A N/A - :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A - :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-responsemock` Simplified requests calls mocking for pytest Mar 10, 2022 5 - Production/Stable N/A - :pypi:`pytest-responses` py.test integration for responses Apr 26, 2021 N/A pytest (>=2.5) - :pypi:`pytest-rest-api` Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed May 11, 2022 5 - Production/Stable pytest - :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A - :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments Aug 16, 2022 N/A pytest (>=7.0.0) - :pypi:`pytest-reverse` Pytest plugin to reverse test order. May 11, 2022 5 - Production/Stable pytest - :pypi:`pytest-rich` Leverage rich for richer test session output Mar 03, 2022 4 - Beta pytest (>=7.0) - :pypi:`pytest-rich-reporter` A pytest plugin using Rich for beautiful test result formatting. Feb 17, 2022 1 - Planning pytest (>=5.0.0) - :pypi:`pytest-ringo` pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A - :pypi:`pytest-rmsis` Sycronise pytest results to Jira RMsis Aug 10, 2022 N/A pytest (>=5.3.5) - :pypi:`pytest-rng` Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest - :pypi:`pytest-roast` pytest plugin for ROAST configuration override and fixtures Jul 29, 2021 5 - Production/Stable pytest - :pypi:`pytest-rocketchat` Pytest to Rocket.Chat reporting plugin Apr 18, 2021 5 - Production/Stable N/A - :pypi:`pytest-rotest` Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) - :pypi:`pytest-rpc` Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) - :pypi:`pytest-rst` Test code from RST documents with pytest Aug 02, 2022 N/A N/A - :pypi:`pytest-rt` pytest data collector plugin for Testgr May 05, 2022 N/A N/A - :pypi:`pytest-rts` Coverage-based regression test selection (RTS) plugin for pytest May 17, 2021 N/A pytest - :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest - :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A - :pypi:`pytest-runner` Invoke py.test as distutils command with dependency resolution Feb 25, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' - :pypi:`pytest-run-subprocess` Pytest Plugin for running and testing subprocesses. Jul 03, 2022 5 - Production/Stable pytest - :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A N/A - :pypi:`pytest-saccharin` pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). May 10, 2022 3 - Alpha N/A - :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A - :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A - :pypi:`pytest-salt-factories` Pytest Salt Plugin Aug 25, 2022 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) - :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) - :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) - :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A - :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Sep 09, 2022 5 - Production/Stable N/A - :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A - :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A - :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) - :pypi:`pytest-selenium` pytest plugin for Selenium Mar 28, 2022 5 - Production/Stable pytest (>=6.0.0,<7.0.0) - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Sep 09, 2022 5 - Production/Stable N/A - :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A - :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A - :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A - :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Apr 21, 2021 N/A pytest - :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A - :pypi:`pytest-servers` pytest servers Aug 22, 2022 3 - Alpha pytest (==7.1.2) - :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A - :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest - :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A - :pypi:`pytest-session_to_file` pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A - :pypi:`pytest-sftpserver` py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A - :pypi:`pytest-shard` Dec 11, 2020 4 - Beta pytest - :pypi:`pytest-sharkreport` this is pytest report plugin. Jul 11, 2022 N/A pytest (>=3.5) - :pypi:`pytest-shell` A pytest plugin to help with testing shell scripts / black box commands Mar 27, 2022 N/A N/A - :pypi:`pytest-shell-utilities` Pytest plugin to simplify running shell commands against the system Jul 28, 2022 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-sheraf` Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest - :pypi:`pytest-sherlock` pytest plugin help to find coupled tests Nov 18, 2021 5 - Production/Stable pytest (>=3.5.1) - :pypi:`pytest-shortcuts` Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-shutil` A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-simplehttpserver` Simple pytest fixture to spin up an HTTP server Jun 24, 2021 4 - Beta N/A - :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A - :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest - :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-skip-markers` Pytest Salt Plugin May 09, 2022 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) - :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) - :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Apr 26, 2022 N/A N/A - :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A - :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A - :pypi:`pytest-smartcollect` A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) - :pypi:`pytest-smartcov` Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A - :pypi:`pytest-smell` Automated bad smell detection tool for Pytest Jun 26, 2022 N/A N/A - :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest - :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) - :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A - :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Apr 23, 2022 4 - Beta pytest (>=3.0.0) - :pypi:`pytest-snmpserver` May 12, 2021 N/A N/A - :pypi:`pytest-snowflake-bdd` Setup test data and run tests on snowflake in BDD style! Jan 05, 2022 4 - Beta pytest (>=6.2.0) - :pypi:`pytest-socket` Pytest Plugin to disable socket calls during tests Jan 23, 2022 4 - Beta pytest (>=3.6.3) - :pypi:`pytest-sofaepione` Test the installation of SOFA and the SofaEpione plugin. Aug 17, 2022 N/A N/A - :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest - :pypi:`pytest-solidity` A PyTest library plugin for Solidity language. Jan 15, 2022 1 - Planning pytest (<7,>=6.0.1) ; extra == 'tests' - :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) - :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-sosu` Unofficial PyTest plugin for Sauce Labs Apr 11, 2022 2 - Pre-Alpha pytest - :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest - :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest - :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A - :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A - :pypi:`pytest-spec2md` Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. Jun 26, 2022 N/A pytest (>7.0) - :pypi:`pytest-speed` Modern benchmarking library for python with pytest integration. Jul 06, 2022 3 - Alpha pytest (>=7) - :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Sep 06, 2022 4 - Beta pytest (>=7.0.0) - :pypi:`pytest-spiratest` Exports unit tests as test runs in SpiraTest/Team/Plan Feb 08, 2022 N/A N/A - :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0) - :pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Jun 11, 2022 6 - Mature pytest (<8.0,>=7.1.2) - :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Apr 22, 2022 4 - Beta pytest (>=5,<8) - :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) - :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) - :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A - :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Sep 08, 2022 N/A pytest (>5.4.0,<6.3) - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Mar 16, 2022 N/A N/A - :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) - :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A - :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A - :pypi:`pytest-sqlalchemy-mock` pytest sqlalchemy plugin for mock Aug 10, 2022 3 - Alpha pytest (>=2.0) - :pypi:`pytest-sql-bigquery` Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest - :pypi:`pytest-squadcast` Pytest report plugin for Squadcast Feb 22, 2022 5 - Production/Stable pytest - :pypi:`pytest-srcpaths` Add paths to sys.path Oct 15, 2021 N/A N/A - :pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest - :pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A - :pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) - :pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest - :pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A - :pypi:`pytest-stepwise` Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A - :pypi:`pytest-stf` pytest plugin for openSTF Sep 09, 2022 N/A pytest (>=5.0) - :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A - :pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) - :pypi:`pytest-structlog` Structured logging assertions Sep 21, 2021 N/A pytest - :pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A - :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A - :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) - :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) - :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Feb 09, 2022 5 - Production/Stable pytest (>=4.0.0) - :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Jul 16, 2022 N/A N/A - :pypi:`pytest-subtests` unittest subTest() support and subtests fixture May 26, 2022 4 - Beta pytest (>=7.0) - :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A - :pypi:`pytest-sugar` pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Jul 10, 2022 3 - Alpha pytest (>=2.9) - :pypi:`pytest-sugar-bugfix159` Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 Nov 07, 2018 5 - Production/Stable pytest (!=3.7.3,>=3.5); extra == 'testing' - :pypi:`pytest-super-check` Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. May 11, 2022 5 - Production/Stable pytest - :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A - :pypi:`pytest-system-statistics` Pytest plugin to track and report system usage statistics Feb 16, 2022 5 - Production/Stable pytest (>=6.0.0) - :pypi:`pytest-system-test-plugin` Pyst - Pytest System-Test Plugin Feb 03, 2022 N/A N/A - :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Jan 04, 2022 N/A N/A - :pypi:`pytest-talisker` Nov 28, 2021 N/A N/A - :pypi:`pytest-tap` Test Anything Protocol (TAP) reporting plugin for pytest Oct 27, 2021 5 - Production/Stable pytest (>=3.0) - :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A - :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) - :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) - :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A - :pypi:`pytest-telegram` Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A - :pypi:`pytest-tempdir` Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-terra-fixt` Terraform and Terragrunt fixtures for pytest Sep 09, 2022 N/A pytest (==6.2.5) - :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures Sep 01, 2022 N/A pytest (>=6.0) - :pypi:`pytest-terraform-fixture` generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A - :pypi:`pytest-testbook` A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A - :pypi:`pytest-testconfig` Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testdirectory` A py.test plugin providing temporary directories in unit tests. Feb 21, 2022 5 - Production/Stable pytest - :pypi:`pytest-testdox` A testdox format reporter for pytest Apr 19, 2022 5 - Production/Stable pytest (>=4.6.0) - :pypi:`pytest-test-grouping` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Jun 17, 2022 5 - Production/Stable pytest (>=2.5) - :pypi:`pytest-test-groups` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A - :pypi:`pytest-testinfra` Test infrastructures Jun 19, 2022 5 - Production/Stable pytest (!=3.0.2) - :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) - :pypi:`pytest-testmon` selects tests affected by changed files and methods Sep 09, 2022 4 - Beta N/A - :pypi:`pytest-testmon-oc` nOly selects tests affected by changed files and methods Jun 01, 2022 4 - Beta pytest (<8,>=5) - :pypi:`pytest-testmon-skip-libraries` selects tests affected by changed files and methods Jun 13, 2022 4 - Beta N/A - :pypi:`pytest-testobject` Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-testpluggy` set your encoding Jan 07, 2022 N/A pytest - :pypi:`pytest-testrail` pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) - :pypi:`pytest-testrail2` A small example package Nov 17, 2020 N/A pytest (>=5) - :pypi:`pytest-testrail-api` Плагин Pytest, для интеграции с TestRail Aug 29, 2022 N/A pytest (>=5.5) - :pypi:`pytest-testrail-api-client` TestRail Api Python Client Dec 14, 2021 N/A pytest - :pypi:`pytest-testrail-appetize` pytest plugin for creating TestRail runs and adding results Sep 29, 2021 N/A N/A - :pypi:`pytest-testrail-client` pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A - :pypi:`pytest-testrail-e2e` pytest plugin for creating TestRail runs and adding results Oct 11, 2021 N/A pytest (>=3.6) - :pypi:`pytest-testrail-integrator` Pytest plugin for sending report to testrail system. Aug 01, 2022 N/A pytest (>=6.2.5) - :pypi:`pytest-testrail-ns` pytest plugin for creating TestRail runs and adding results Aug 12, 2022 N/A N/A - :pypi:`pytest-testrail-plugin` PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest - :pypi:`pytest-testrail-reporter` Sep 10, 2018 N/A N/A - :pypi:`pytest-testreport` May 23, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testreport-new` Aug 15, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-testslide` TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) - :pypi:`pytest-test-this` Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) - :pypi:`pytest-test-utils` Jul 14, 2022 N/A pytest (>=5) - :pypi:`pytest-tesults` Tesults plugin for pytest Dec 31, 2021 5 - Production/Stable pytest (>=3.5.0) - :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A - :pypi:`pytest-th2-bdd` pytest_th2_bdd May 13, 2022 N/A N/A - :pypi:`pytest-thawgun` Pytest plugin for time travel May 26, 2020 3 - Alpha N/A - :pypi:`pytest-threadleak` Detects thread leaks Jul 03, 2022 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A - :pypi:`pytest-timeout` pytest plugin to abort hanging tests Jan 18, 2022 5 - Production/Stable pytest (>=5.0.0) - :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-timer` A timer plugin for pytest Jun 02, 2021 N/A N/A - :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A - :pypi:`pytest-timestamps` A simple plugin to view timestamps for each test Jan 16, 2022 N/A pytest (>=5.2) - :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) - :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Mar 12, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest - :pypi:`pytest-tmnet` A small example package Mar 01, 2022 N/A N/A - :pypi:`pytest-tmp-files` Utilities to create temporary file hierarchies in pytest. Apr 03, 2022 N/A pytest - :pypi:`pytest-tmpfs` A pytest plugin that helps you on using a temporary filesystem for testing. Aug 29, 2022 N/A pytest - :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Aug 12, 2022 N/A N/A - :pypi:`pytest-todo` A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest - :pypi:`pytest-tomato` Mar 01, 2019 5 - Production/Stable N/A - :pypi:`pytest-toolbelt` This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A - :pypi:`pytest-toolbox` Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) - :pypi:`pytest-tools` Pytest tools Jul 04, 2022 4 - Beta N/A - :pypi:`pytest-tornado` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-tornado5` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) - :pypi:`pytest-tornado-yen3` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A - :pypi:`pytest-tornasync` py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) - :pypi:`pytest-trace` Save OpenTelemetry spans generated during testing Jun 19, 2022 N/A pytest (>=4.6) - :pypi:`pytest-track` Feb 26, 2021 3 - Alpha pytest (>=3.0) - :pypi:`pytest-translations` Test your translation files. Nov 05, 2021 5 - Production/Stable N/A - :pypi:`pytest-travis-fold` Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) - :pypi:`pytest-trello` Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A - :pypi:`pytest-trepan` Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A - :pypi:`pytest-trialtemp` py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A - :pypi:`pytest-trio` Pytest plugin for trio Oct 16, 2020 N/A N/A - :pypi:`pytest-trytond` Pytest plugin for the Tryton server framework Feb 02, 2022 3 - Alpha pytest (>=5) - :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-tst` Customize pytest options, output and exit code to make it compatible with tst Apr 27, 2022 N/A pytest (>=5.0.0) - :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A - :pypi:`pytest-tui` Text User Interface (TUI) for Pytest, with optional auto-launch and HTML export Sep 07, 2022 4 - Beta pytest (>=6.2.5) - :pypi:`pytest-twilio-conversations-client-mock` Aug 02, 2022 N/A N/A - :pypi:`pytest-twisted` A twisted plugin for pytest. Aug 30, 2021 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-typechecker` Run type checkers on specified test files Feb 04, 2022 N/A pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-typhoon-config` A Typhoon HIL plugin that facilitates test parameter configuration at runtime Apr 07, 2022 5 - Production/Stable N/A - :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Mar 07, 2022 4 - Beta N/A - :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) - :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A - :pypi:`pytest-ui` Text User Interface for running python tests Jul 05, 2021 4 - Beta pytest - :pypi:`pytest-unflakable` Unflakable plugin for PyTest Jun 14, 2022 4 - Beta pytest (>=6.2.0) - :pypi:`pytest-unhandled-exception-exit-code` Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) - :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) - :pypi:`pytest-unmarked` Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A - :pypi:`pytest-unordered` Test equality of unordered collections in pytest Jul 08, 2022 4 - Beta pytest (>=6.0.0) - :pypi:`pytest-unstable` Set a test as unstable to return 0 even if it failed Jun 10, 2022 4 - Beta N/A - :pypi:`pytest-upload-report` pytest-upload-report is a plugin for pytest that upload your test report for test results. Jun 18, 2021 5 - Production/Stable N/A - :pypi:`pytest-utils` Some helpers for pytest. Dec 04, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) - :pypi:`pytest-vagrant` A py.test plugin providing access to vagrant. Sep 07, 2021 5 - Production/Stable pytest - :pypi:`pytest-valgrind` May 19, 2021 N/A N/A - :pypi:`pytest-variables` pytest plugin for providing variables to tests/fixtures Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) - :pypi:`pytest-variant` Variant support for Pytest Jun 06, 2022 N/A N/A - :pypi:`pytest-vcr` Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) - :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Jun 20, 2022 5 - Production/Stable pytest (>=6.2.2) - :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest - :pypi:`pytest-venv` py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Report Aug 21, 2022 2 - Pre-Alpha N/A - :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-vnc` VNC client for Pytest Jun 03, 2022 N/A pytest - :pypi:`pytest-voluptuous` Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest - :pypi:`pytest-vscodedebug` A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A - :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) - :pypi:`pytest-vw` pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A - :pypi:`pytest-vyper` Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A - :pypi:`pytest-wa-e2e-plugin` Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-wake` Aug 30, 2022 N/A pytest - :pypi:`pytest-watch` Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A - :pypi:`pytest-watcher` Continiously runs pytest on changes in \*.py files Dec 30, 2021 3 - Alpha N/A - :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A - :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A - :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A - :pypi:`pytest-wholenodeid` pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) - :pypi:`pytest-win32consoletitle` Pytest progress in console title (Win32 only) Aug 08, 2021 N/A N/A - :pypi:`pytest-winnotify` Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A - :pypi:`pytest-wiremock` A pytest plugin for programmatically using wiremock in integration tests Mar 27, 2022 N/A pytest (>=7.1.1,<8.0.0) - :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest - :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Dec 03, 2021 5 - Production/Stable pytest (>=5.4.0) - :pypi:`pytest-xdist` pytest xdist plugin for distributed testing and loop-on-failing modes Dec 10, 2021 5 - Production/Stable pytest (>=6.2.0) - :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) - :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) - :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) - :pypi:`pytest-xfaillist` Maintain a xfaillist in an additional file to avoid merge-conflicts. Sep 17, 2021 N/A pytest (>=6.2.2,<7.0.0) - :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A - :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A - :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Aug 04, 2022 N/A N/A - :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest - :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Aug 29, 2022 4 - Beta pytest (>=2.8) - :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A - :pypi:`pytest-xrayjira` Mar 17, 2020 3 - Alpha pytest (==4.3.1) - :pypi:`pytest-xray-server` May 03, 2022 3 - Alpha pytest (>=5.3.1) - :pypi:`pytest-xreport` May 17, 2022 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-xvfb` A pytest plugin to run Xvfb for tests. Jun 09, 2020 4 - Beta pytest (>=2.8.1) - :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest - :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A - :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) - :pypi:`pytest-yapf3` Validate your Python file format with yapf Aug 03, 2020 5 - Production/Stable pytest (>=5.4) - :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A - :pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) - :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A N/A - :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) - :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A - :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jun 02, 2022 5 - Production/Stable pytest (>=4.5.0) - :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) - :pypi:`pytest-zulip` Pytest report plugin for Zulip May 07, 2022 5 - Production/Stable pytest - =============================================== ============================================================================================================================================================================ ============== ===================== ================================================ + =============================================== ======================================================================================================================================================================================================== ============== ===================== ================================================ + name summary last release status requires + =============================================== ======================================================================================================================================================================================================== ============== ===================== ================================================ + :pypi:`logassert` Simple but powerful assertion and verification of logged lines. May 20, 2022 5 - Production/Stable N/A + :pypi:`pytest-abq` Pytest integration for the ABQ universal test runner. Apr 07, 2023 N/A N/A + :pypi:`pytest-abstracts` A contextmanager pytest fixture for handling multiple mock abstracts May 25, 2022 N/A N/A + :pypi:`pytest-accept` A pytest-plugin for updating doctest outputs Dec 21, 2022 N/A pytest (>=6,<8) + :pypi:`pytest-adaptavist` pytest plugin for generating test execution results within Jira Test Management (tm4j) Oct 13, 2022 N/A pytest (>=5.4.0) + :pypi:`pytest-addons-test` 用于测试pytest的插件 Aug 02, 2021 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-adf` Pytest plugin for writing Azure Data Factory integration tests May 10, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-adf-azure-identity` Pytest plugin for writing Azure Data Factory integration tests Mar 06, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ads-testplan` Azure DevOps Test Case reporting for pytest tests Sep 15, 2022 N/A N/A + :pypi:`pytest-agent` Service that exposes a REST API that can be used to interract remotely with Pytest. It is shipped with a dashboard that enables running tests in a more convenient way. Nov 25, 2021 N/A N/A + :pypi:`pytest-aggreport` pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. Mar 07, 2021 4 - Beta pytest (>=6.2.2) + :pypi:`pytest-aio` Pytest plugin for testing async python code Feb 03, 2023 4 - Beta pytest + :pypi:`pytest-aiofiles` pytest fixtures for writing aiofiles tests with pyfakefs May 14, 2017 5 - Production/Stable N/A + :pypi:`pytest-aiogram` May 06, 2023 N/A N/A + :pypi:`pytest-aiohttp` Pytest plugin for aiohttp support Feb 12, 2022 4 - Beta pytest (>=6.1.0) + :pypi:`pytest-aiohttp-client` Pytest \`client\` fixture for the Aiohttp Jan 10, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-aiomoto` pytest-aiomoto Nov 09, 2022 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-aioresponses` py.test integration for aioresponses Jul 29, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-aioworkers` A plugin to test aioworkers project with pytest May 01, 2023 5 - Production/Stable pytest>=6.1.0 + :pypi:`pytest-airflow` pytest support for airflow. Apr 03, 2019 3 - Alpha pytest (>=4.4.0) + :pypi:`pytest-airflow-utils` Nov 15, 2021 N/A N/A + :pypi:`pytest-alembic` A pytest plugin for verifying alembic migrations. May 23, 2023 N/A pytest (>=6.0) + :pypi:`pytest-allclose` Pytest fixture extending Numpy's allclose function Jul 30, 2019 5 - Production/Stable pytest + :pypi:`pytest-allure-adaptor` Plugin for py.test to generate allure xml reports Jan 10, 2018 N/A pytest (>=2.7.3) + :pypi:`pytest-allure-adaptor2` Plugin for py.test to generate allure xml reports Oct 14, 2020 N/A pytest (>=2.7.3) + :pypi:`pytest-allure-collection` pytest plugin to collect allure markers without running any tests Apr 13, 2023 N/A pytest + :pypi:`pytest-allure-dsl` pytest plugin to test case doc string dls instructions Oct 25, 2020 4 - Beta pytest + :pypi:`pytest-allure-intersection` Oct 27, 2022 N/A pytest (<5) + :pypi:`pytest-allure-spec-coverage` The pytest plugin aimed to display test coverage of the specs(requirements) in Allure Oct 26, 2021 N/A pytest + :pypi:`pytest-alphamoon` Static code checks used at Alphamoon Dec 30, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-android` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Feb 21, 2019 3 - Alpha pytest + :pypi:`pytest-anki` A pytest plugin for testing Anki add-ons Jul 31, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-annotate` pytest-annotate: Generate PyAnnotate annotations from your pytest tests. Jun 07, 2022 3 - Alpha pytest (<8.0.0,>=3.2.0) + :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures May 15, 2023 5 - Production/Stable pytest (<8.0.0,>=6) + :pypi:`pytest-ansible-playbook` Pytest fixture which runs given ansible playbook file. Mar 08, 2019 4 - Beta N/A + :pypi:`pytest-ansible-playbook-runner` Pytest fixture which runs given ansible playbook file. Dec 02, 2020 4 - Beta pytest (>=3.1.0) + :pypi:`pytest-ansible-units` A pytest plugin for running unit tests within an ansible collection Apr 14, 2022 N/A N/A + :pypi:`pytest-antilru` Bust functools.lru_cache when running pytest to avoid test pollution Jul 05, 2022 5 - Production/Stable pytest + :pypi:`pytest-anyio` The pytest anyio plugin is built into anyio. You don't need this package. Jun 29, 2021 N/A pytest + :pypi:`pytest-anything` Pytest fixtures to assert anything and something Oct 13, 2022 N/A pytest + :pypi:`pytest-aoc` Downloads puzzle inputs for Advent of Code and synthesizes PyTest fixtures Dec 08, 2022 N/A pytest ; extra == 'test' + :pypi:`pytest-aoreporter` pytest report Jun 27, 2022 N/A N/A + :pypi:`pytest-api` An ASGI middleware to populate OpenAPI Specification examples from pytest functions May 12, 2022 N/A pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-api-soup` Validate multiple endpoints with unit testing using a single source of truth. Aug 27, 2022 N/A N/A + :pypi:`pytest-apistellar` apistellar plugin for pytest. Jun 18, 2019 N/A N/A + :pypi:`pytest-appengine` AppEngine integration that works well with pytest-django Feb 27, 2017 N/A N/A + :pypi:`pytest-appium` Pytest plugin for appium Dec 05, 2019 N/A N/A + :pypi:`pytest-approvaltests` A plugin to use approvaltests with pytest May 08, 2022 4 - Beta pytest (>=7.0.1) + :pypi:`pytest-approvaltests-geo` Extension for ApprovalTests.Python specific to geo data verification Mar 04, 2023 5 - Production/Stable pytest + :pypi:`pytest-archon` Rule your architecture like a real developer Jan 31, 2023 5 - Production/Stable pytest (>=7.2) + :pypi:`pytest-argus` pyest results colection plugin Jun 24, 2021 5 - Production/Stable pytest (>=6.2.4) + :pypi:`pytest-arraydiff` pytest plugin to help with comparing array output from tests Jan 13, 2022 4 - Beta pytest (>=4.6) + :pypi:`pytest-asgi-server` Convenient ASGI client/server fixtures for Pytest Dec 12, 2020 N/A pytest (>=5.4.1) + :pypi:`pytest-asptest` test Answer Set Programming programs Apr 28, 2018 4 - Beta N/A + :pypi:`pytest-assertcount` Plugin to count actual number of asserts in pytest Oct 23, 2022 N/A pytest (>=5.0.0) + :pypi:`pytest-assertions` Pytest Assertions Apr 27, 2022 N/A N/A + :pypi:`pytest-assertutil` pytest-assertutil May 10, 2019 N/A N/A + :pypi:`pytest-assert-utils` Useful assertion utilities for use with pytest Apr 14, 2022 3 - Alpha N/A + :pypi:`pytest-assume` A pytest plugin that allows multiple failures per test Jun 24, 2021 N/A pytest (>=2.7) + :pypi:`pytest-assurka` A pytest plugin for Assurka Studio Aug 04, 2022 N/A N/A + :pypi:`pytest-ast-back-to-python` A plugin for pytest devs to view how assertion rewriting recodes the AST Sep 29, 2019 4 - Beta N/A + :pypi:`pytest-asteroid` PyTest plugin for docker-based testing on database images Aug 15, 2022 N/A pytest (>=6.2.5,<8.0.0) + :pypi:`pytest-astropy` Meta-package containing dependencies for testing Apr 12, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-astropy-header` pytest plugin to add diagnostic information to the header of the test output Sep 06, 2022 3 - Alpha pytest (>=4.6) + :pypi:`pytest-ast-transformer` May 04, 2019 3 - Alpha pytest + :pypi:`pytest-asyncio` Pytest support for asyncio Mar 19, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. May 31, 2023 N/A N/A + :pypi:`pytest-asyncio-network-simulator` pytest-asyncio-network-simulator: Plugin for pytest for simulator the network in tests Jul 31, 2018 3 - Alpha pytest (<3.7.0,>=3.3.2) + :pypi:`pytest-async-mongodb` pytest plugin for async MongoDB Oct 18, 2017 5 - Production/Stable pytest (>=2.5.2) + :pypi:`pytest-async-sqlalchemy` Database testing fixtures using the SQLAlchemy asyncio API Oct 07, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-atomic` Skip rest of tests if previous test failed. Nov 24, 2018 4 - Beta N/A + :pypi:`pytest-attrib` pytest plugin to select tests based on attributes similar to the nose-attrib plugin May 24, 2016 4 - Beta N/A + :pypi:`pytest-austin` Austin plugin for pytest Oct 11, 2020 4 - Beta N/A + :pypi:`pytest-autocap` automatically capture test & fixture stdout/stderr to files May 15, 2022 N/A pytest (<7.2,>=7.1.2) + :pypi:`pytest-autochecklog` automatically check condition and log all the checks Apr 25, 2015 4 - Beta N/A + :pypi:`pytest-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. May 20, 2022 N/A pytest (>=7.0.0) + :pypi:`pytest-automock` Pytest plugin for automatical mocks creation May 16, 2023 N/A pytest ; extra == 'dev' + :pypi:`pytest-auto-parametrize` pytest plugin: avoid repeating arguments in parametrize Oct 02, 2016 3 - Alpha N/A + :pypi:`pytest-autotest` This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. Aug 25, 2021 N/A pytest + :pypi:`pytest-aviator` Aviator's Flakybot pytest plugin that automatically reruns flaky tests. Nov 04, 2022 4 - Beta pytest + :pypi:`pytest-avoidance` Makes pytest skip tests that don not need rerunning May 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A + :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A + :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) + :pypi:`pytest-azure` Pytest utilities and mocks for Azure Jan 18, 2023 3 - Alpha pytest + :pypi:`pytest-azure-devops` Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. Jun 20, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-azurepipelines` Formatting PyTest output for Azure Pipelines UI Oct 20, 2022 5 - Production/Stable pytest (>=5.0.0) + :pypi:`pytest-bandit` A bandit plugin for pytest Feb 23, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bandit-xayon` A bandit plugin for pytest Oct 17, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-base-url` pytest plugin for URL based testing Mar 27, 2022 5 - Production/Stable pytest (>=3.0.0,<8.0.0) + :pypi:`pytest-bdd` BDD for pytest Nov 08, 2022 6 - Mature pytest (>=6.2.0) + :pypi:`pytest-bdd-html` pytest plugin to display BDD info in HTML test report Nov 22, 2022 3 - Alpha pytest (!=6.0.0,>=5.0) + :pypi:`pytest-bdd-ng` BDD for pytest Oct 06, 2022 4 - Beta pytest (>=5.0) + :pypi:`pytest-bdd-splinter` Common steps for pytest bdd and splinter integration Aug 12, 2019 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-bdd-web` A simple plugin to use with pytest Jan 02, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bdd-wrappers` Feb 11, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-beakerlib` A pytest plugin that reports test results to the BeakerLib framework Mar 17, 2017 5 - Production/Stable pytest + :pypi:`pytest-beds` Fixtures for testing Google Appengine (GAE) apps Jun 07, 2016 4 - Beta N/A + :pypi:`pytest-beeprint` use icdiff for better error messages in pytest assertions Jun 09, 2023 4 - Beta N/A + :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A + :pypi:`pytest-benchmark` A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. Oct 25, 2022 5 - Production/Stable pytest (>=3.8) + :pypi:`pytest-better-datadir` A small example package Mar 13, 2023 N/A N/A + :pypi:`pytest-bg-process` Pytest plugin to initialize background process Jan 24, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-bigchaindb` A BigchainDB plugin for pytest. Jan 24, 2022 4 - Beta N/A + :pypi:`pytest-bigquery-mock` Provides a mock fixture for python bigquery client Dec 28, 2022 N/A pytest (>=5.0) + :pypi:`pytest-black` A pytest plugin to enable format checking with black Oct 05, 2020 4 - Beta N/A + :pypi:`pytest-black-multipy` Allow '--black' on older Pythons Jan 14, 2021 5 - Production/Stable pytest (!=3.7.3,>=3.5) ; extra == 'testing' + :pypi:`pytest-black-ng` A pytest plugin to enable format checking with black Oct 20, 2022 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-blame` A pytest plugin helps developers to debug by providing useful commits history. May 04, 2019 N/A pytest (>=4.4.0) + :pypi:`pytest-blender` Blender Pytest plugin. Jan 04, 2023 N/A pytest ; extra == 'dev' + :pypi:`pytest-blink1` Pytest plugin to emit notifications via the Blink(1) RGB LED Jan 07, 2018 4 - Beta N/A + :pypi:`pytest-blockage` Disable network requests during a test run. Dec 21, 2021 N/A pytest + :pypi:`pytest-blocker` pytest plugin to mark a test as blocker and skip all other tests Sep 07, 2015 4 - Beta N/A + :pypi:`pytest-blue` A pytest plugin that adds a \`blue\` fixture for printing stuff in blue. Sep 05, 2022 N/A N/A + :pypi:`pytest-board` Local continuous test runner with pytest and watchdog. Jan 20, 2019 N/A N/A + :pypi:`pytest-boost-xml` Plugin for pytest to generate boost xml reports Nov 30, 2022 4 - Beta N/A + :pypi:`pytest-bootstrap` Mar 04, 2022 N/A N/A + :pypi:`pytest-bpdb` A py.test plug-in to enable drop to bpdb debugger on test failure. Jan 19, 2015 2 - Pre-Alpha N/A + :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Feb 15, 2022 N/A N/A + :pypi:`pytest-breakword` Use breakword with pytest Aug 04, 2021 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-breed-adapter` A simple plugin to connect with breed-server Nov 07, 2018 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-briefcase` A pytest plugin for running tests on a Briefcase project. Jun 14, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-browser` A pytest plugin for console based browser test selection just after the collection phase Dec 10, 2016 3 - Alpha N/A + :pypi:`pytest-browsermob-proxy` BrowserMob proxy plugin for py.test. Jun 11, 2013 4 - Beta N/A + :pypi:`pytest-browserstack-local` \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. Feb 09, 2018 N/A N/A + :pypi:`pytest-budosystems` Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. May 07, 2023 3 - Alpha pytest + :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Jan 29, 2023 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-bugtong-tag` pytest-bugtong-tag is a plugin for pytest Jan 16, 2022 N/A N/A + :pypi:`pytest-bugzilla` py.test bugzilla integration plugin May 05, 2010 4 - Beta N/A + :pypi:`pytest-bugzilla-notifier` A plugin that allows you to execute create, update, and read information from BugZilla bugs Jun 15, 2018 4 - Beta pytest (>=2.9.2) + :pypi:`pytest-buildkite` Plugin for pytest that automatically publishes coverage and pytest report annotations to Buildkite. Jul 13, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-builtin-types` Nov 17, 2021 N/A pytest + :pypi:`pytest-bwrap` Run your tests in Bubblewrap sandboxes Oct 26, 2018 3 - Alpha N/A + :pypi:`pytest-cache` pytest plugin with mechanisms for caching across test runs Jun 04, 2013 3 - Alpha N/A + :pypi:`pytest-cache-assert` Cache assertion data to simplify regression testing of complex serializable data Feb 26, 2023 5 - Production/Stable pytest (>=5.0.0) + :pypi:`pytest-cagoule` Pytest plugin to only run tests affected by changes Jan 01, 2020 3 - Alpha N/A + :pypi:`pytest-cairo` Pytest support for cairo-lang and starknet Apr 17, 2022 N/A pytest + :pypi:`pytest-call-checker` Small pytest utility to easily create test doubles Oct 16, 2022 4 - Beta pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-camel-collect` Enable CamelCase-aware pytest class collection Aug 02, 2020 N/A pytest (>=2.9) + :pypi:`pytest-canonical-data` A plugin which allows to compare results with canonical results, based on previous runs May 08, 2020 2 - Pre-Alpha pytest (>=3.5.0) + :pypi:`pytest-caprng` A plugin that replays pRNG state on failure. May 02, 2018 4 - Beta N/A + :pypi:`pytest-capture-deprecatedwarnings` pytest plugin to capture all deprecatedwarnings and put them in one file Apr 30, 2019 N/A N/A + :pypi:`pytest-capture-warnings` pytest plugin to capture all warnings and put them in one file of your choice May 03, 2022 N/A pytest + :pypi:`pytest-cases` Separate test code from test cases in pytest. Feb 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-cassandra` Cassandra CCM Test Fixtures for pytest Nov 04, 2017 1 - Planning N/A + :pypi:`pytest-catchlog` py.test plugin to catch log messages. This is a fork of pytest-capturelog. Jan 24, 2016 4 - Beta pytest (>=2.6) + :pypi:`pytest-catch-server` Pytest plugin with server for catching HTTP requests. Dec 12, 2019 5 - Production/Stable N/A + :pypi:`pytest-celery` pytest-celery a shim pytest plugin to enable celery.contrib.pytest May 06, 2021 N/A N/A + :pypi:`pytest-chainmaker` pytest plugin for chainmaker Oct 15, 2021 N/A N/A + :pypi:`pytest-chalice` A set of py.test fixtures for AWS Chalice Jul 01, 2020 4 - Beta N/A + :pypi:`pytest-change-assert` 修改报错中文为英文 Oct 19, 2022 N/A N/A + :pypi:`pytest-change-demo` turn . into √,turn F into x Mar 02, 2022 N/A pytest + :pypi:`pytest-change-report` turn . into √,turn F into x Sep 14, 2020 N/A pytest + :pypi:`pytest-change-xds` turn . into √,turn F into x Apr 16, 2022 N/A pytest + :pypi:`pytest-chdir` A pytest fixture for changing current working directory Jan 28, 2020 N/A pytest (>=5.0.0,<6.0.0) + :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Jun 06, 2023 N/A pytest + :pypi:`pytest-checkdocs` check the README when running tests Oct 09, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Jul 22, 2020 5 - Production/Stable pytest (>=2.9.2) + :pypi:`pytest-check-library` check your missing library Jul 17, 2022 N/A N/A + :pypi:`pytest-check-libs` check your missing library Jul 17, 2022 N/A N/A + :pypi:`pytest-check-links` Check links in files Jul 29, 2020 N/A pytest>=7.0 + :pypi:`pytest-check-mk` pytest plugin to test Check_MK checks Nov 19, 2015 4 - Beta pytest + :pypi:`pytest-check-requirements` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :pypi:`pytest-chic-report` A pytest plugin to send a report and printing summary of tests. Jan 31, 2023 5 - Production/Stable N/A + :pypi:`pytest-chunks` Run only a chunk of your test suite Jul 05, 2022 N/A pytest (>=6.0.0) + :pypi:`pytest-circleci` py.test plugin for CircleCI May 03, 2019 N/A N/A + :pypi:`pytest-circleci-parallelized` Parallelize pytest across CircleCI workers. Oct 20, 2022 N/A N/A + :pypi:`pytest-circleci-parallelized-rjp` Parallelize pytest across CircleCI workers. Jun 21, 2022 N/A pytest + :pypi:`pytest-ckan` Backport of CKAN 2.9 pytest plugin and fixtures to CAKN 2.8 Apr 28, 2020 4 - Beta pytest + :pypi:`pytest-clarity` A plugin providing an alternative, colourful diff output for failing assertions. Jun 11, 2021 N/A N/A + :pypi:`pytest-cldf` Easy quality control for CLDF datasets using pytest Nov 07, 2022 N/A pytest (>=3.6) + :pypi:`pytest-click` Pytest plugin for Click Feb 11, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-cli-fixtures` Automatically register fixtures for custom CLI arguments Jul 28, 2022 N/A pytest (~=7.0) + :pypi:`pytest-clld` Jul 06, 2022 N/A pytest (>=3.6) + :pypi:`pytest-cloud` Distributed tests planner plugin for pytest testing framework. Oct 05, 2020 6 - Mature N/A + :pypi:`pytest-cloudflare-worker` pytest plugin for testing cloudflare workers Mar 30, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-cloudist` Distribute tests to cloud machines without fuss Sep 02, 2022 4 - Beta pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cmake` Provide CMake module for Pytest Jan 21, 2023 N/A pytest<8,>=4 + :pypi:`pytest-cmake-presets` Execute CMake Presets via pytest Dec 26, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-cobra` PyTest plugin for testing Smart Contracts for Ethereum blockchain. Jun 29, 2019 3 - Alpha pytest (<4.0.0,>=3.7.1) + :pypi:`pytest-codecarbon` Pytest plugin for measuring carbon emissions Jun 15, 2022 N/A pytest + :pypi:`pytest-codecheckers` pytest plugin to add source code sanity checks (pep8 and friends) Feb 13, 2010 N/A N/A + :pypi:`pytest-codecov` Pytest plugin for uploading pytest-cov results to codecov.io Nov 29, 2022 4 - Beta pytest (>=4.6.0) + :pypi:`pytest-codegen` Automatically create pytest test signatures Aug 23, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-codeowners` Pytest plugin for selecting tests by GitHub CODEOWNERS. Mar 30, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-codestyle` pytest plugin to run pycodestyle Mar 23, 2020 3 - Alpha N/A + :pypi:`pytest-codspeed` Pytest plugin to create CodSpeed benchmarks Dec 02, 2022 5 - Production/Stable pytest>=3.8 + :pypi:`pytest-collect-formatter` Formatter for pytest collect output Mar 29, 2021 5 - Production/Stable N/A + :pypi:`pytest-collect-formatter2` Formatter for pytest collect output May 31, 2021 5 - Production/Stable N/A + :pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A + :pypi:`pytest-commander` An interactive GUI test runner for PyTest Aug 17, 2021 N/A pytest (<7.0.0,>=6.2.4) + :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method May 15, 2022 N/A pytest (>=3.6,<8) + :pypi:`pytest-compare` pytest plugin for comparing call arguments. Mar 30, 2023 5 - Production/Stable N/A + :pypi:`pytest-concurrent` Concurrently execute test cases with multithread, multiprocess and gevent Jan 12, 2019 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-config` Base configurations and utilities for developing your Python project test suite with pytest. Nov 07, 2014 5 - Production/Stable N/A + :pypi:`pytest-confluence-report` Package stands for pytest plugin to upload results into Confluence page. Apr 17, 2022 N/A N/A + :pypi:`pytest-console-scripts` Pytest plugin for testing console scripts May 31, 2023 4 - Beta pytest (>=4.0.0) + :pypi:`pytest-consul` pytest plugin with fixtures for testing consul aware apps Nov 24, 2018 3 - Alpha pytest + :pypi:`pytest-container` Pytest fixtures for writing container based tests Mar 21, 2023 4 - Beta pytest (>=3.10) + :pypi:`pytest-contextfixture` Define pytest fixtures as context managers. Mar 12, 2013 4 - Beta N/A + :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A + :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.0) + :pypi:`pytest-couchdbkit` py.test extension for per-test couchdb databases using couchdbkit Apr 17, 2012 N/A N/A + :pypi:`pytest-count` count erros and send email Jan 12, 2018 4 - Beta N/A + :pypi:`pytest-cov` Pytest plugin for measuring coverage. May 24, 2023 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-cover` Pytest plugin for measuring coverage. Forked from \`pytest-cov\`. Aug 01, 2015 5 - Production/Stable N/A + :pypi:`pytest-coverage` Jun 17, 2015 N/A N/A + :pypi:`pytest-coverage-context` Coverage dynamic context support for PyTest, including sub-processes Jan 04, 2021 4 - Beta pytest (>=6.1.0) + :pypi:`pytest-coveragemarkers` Using pytest markers to track functional coverage and filtering of tests Nov 29, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cov-exclude` Pytest plugin for excluding tests based on coverage data Apr 29, 2016 4 - Beta pytest (>=2.8.0,<2.9.0); extra == 'dev' + :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Jan 30, 2023 5 - Production/Stable pytest (>=7.0) + :pypi:`pytest-cppython` A pytest plugin that imports CPPython testing types Jun 14, 2023 N/A N/A + :pypi:`pytest-cqase` Custom qase pytest plugin Aug 22, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-cram` Run cram tests with pytest. Aug 08, 2020 N/A N/A + :pypi:`pytest-crate` Manages CrateDB instances during your integration tests May 28, 2019 3 - Alpha pytest (>=4.0) + :pypi:`pytest-crayons` A pytest plugin for colorful print statements Mar 19, 2023 N/A pytest + :pypi:`pytest-create` pytest-create Feb 15, 2023 1 - Planning N/A + :pypi:`pytest-cricri` A Cricri plugin for pytest. Jan 27, 2018 N/A pytest + :pypi:`pytest-crontab` add crontab task in crontab Dec 09, 2019 N/A N/A + :pypi:`pytest-csv` CSV output for pytest. Apr 22, 2021 N/A pytest (>=6.0) + :pypi:`pytest-csv-params` Pytest plugin for Test Case Parametrization with CSV files Aug 28, 2022 5 - Production/Stable pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-curio` Pytest support for curio. Oct 07, 2020 N/A N/A + :pypi:`pytest-curl-report` pytest plugin to generate curl command line report Dec 11, 2016 4 - Beta N/A + :pypi:`pytest-custom-concurrency` Custom grouping concurrence for pytest Feb 08, 2021 N/A N/A + :pypi:`pytest-custom-exit-code` Exit pytest test session with custom exit code in different scenarios Aug 07, 2019 4 - Beta pytest (>=4.0.2) + :pypi:`pytest-custom-nodeid` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 07, 2021 N/A N/A + :pypi:`pytest-custom-report` Configure the symbols displayed for test outcomes Jan 30, 2019 N/A pytest + :pypi:`pytest-custom-scheduling` Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report Mar 01, 2021 N/A N/A + :pypi:`pytest-cython` A plugin for testing Cython extension modules Feb 16, 2023 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-cython-collect` Jun 17, 2022 N/A pytest + :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Aug 16, 2020 N/A pytest (>=6.0.1) ; extra == 'test' + :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A + :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A + :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest + :pypi:`pytest-datadir` pytest plugin for test data directories and files Oct 25, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-datadir-mgr` Manager for test data: downloads, artifact caching, and a tmpdir context. Apr 06, 2023 5 - Production/Stable pytest (>=7.1) + :pypi:`pytest-datadir-ng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Dec 25, 2019 5 - Production/Stable pytest + :pypi:`pytest-datadir-nng` Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. Nov 09, 2022 5 - Production/Stable pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-data-extractor` A pytest plugin to extract relevant metadata about tests into an external file (currently only json support) Jul 19, 2022 N/A pytest (>=7.0.1) + :pypi:`pytest-data-file` Fixture "data" and "case_data" for test from yaml file Dec 04, 2019 N/A N/A + :pypi:`pytest-datafiles` py.test plugin to create a 'tmp_path' containing predefined files/directories. Feb 24, 2023 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-datafixtures` Data fixtures for pytest made simple Dec 05, 2020 5 - Production/Stable N/A + :pypi:`pytest-data-from-files` pytest plugin to provide data from files loaded automatically Oct 13, 2021 4 - Beta pytest + :pypi:`pytest-dataplugin` A pytest plugin for managing an archive of test data. Sep 16, 2017 1 - Planning N/A + :pypi:`pytest-datarecorder` A py.test plugin recording and comparing test output. Jan 08, 2023 5 - Production/Stable pytest + :pypi:`pytest-dataset` Plugin for loading different datasets for pytest by prefix from json or yaml files May 01, 2023 5 - Production/Stable N/A + :pypi:`pytest-data-suites` Class-based pytest parametrization Jul 24, 2022 N/A pytest (>=6.0,<8.0) + :pypi:`pytest-datatest` A pytest plugin for test driven data-wrangling (this is the development version of datatest's pytest integration). Oct 15, 2020 4 - Beta pytest (>=3.3) + :pypi:`pytest-db` Session scope fixture "db" for mysql query or change Dec 04, 2019 N/A N/A + :pypi:`pytest-dbfixtures` Databases fixtures plugin for py.test. Dec 07, 2016 4 - Beta N/A + :pypi:`pytest-db-plugin` Nov 27, 2021 N/A pytest (>=5.0) + :pypi:`pytest-dbt` Unit test dbt models with standard python tooling Jun 08, 2023 2 - Pre-Alpha pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-dbt-adapter` A pytest plugin for testing dbt adapter plugins Nov 24, 2021 N/A pytest (<7,>=6) + :pypi:`pytest-dbt-conventions` A pytest plugin for linting a dbt project's conventions Mar 02, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-dbt-core` Pytest extension for dbt. May 03, 2023 N/A pytest (>=6.2.5) ; extra == 'test' + :pypi:`pytest-dbus-notification` D-BUS notifications for pytest results. Mar 05, 2014 5 - Production/Stable N/A + :pypi:`pytest-dbx` Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code Nov 29, 2022 N/A pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-deadfixtures` A simple plugin to list unused fixtures in pytest Jul 23, 2020 5 - Production/Stable N/A + :pypi:`pytest-deepcov` deepcov Mar 30, 2021 N/A N/A + :pypi:`pytest-defer` Aug 24, 2021 N/A N/A + :pypi:`pytest-demo-plugin` pytest示例插件 May 15, 2021 N/A N/A + :pypi:`pytest-dependency` Manage dependencies of tests Feb 14, 2020 4 - Beta N/A + :pypi:`pytest-depends` Tests that depend on other tests Apr 05, 2020 5 - Production/Stable pytest (>=3) + :pypi:`pytest-deprecate` Mark tests as testing a deprecated feature with a warning note. Jul 01, 2019 N/A N/A + :pypi:`pytest-describe` Describe-style plugin for pytest Apr 09, 2023 5 - Production/Stable pytest (<8,>=4.6) + :pypi:`pytest-describe-it` plugin for rich text descriptions Jul 19, 2019 4 - Beta pytest + :pypi:`pytest-devpi-server` DevPI server fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-dhos` Common fixtures for pytest in DHOS services and libraries Sep 07, 2022 N/A N/A + :pypi:`pytest-diamond` pytest plugin for diamond Aug 31, 2015 4 - Beta N/A + :pypi:`pytest-dicom` pytest plugin to provide DICOM fixtures Dec 19, 2018 3 - Alpha pytest + :pypi:`pytest-dictsdiff` Jul 26, 2019 N/A N/A + :pypi:`pytest-diff` A simple plugin to use with pytest Mar 30, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-diffeo` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :pypi:`pytest-diff-selector` Get tests affected by code changes (using git) Feb 24, 2022 4 - Beta pytest (>=6.2.2) ; extra == 'all' + :pypi:`pytest-difido` PyTest plugin for generating Difido reports Oct 23, 2022 4 - Beta pytest (>=4.0.0) + :pypi:`pytest-disable` pytest plugin to disable a test and skip it from testrun Sep 10, 2015 4 - Beta N/A + :pypi:`pytest-disable-plugin` Disable plugins per test Feb 28, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. Feb 05, 2023 4 - Beta pytest (!=6.0.0,<8,>=3.3.2) + :pypi:`pytest-django` A Django plugin for pytest. Dec 07, 2021 5 - Production/Stable pytest (>=5.4.0) + :pypi:`pytest-django-ahead` A Django plugin for pytest. Oct 27, 2016 5 - Production/Stable pytest (>=2.9) + :pypi:`pytest-djangoapp` Nice pytest plugin to help you with Django pluggable application testing. May 19, 2023 4 - Beta pytest + :pypi:`pytest-django-cache-xdist` A djangocachexdist plugin for pytest May 12, 2020 4 - Beta N/A + :pypi:`pytest-django-casperjs` Integrate CasperJS with your django tests as a pytest fixture. Mar 15, 2015 2 - Pre-Alpha N/A + :pypi:`pytest-django-dotenv` Pytest plugin used to setup environment variables with django-dotenv Nov 26, 2019 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-django-factories` Factories for your Django models that can be used as Pytest fixtures. Nov 12, 2020 4 - Beta N/A + :pypi:`pytest-django-filefield` Replaces FileField.storage with something you can patch globally. May 09, 2022 5 - Production/Stable pytest >= 5.2 + :pypi:`pytest-django-gcir` A Django plugin for pytest. Mar 06, 2018 5 - Production/Stable N/A + :pypi:`pytest-django-haystack` Cleanup your Haystack indexes between tests Sep 03, 2017 5 - Production/Stable pytest (>=2.3.4) + :pypi:`pytest-django-ifactory` A model instance factory for pytest-django Jun 06, 2023 5 - Production/Stable N/A + :pypi:`pytest-django-lite` The bare minimum to integrate py.test with Django. Jan 30, 2014 N/A N/A + :pypi:`pytest-django-liveserver-ssl` Jan 20, 2022 3 - Alpha N/A + :pypi:`pytest-django-model` A Simple Way to Test your Django Models Feb 14, 2019 4 - Beta N/A + :pypi:`pytest-django-ordering` A pytest plugin for preserving the order in which Django runs tests. Jul 25, 2019 5 - Production/Stable pytest (>=2.3.0) + :pypi:`pytest-django-queries` Generate performance reports from your django database performance tests. Mar 01, 2021 N/A N/A + :pypi:`pytest-djangorestframework` A djangorestframework plugin for pytest Aug 11, 2019 4 - Beta N/A + :pypi:`pytest-django-rq` A pytest plugin to help writing unit test for django-rq Apr 13, 2020 4 - Beta N/A + :pypi:`pytest-django-sqlcounts` py.test plugin for reporting the number of SQLs executed per django testcase. Jun 16, 2015 4 - Beta N/A + :pypi:`pytest-django-testing-postgresql` Use a temporary PostgreSQL database with pytest-django Jan 31, 2022 4 - Beta N/A + :pypi:`pytest-doc` A documentation plugin for py.test. Jun 28, 2015 5 - Production/Stable N/A + :pypi:`pytest-docfiles` pytest plugin to test codeblocks in your documentation. Dec 22, 2021 4 - Beta pytest (>=3.7.0) + :pypi:`pytest-docgen` An RST Documentation Generator for pytest-based test suites Apr 17, 2020 N/A N/A + :pypi:`pytest-docker` Simple pytest fixtures for Docker and docker-compose based tests Sep 14, 2022 N/A pytest (<8.0,>=4.0) + :pypi:`pytest-docker-apache-fixtures` Pytest fixtures for testing with apache2 (httpd). Feb 16, 2022 4 - Beta pytest + :pypi:`pytest-docker-butla` Jun 16, 2019 3 - Alpha N/A + :pypi:`pytest-dockerc` Run, manage and stop Docker Compose project from Docker API Oct 09, 2020 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-docker-compose` Manages Docker containers during your integration tests Jan 26, 2021 5 - Production/Stable pytest (>=3.3) + :pypi:`pytest-docker-db` A plugin to use docker databases for pytests Mar 20, 2021 5 - Production/Stable pytest (>=3.1.1) + :pypi:`pytest-docker-fixtures` pytest docker fixtures May 02, 2023 3 - Alpha pytest + :pypi:`pytest-docker-git-fixtures` Pytest fixtures for testing with git scm. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-haproxy-fixtures` Pytest fixtures for testing with haproxy. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-pexpect` pytest plugin for writing functional tests with pexpect and docker Jan 14, 2019 N/A pytest + :pypi:`pytest-docker-postgresql` A simple plugin to use with pytest Sep 24, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-docker-py` Easy to use, simple to extend, pytest plugin that minimally leverages docker-py. Nov 27, 2018 N/A pytest (==4.0.0) + :pypi:`pytest-docker-registry-fixtures` Pytest fixtures for testing with docker registries. Apr 08, 2022 4 - Beta pytest + :pypi:`pytest-docker-service` pytest plugin to start docker container Feb 22, 2023 3 - Alpha pytest (>=7.1.3) + :pypi:`pytest-docker-squid-fixtures` Pytest fixtures for testing with squid. Feb 09, 2022 4 - Beta pytest + :pypi:`pytest-docker-tools` Docker integration tests for pytest Feb 17, 2022 4 - Beta pytest (>=6.0.1) + :pypi:`pytest-docs` Documentation tool for pytest Nov 11, 2018 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-docstyle` pytest plugin to run pydocstyle Mar 23, 2020 3 - Alpha N/A + :pypi:`pytest-doctest-custom` A py.test plugin for customizing string representations of doctest results. Jul 25, 2016 4 - Beta N/A + :pypi:`pytest-doctest-ellipsis-markers` Setup additional values for ELLIPSIS_MARKER for doctests Jan 12, 2018 4 - Beta N/A + :pypi:`pytest-doctest-import` A simple pytest plugin to import names and add them to the doctest namespace. Nov 13, 2018 4 - Beta pytest (>=3.3.0) + :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Jun 08, 2023 3 - Alpha pytest (>=4.6) + :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) + :pypi:`pytest-doorstop` A pytest plugin for adding test results into doorstop items. Jun 09, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-dotenv` A py.test plugin that parses environment files before running tests Jun 16, 2020 4 - Beta pytest (>=5.0.0) + :pypi:`pytest-draw` Pytest plugin for randomly selecting a specific number of tests Mar 21, 2023 3 - Alpha pytest + :pypi:`pytest-drf` A Django REST framework plugin for pytest. Jul 12, 2022 5 - Production/Stable pytest (>=3.7) + :pypi:`pytest-drivings` Tool to allow webdriver automation to be ran locally or remotely Jan 13, 2021 N/A N/A + :pypi:`pytest-drop-dup-tests` A Pytest plugin to drop duplicated tests during collection May 23, 2020 4 - Beta pytest (>=2.7) + :pypi:`pytest-dummynet` A py.test plugin providing access to a dummynet. Dec 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-dump2json` A pytest plugin for dumping test results to json. Jun 29, 2015 N/A N/A + :pypi:`pytest-duration-insights` Jun 25, 2021 N/A N/A + :pypi:`pytest-durations` Pytest plugin reporting fixtures and test functions execution time. Apr 22, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-dynamicrerun` A pytest plugin to rerun tests dynamically based off of test outcome and output. Aug 15, 2020 4 - Beta N/A + :pypi:`pytest-dynamodb` DynamoDB fixtures for pytest Jun 12, 2023 5 - Production/Stable pytest + :pypi:`pytest-easy-addoption` pytest-easy-addoption: Easy way to work with pytest addoption Jan 22, 2020 N/A N/A + :pypi:`pytest-easy-api` Simple API testing with pytest Mar 26, 2018 N/A N/A + :pypi:`pytest-easyMPI` Package that supports mpi tests in pytest Oct 21, 2020 N/A N/A + :pypi:`pytest-easyread` pytest plugin that makes terminal printouts of the reports easier to read Nov 17, 2017 N/A N/A + :pypi:`pytest-easy-server` Pytest plugin for easy testing against servers May 01, 2021 4 - Beta pytest (<5.0.0,>=4.3.1) ; python_version < "3.5" + :pypi:`pytest-ebics-sandbox` A pytest plugin for testing against an EBICS sandbox server. Requires docker. Aug 15, 2022 N/A N/A + :pypi:`pytest-ec2` Pytest execution on EC2 instance Oct 22, 2019 3 - Alpha N/A + :pypi:`pytest-echo` pytest plugin with mechanisms for echoing environment variables, package version and generic attributes Jan 08, 2020 5 - Production/Stable N/A + :pypi:`pytest-ekstazi` Pytest plugin to select test using Ekstazi algorithm Sep 10, 2022 N/A pytest + :pypi:`pytest-elasticsearch` Elasticsearch fixtures and fixture factories for Pytest. Mar 01, 2022 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) + :pypi:`pytest-eliot` An eliot plugin for pytest. Aug 31, 2022 1 - Planning pytest (>=5.4.0) + :pypi:`pytest-elk-reporter` A simple plugin to use with pytest Jan 24, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-email` Send execution result email Jul 08, 2020 N/A pytest + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Jun 14, 2023 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Jun 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Jun 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Jun 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Jun 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Jun 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Jun 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-embrace` 💝 Dataclasses-as-tests. Describe the runtime once and multiply coverage with no boilerplate. Mar 25, 2023 N/A pytest (>=7.0,<8.0) + :pypi:`pytest-emoji` A pytest plugin that adds emojis to your test result report Feb 19, 2019 4 - Beta pytest (>=4.2.1) + :pypi:`pytest-emoji-output` Pytest plugin to represent test output with emoji support Apr 09, 2023 4 - Beta pytest (==7.0.1) + :pypi:`pytest-enabler` Enable installed pytest plugins May 12, 2023 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-encode` set your encoding and logger Nov 06, 2021 N/A N/A + :pypi:`pytest-encode-kane` set your encoding and logger Nov 16, 2021 N/A pytest + :pypi:`pytest-enhanced-reports` Enhanced test reports for pytest Dec 15, 2022 N/A N/A + :pypi:`pytest-enhancements` Improvements for pytest (rejected upstream) Oct 30, 2019 4 - Beta N/A + :pypi:`pytest-env` py.test plugin that allows you to add environment variables. Jun 15, 2023 5 - Production/Stable pytest>=7.3.1 + :pypi:`pytest-envfiles` A py.test plugin that parses environment files before running tests Oct 08, 2015 3 - Alpha N/A + :pypi:`pytest-env-info` Push information about the running pytest into envvars Nov 25, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-envraw` py.test plugin that allows you to add environment variables. Aug 27, 2020 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-envvars` Pytest plugin to validate use of envvars on your tests Jun 13, 2020 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-env-yaml` Apr 02, 2019 N/A N/A + :pypi:`pytest-eradicate` pytest plugin to check for commented out code Sep 08, 2020 N/A pytest (>=2.4.2) + :pypi:`pytest-error-for-skips` Pytest plugin to treat skipped tests a test failure Dec 19, 2019 4 - Beta pytest (>=4.6) + :pypi:`pytest-eth` PyTest plugin for testing Smart Contracts for Ethereum Virtual Machine (EVM). Aug 14, 2020 1 - Planning N/A + :pypi:`pytest-ethereum` pytest-ethereum: Pytest library for ethereum projects. Jun 24, 2019 3 - Alpha pytest (==3.3.2); extra == 'dev' + :pypi:`pytest-eucalyptus` Pytest Plugin for BDD Jun 28, 2022 N/A pytest (>=4.2.0) + :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' + :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. May 05, 2023 4 - Beta pytest>=7 + :pypi:`pytest-excel` pytest plugin for generating excel reports Jan 31, 2022 5 - Production/Stable N/A + :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A + :pypi:`pytest-exception-script` Walk your code through exception script to check it's resiliency to failures. Aug 04, 2020 3 - Alpha pytest + :pypi:`pytest-executable` pytest plugin for testing executables Mar 25, 2023 N/A pytest (<8,>=4.3) + :pypi:`pytest-execution-timer` A timer for the phases of Pytest's execution. Dec 24, 2021 4 - Beta N/A + :pypi:`pytest-expect` py.test plugin to store test expectations and mark tests based on them Apr 21, 2016 4 - Beta N/A + :pypi:`pytest-expectdir` A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one Mar 19, 2023 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-expecter` Better testing with expecter and pytest. Sep 18, 2022 5 - Production/Stable N/A + :pypi:`pytest-expectr` This plugin is used to expect multiple assert using pytest framework. Oct 05, 2018 N/A pytest (>=2.4.2) + :pypi:`pytest-expect-test` A fixture to support expect tests in pytest Apr 10, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-experiments` A pytest plugin to help developers of research-oriented software projects keep track of the results of their numerical experiments. Dec 13, 2021 4 - Beta pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-explicit` A Pytest plugin to ignore certain marked tests by default Jun 15, 2021 5 - Production/Stable pytest + :pypi:`pytest-exploratory` Interactive console for pytest. Feb 21, 2022 N/A pytest (>=6.2) + :pypi:`pytest-extensions` A collection of helpers for pytest to ease testing Aug 17, 2022 4 - Beta pytest ; extra == 'testing' + :pypi:`pytest-external-blockers` a special outcome for tests that are blocked for external reasons Oct 05, 2021 N/A pytest + :pypi:`pytest-extra-durations` A pytest plugin to get durations on a per-function basis and per module basis. Apr 21, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-extra-markers` Additional pytest markers to dynamically enable/disable tests viia CLI flags Mar 05, 2023 4 - Beta pytest + :pypi:`pytest-fabric` Provides test utilities to run fabric task tests by using docker containers Sep 12, 2018 5 - Production/Stable N/A + :pypi:`pytest-factor` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :pypi:`pytest-factory` Use factories for test setup with py.test Sep 06, 2020 3 - Alpha pytest (>4.3) + :pypi:`pytest-factoryboy` Factory Boy support for pytest. Dec 01, 2022 6 - Mature pytest (>=5.0.0) + :pypi:`pytest-factoryboy-fixtures` Generates pytest fixtures that allow the use of type hinting Jun 25, 2020 N/A N/A + :pypi:`pytest-factoryboy-state` Simple factoryboy random state management Mar 22, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-failed-screen-record` Create a video of the screen when pytest fails Jan 05, 2023 4 - Beta pytest (>=7.1.2d,<8.0.0) + :pypi:`pytest-failed-screenshot` Test case fails,take a screenshot,save it,attach it to the allure Apr 21, 2021 N/A N/A + :pypi:`pytest-failed-to-verify` A pytest plugin that helps better distinguishing real test failures from setup flakiness. Aug 08, 2019 5 - Production/Stable pytest (>=4.1.0) + :pypi:`pytest-fail-slow` Fail tests that take too long to run Aug 13, 2022 4 - Beta pytest (>=6.0) + :pypi:`pytest-faker` Faker integration with the pytest framework. Dec 19, 2016 6 - Mature N/A + :pypi:`pytest-falcon` Pytest helpers for Falcon. Sep 07, 2016 4 - Beta N/A + :pypi:`pytest-falcon-client` Pytest \`client\` fixture for the Falcon Framework Mar 19, 2019 N/A N/A + :pypi:`pytest-fantasy` Pytest plugin for Flask Fantasy Framework Mar 14, 2019 N/A N/A + :pypi:`pytest-fastapi` Dec 27, 2020 N/A N/A + :pypi:`pytest-fastapi-deps` A fixture which allows easy replacement of fastapi dependencies for testing Jul 20, 2022 5 - Production/Stable pytest + :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Jun 15, 2023 4 - Beta pytest (>=4.4) + :pypi:`pytest-fast-first` Pytest plugin that runs fast tests first Jan 19, 2023 3 - Alpha pytest + :pypi:`pytest-faulthandler` py.test plugin that activates the fault handler module for tests (dummy package) Jul 04, 2019 6 - Mature pytest (>=5.0) + :pypi:`pytest-fauxfactory` Integration of fauxfactory into pytest. Dec 06, 2017 5 - Production/Stable pytest (>=3.2) + :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A + :pypi:`pytest-filecov` A pytest plugin to detect unused files Jun 27, 2021 4 - Beta pytest + :pypi:`pytest-filedata` easily load data from files Jan 17, 2019 4 - Beta N/A + :pypi:`pytest-filemarker` A pytest plugin that runs marked tests when files change. Dec 01, 2020 N/A pytest + :pypi:`pytest-file-watcher` Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files. Mar 23, 2023 N/A pytest + :pypi:`pytest-filter-case` run test cases filter by mark Nov 05, 2020 N/A N/A + :pypi:`pytest-filter-subpackage` Pytest plugin for filtering based on sub-packages Dec 12, 2022 3 - Alpha pytest (>=3.0) + :pypi:`pytest-find-dependencies` A pytest plugin to find dependencies between tests Apr 09, 2022 4 - Beta pytest (>=4.3.0) + :pypi:`pytest-finer-verdicts` A pytest plugin to treat non-assertion failures as test errors. Jun 18, 2020 N/A pytest (>=5.4.3) + :pypi:`pytest-firefox` pytest plugin to manipulate firefox Aug 08, 2017 3 - Alpha pytest (>=3.0.2) + :pypi:`pytest-fixture-classes` Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers Jan 20, 2023 4 - Beta pytest + :pypi:`pytest-fixture-config` Fixture configuration utils for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-fixture-maker` Pytest plugin to load fixtures from YAML files Sep 21, 2021 N/A N/A + :pypi:`pytest-fixture-marker` A pytest plugin to add markers based on fixtures used. Oct 11, 2020 5 - Production/Stable N/A + :pypi:`pytest-fixture-order` pytest plugin to control fixture evaluation order May 16, 2022 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-fixture-ref` Lets users reference fixtures without name matching magic. Nov 17, 2022 4 - Beta N/A + :pypi:`pytest-fixture-rtttg` Warn or fail on fixture name clash Feb 23, 2022 N/A pytest (>=7.0.1,<8.0.0) + :pypi:`pytest-fixtures` Common fixtures for pytest May 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-fixture-tools` Plugin for pytest which provides tools for fixtures Aug 18, 2020 6 - Mature pytest + :pypi:`pytest-fixture-typecheck` A pytest plugin to assert type annotations at runtime. Aug 24, 2021 N/A pytest + :pypi:`pytest-flake8` pytest plugin to check FLAKE8 requirements Mar 18, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-flake8-path` A pytest fixture for testing flake8 plugins. Jun 16, 2023 5 - Production/Stable pytest + :pypi:`pytest-flake8-v2` pytest plugin to check FLAKE8 requirements Mar 01, 2022 5 - Production/Stable pytest (>=7.0) + :pypi:`pytest-flakefinder` Runs tests multiple times to expose flakiness. Oct 26, 2022 4 - Beta pytest (>=2.7.1) + :pypi:`pytest-flakes` pytest plugin to check source code with pyflakes Dec 02, 2021 5 - Production/Stable pytest (>=5) + :pypi:`pytest-flaptastic` Flaptastic py.test plugin Mar 17, 2019 N/A N/A + :pypi:`pytest-flask` A set of py.test fixtures to test Flask applications. Feb 27, 2021 5 - Production/Stable pytest (>=5.2) + :pypi:`pytest-flask-ligand` Pytest fixtures and helper functions to use for testing flask-ligand microservices. Apr 25, 2023 4 - Beta pytest (~=7.3) + :pypi:`pytest-flask-sqlalchemy` A pytest plugin for preserving test isolation in Flask-SQlAlchemy using database transactions. Apr 30, 2022 4 - Beta pytest (>=3.2.1) + :pypi:`pytest-flask-sqlalchemy-transactions` Run tests in transactions using pytest, Flask, and SQLalchemy. Aug 02, 2018 4 - Beta pytest (>=3.2.1) + :pypi:`pytest-flexreport` Apr 15, 2023 4 - Beta pytest + :pypi:`pytest-fluent` A pytest plugin in order to provide logs via fluentd Jul 12, 2022 4 - Beta pytest + :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest + :pypi:`pytest-focus` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest + :pypi:`pytest-forbid` Mar 07, 2023 N/A pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-forcefail` py.test plugin to make the test failing regardless of pytest.mark.xfail May 15, 2018 4 - Beta N/A + :pypi:`pytest-forward-compatability` A name to avoid typosquating pytest-foward-compatibility Sep 06, 2020 N/A N/A + :pypi:`pytest-forward-compatibility` A pytest plugin to shim pytest commandline options for fowards compatibility Sep 29, 2020 N/A N/A + :pypi:`pytest-frappe` Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications May 03, 2023 4 - Beta pytest>=7.0.0 + :pypi:`pytest-freezegun` Wrap tests with fixtures in freeze_time Jul 19, 2020 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-freezer` Pytest plugin providing a fixture interface for spulec/freezegun Jun 17, 2023 N/A pytest>=3.6 + :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A + :pypi:`pytest-frozen-uuids` Deterministically frozen UUID's for your tests Apr 17, 2022 N/A pytest (>=3.0) + :pypi:`pytest-func-cov` Pytest plugin for measuring function coverage Apr 15, 2021 3 - Alpha pytest (>=5) + :pypi:`pytest-funparam` An alternative way to parametrize test cases. Dec 02, 2021 4 - Beta pytest >=4.6.0 + :pypi:`pytest-fxa` pytest plugin for Firefox Accounts Aug 28, 2018 5 - Production/Stable N/A + :pypi:`pytest-fxtest` Oct 27, 2020 N/A N/A + :pypi:`pytest-fzf` fzf-based test selector for pytest Aug 17, 2022 1 - Planning pytest (>=7.1.2) + :pypi:`pytest-gather-fixtures` set up asynchronous pytest fixtures concurrently Apr 12, 2022 N/A pytest (>=6.0.0) + :pypi:`pytest-gc` The garbage collector plugin for py.test Feb 01, 2018 N/A N/A + :pypi:`pytest-gcov` Uses gcov to measure test coverage of a C library Feb 01, 2018 3 - Alpha N/A + :pypi:`pytest-gevent` Ensure that gevent is properly patched when invoking pytest Feb 25, 2020 N/A pytest + :pypi:`pytest-gherkin` A flexible framework for executing BDD gherkin tests Jul 27, 2019 3 - Alpha pytest (>=5.0.0) + :pypi:`pytest-gh-log-group` pytest plugin for gh actions Jan 11, 2022 3 - Alpha pytest + :pypi:`pytest-ghostinspector` For finding/executing Ghost Inspector tests May 17, 2016 3 - Alpha N/A + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Jun 14, 2023 N/A N/A + :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-git-fixtures` Pytest fixtures for testing with git. Mar 11, 2021 4 - Beta pytest + :pypi:`pytest-github` Plugin for py.test that associates tests with github issues using a marker. Mar 07, 2019 5 - Production/Stable N/A + :pypi:`pytest-github-actions-annotate-failures` pytest plugin to annotate failed tests with a workflow command for GitHub Actions May 04, 2023 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-github-report` Generate a GitHub report using pytest in GitHub Workflows Jun 03, 2022 4 - Beta N/A + :pypi:`pytest-gitignore` py.test plugin to ignore the same files as git Jul 17, 2015 4 - Beta N/A + :pypi:`pytest-gitlabci-parallelized` Parallelize pytest across GitLab CI workers. Mar 08, 2023 N/A N/A + :pypi:`pytest-git-selector` Utility to select tests that have had its dependencies modified (as identified by git diff) Nov 17, 2022 N/A N/A + :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Jul 22, 2022 4 - Beta pytest + :pypi:`pytest-gnupg-fixtures` Pytest fixtures for testing with gnupg. Mar 04, 2021 4 - Beta pytest + :pypi:`pytest-golden` Plugin for pytest that offloads expected outputs to data files Jul 18, 2022 N/A pytest (>=6.1.2) + :pypi:`pytest-goldie` A plugin to support golden tests with pytest. May 23, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-google-chat` Notify google chat channel for test results Mar 27, 2022 4 - Beta pytest + :pypi:`pytest-graphql-schema` Get graphql schema as fixture for pytest Oct 18, 2019 N/A N/A + :pypi:`pytest-greendots` Green progress dots Feb 08, 2014 3 - Alpha N/A + :pypi:`pytest-growl` Growl notifications for pytest results. Jan 13, 2014 5 - Production/Stable N/A + :pypi:`pytest-grpc` pytest plugin for grpc May 01, 2020 N/A pytest (>=3.6.0) + :pypi:`pytest-grunnur` Py.Test plugin for Grunnur-based packages. Feb 05, 2023 N/A N/A + :pypi:`pytest-hammertime` Display "🔨 " instead of "." for passed pytest tests. Jul 28, 2018 N/A pytest + :pypi:`pytest-harmony` Chain tests and data with pytest Jan 17, 2023 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-harvest` Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. Jun 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-helm-chart` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Jun 15, 2020 4 - Beta pytest (>=5.4.2,<6.0.0) + :pypi:`pytest-helm-charts` A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. Mar 08, 2023 4 - Beta pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A + :pypi:`pytest-helpers` pytest helpers May 17, 2020 N/A pytest + :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Dec 29, 2021 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) + :pypi:`pytest-historic` Custom report to display pytest historical execution records Apr 08, 2020 N/A pytest + :pypi:`pytest-historic-hook` Custom listener to store execution results into MYSQL DB, which is used for pytest-historic report Apr 08, 2020 N/A pytest + :pypi:`pytest-homeassistant` A pytest plugin for use with homeassistant custom components. Aug 12, 2020 4 - Beta N/A + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 16, 2023 3 - Alpha pytest (==7.3.1) + :pypi:`pytest-honey` A simple plugin to use with pytest Jan 07, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-honors` Report on tests that honor constraints, and guard against regressions Mar 06, 2020 4 - Beta N/A + :pypi:`pytest-hot-reloading` Jun 16, 2023 N/A N/A + :pypi:`pytest-hot-test` A plugin that tracks test changes Dec 10, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-hoverfly` Simplify working with Hoverfly from pytest Jan 30, 2023 N/A pytest (>=5.0) + :pypi:`pytest-hoverfly-wrapper` Integrates the Hoverfly HTTP proxy into Pytest Feb 27, 2023 5 - Production/Stable pytest (>=3.7.0) + :pypi:`pytest-hpfeeds` Helpers for testing hpfeeds in your python project Feb 28, 2023 4 - Beta pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-html` pytest plugin for generating HTML reports Apr 08, 2023 5 - Production/Stable pytest (!=6.0.0,>=5.0) + :pypi:`pytest-html-lee` optimized pytest plugin for generating HTML reports Jun 30, 2020 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-html-merger` Pytest HTML reports merging utility Apr 03, 2022 N/A N/A + :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Mar 04, 2022 5 - Production/Stable N/A + :pypi:`pytest-html-profiling` Pytest plugin for generating HTML reports with per-test profiling and optionally call graph visualizations. Based on pytest-html by Dave Hunt. Feb 11, 2020 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-html-reporter` Generates a static html report based on pytest framework Feb 13, 2022 N/A N/A + :pypi:`pytest-html-report-merger` Aug 31, 2022 N/A N/A + :pypi:`pytest-html-thread` pytest plugin for generating HTML reports Dec 29, 2020 5 - Production/Stable N/A + :pypi:`pytest-http` Fixture "http" for http requests Dec 05, 2019 N/A N/A + :pypi:`pytest-httpbin` Easily test your HTTP library against a local copy of httpbin May 08, 2023 5 - Production/Stable pytest ; extra == 'test' + :pypi:`pytest-httpdbg` A pytest plugin to record HTTP(S) requests with stack trace May 09, 2023 3 - Alpha pytest (>=7.0.0) + :pypi:`pytest-http-mocker` Pytest plugin for http mocking (via https://github.com/vilus/mocker) Oct 20, 2019 N/A N/A + :pypi:`pytest-httpretty` A thin wrapper of HTTPretty for pytest Feb 16, 2014 3 - Alpha N/A + :pypi:`pytest-httpserver` pytest-httpserver is a httpserver for pytest May 22, 2023 3 - Alpha N/A + :pypi:`pytest-httptesting` http_testing framework on top of pytest Jun 03, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-httpx` Send responses to httpx. Apr 12, 2023 5 - Production/Stable pytest (<8.0,>=6.0) + :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Feb 16, 2023 N/A pytest (>=7.2.1) + :pypi:`pytest-hue` Visualise PyTest status via your Phillips Hue lights May 09, 2019 N/A N/A + :pypi:`pytest-hylang` Pytest plugin to allow running tests written in hylang Mar 28, 2021 N/A pytest + :pypi:`pytest-hypo-25` help hypo module for pytest Jan 12, 2020 3 - Alpha N/A + :pypi:`pytest-ibutsu` A plugin to sent pytest results to an Ibutsu server Aug 05, 2022 4 - Beta pytest>=7.1 + :pypi:`pytest-icdiff` use icdiff for better error messages in pytest assertions Aug 09, 2022 4 - Beta N/A + :pypi:`pytest-idapro` A pytest plugin for idapython. Allows a pytest setup to run tests outside and inside IDA in an automated manner by runnig pytest inside IDA and by mocking idapython api Nov 03, 2018 N/A N/A + :pypi:`pytest-idem` A pytest plugin to help with testing idem projects Sep 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-idempotent` Pytest plugin for testing function idempotence. Jul 25, 2022 N/A N/A + :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 23, 2021 5 - Production/Stable N/A + :pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest + :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A + :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A + :pypi:`pytest-info-collector` pytest plugin to collect information from tests May 26, 2019 3 - Alpha N/A + :pypi:`pytest-informative-node` display more node ininformation. Apr 25, 2019 4 - Beta N/A + :pypi:`pytest-infrastructure` pytest stack validation prior to testing executing Apr 12, 2020 4 - Beta N/A + :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A + :pypi:`pytest-inline` A pytest plugin for writing inline tests. Feb 08, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Feb 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-extensions` Inmanta tests package Apr 12, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules May 17, 2023 5 - Production/Stable N/A + :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Jun 16, 2022 4 - Beta N/A + :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A + :pypi:`pytest-insta` A practical snapshot testing plugin for pytest Nov 02, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-instafail` pytest plugin to show failures instantly Mar 31, 2023 4 - Beta pytest (>=5) + :pypi:`pytest-instrument` pytest plugin to instrument tests Apr 05, 2020 5 - Production/Stable pytest (>=5.1.0) + :pypi:`pytest-integration` Organizing pytests by integration or not Nov 17, 2022 N/A N/A + :pypi:`pytest-integration-mark` Automatic integration test marking and excluding plugin for pytest May 22, 2023 N/A pytest (>=5.2) + :pypi:`pytest-interactive` A pytest plugin for console based interactive test selection just after the collection phase Nov 30, 2017 3 - Alpha N/A + :pypi:`pytest-intercept-remote` Pytest plugin for intercepting outgoing connection requests during pytest run. May 24, 2021 4 - Beta pytest (>=4.6) + :pypi:`pytest-interface-tester` Pytest plugin for checking charm relation interface protocol compliance. May 09, 2023 4 - Beta pytest + :pypi:`pytest-invenio` Pytest fixtures for Invenio. Jun 02, 2023 5 - Production/Stable pytest (<7.2.0,>=6) + :pypi:`pytest-involve` Run tests covering a specific file or changeset Feb 02, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ipdb` A py.test plug-in to enable drop to ipdb debugger on test failure. Mar 20, 2013 2 - Pre-Alpha N/A + :pypi:`pytest-ipynb` THIS PROJECT IS ABANDONED Jan 29, 2019 3 - Alpha N/A + :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest + :pypi:`pytest-isort` py.test plugin to check import ordering using isort Oct 31, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-is-running` pytest plugin providing a function to check if pytest is running. Jun 16, 2023 5 - Production/Stable N/A + :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 22, 2020 4 - Beta N/A + :pypi:`pytest-iterassert` Nicer list and iterable assertion messages for pytest May 11, 2020 3 - Alpha N/A + :pypi:`pytest-iters` A contextmanager pytest fixture for handling multiple mock iters May 24, 2022 N/A N/A + :pypi:`pytest-jasmine` Run jasmine tests from your pytest test suite Nov 04, 2017 1 - Planning N/A + :pypi:`pytest-jelastic` Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment. Nov 16, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-jest` A custom jest-pytest oriented Pytest reporter May 22, 2018 4 - Beta pytest (>=3.3.2) + :pypi:`pytest-jinja` A plugin to generate customizable jinja-based HTML reports in pytest Oct 04, 2022 3 - Alpha pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Jun 12, 2023 3 - Alpha N/A + :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jun 14, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Jun 06, 2023 4 - Beta pytest + :pypi:`pytest-job-selection` A pytest plugin for load balancing test suites Jan 30, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-jobserver` Limit parallel tests with posix jobserver. May 15, 2019 5 - Production/Stable pytest + :pypi:`pytest-joke` Test failures are better served with humor. Oct 08, 2019 4 - Beta pytest (>=4.2.1) + :pypi:`pytest-json` Generate JSON test reports Jan 18, 2016 4 - Beta N/A + :pypi:`pytest-json-fixtures` JSON output for the --fixtures flag Mar 14, 2023 4 - Beta N/A + :pypi:`pytest-jsonlint` UNKNOWN Aug 04, 2016 N/A N/A + :pypi:`pytest-json-report` A pytest plugin to report test results as JSON files Mar 15, 2022 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-jtr` pytest plugin supporting json test report output Nov 29, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Mar 30, 2023 4 - Beta pytest + :pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest + :pypi:`pytest-kafka` Zookeeper, Kafka server, and Kafka consumer fixtures for Pytest Jun 14, 2023 N/A pytest + :pypi:`pytest-kafkavents` A plugin to send pytest events to Kafka Sep 08, 2021 4 - Beta pytest + :pypi:`pytest-kasima` Display horizontal lines above and below the captured standard output for easy viewing. Jan 26, 2023 5 - Production/Stable pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-keep-together` Pytest plugin to customize test ordering by running all 'related' tests together Dec 07, 2022 5 - Production/Stable pytest + :pypi:`pytest-kexi` Apr 29, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-kind` Kubernetes test support with KIND for pytest Nov 30, 2022 5 - Production/Stable N/A + :pypi:`pytest-kivy` Kivy GUI tests fixtures using pytest Jul 06, 2021 4 - Beta pytest (>=3.6) + :pypi:`pytest-knows` A pytest plugin that can automaticly skip test case based on dependence info calculated by trace Aug 22, 2014 N/A N/A + :pypi:`pytest-konira` Run Konira DSL tests with py.test Oct 09, 2011 N/A N/A + :pypi:`pytest-koopmans` A plugin for testing the koopmans package Nov 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-krtech-common` pytest krtech common library Nov 28, 2016 4 - Beta N/A + :pypi:`pytest-kubernetes` May 17, 2023 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-kwparametrize` Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks Jan 22, 2021 N/A pytest (>=6) + :pypi:`pytest-lambda` Define pytest fixtures with lambda functions. Aug 20, 2022 3 - Alpha pytest (>=3.6,<8) + :pypi:`pytest-lamp` Jan 06, 2017 3 - Alpha N/A + :pypi:`pytest-langchain` Pytest-style test runner for langchain agents Feb 26, 2023 N/A pytest + :pypi:`pytest-lark` A package for enhancing pytest Nov 20, 2022 N/A N/A + :pypi:`pytest-launchable` Launchable Pytest Plugin Apr 05, 2023 N/A pytest (>=4.2.0) + :pypi:`pytest-layab` Pytest fixtures for layab. Oct 05, 2020 5 - Production/Stable N/A + :pypi:`pytest-lazy-fixture` It helps to use fixtures in pytest.mark.parametrize Feb 01, 2020 4 - Beta pytest (>=3.2.5) + :pypi:`pytest-lazy-fixtures` Allows you to use fixtures in @pytest.mark.parametrize. May 28, 2023 N/A pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-ldap` python-ldap fixtures for pytest Aug 18, 2020 N/A pytest + :pypi:`pytest-leak-finder` Find the test that's leaking before the one that fails Feb 15, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-leaks` A pytest plugin to trace resource leaks. Nov 27, 2019 1 - Planning N/A + :pypi:`pytest-level` Select tests of a given level or lower Oct 21, 2019 N/A pytest + :pypi:`pytest-libfaketime` A python-libfaketime plugin for pytest. Dec 22, 2018 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-libiio` A pytest plugin to manage interfacing with libiio contexts Jul 11, 2022 4 - Beta N/A + :pypi:`pytest-libnotify` Pytest plugin that shows notifications about the test run Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-ligo` Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-lineno` A pytest plugin to show the line numbers of test functions Dec 04, 2020 N/A pytest + :pypi:`pytest-line-profiler` Profile code executed by pytest May 03, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-line-profiler-apn` Profile code executed by pytest Dec 05, 2022 N/A pytest (>=3.5.0) + :pypi:`pytest-lisa` Pytest plugin for organizing tests. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-listener` A simple network listener May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-litf` A pytest plugin that stream output in LITF format Jan 18, 2021 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-live` Live results for pytest Mar 08, 2020 N/A pytest + :pypi:`pytest-local-badge` Generate local badges (shields) reporting your test suite status. Jan 15, 2023 N/A pytest (>=6.1.0) + :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests Oct 04, 2022 5 - Production/Stable pytest + :pypi:`pytest-localserver` pytest plugin to test server connections locally. Jan 30, 2023 4 - Beta N/A + :pypi:`pytest-localstack` Pytest plugin for AWS integration tests Jun 07, 2023 4 - Beta pytest (>=6.0.0,<7.0.0) + :pypi:`pytest-lockable` lockable resource plugin for pytest Jul 20, 2022 5 - Production/Stable pytest + :pypi:`pytest-locker` Used to lock object during testing. Essentially changing assertions from being hard coded to asserting that nothing changed Oct 29, 2021 N/A pytest (>=5.4) + :pypi:`pytest-log` print log Aug 15, 2021 N/A pytest (>=3.8) + :pypi:`pytest-logbook` py.test plugin to capture logbook log messages Nov 23, 2015 5 - Production/Stable pytest (>=2.8) + :pypi:`pytest-logdog` Pytest plugin to test logging Jun 15, 2021 1 - Planning pytest (>=6.2.0) + :pypi:`pytest-logfest` Pytest plugin providing three logger fixtures with basic or full writing to log files Jul 21, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Jul 25, 2019 4 - Beta pytest (>=3.2) + :pypi:`pytest-logging` Configures logging and allows tweaking the log level with a py.test flag Nov 04, 2015 4 - Beta N/A + :pypi:`pytest-logging-end-to-end-test-tool` Sep 23, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-logikal` Common testing environment Jun 04, 2023 5 - Production/Stable pytest (==7.3.1) + :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A + :pypi:`pytest-loguru` Pytest Loguru Apr 12, 2022 5 - Production/Stable N/A + :pypi:`pytest-loop` pytest plugin for looping tests Jul 22, 2022 5 - Production/Stable pytest (>=6) + :pypi:`pytest-lsp` pytest plugin for end-to-end testing of language servers May 19, 2023 3 - Alpha pytest + :pypi:`pytest-manual-marker` pytest marker for marking manual tests Aug 04, 2022 3 - Alpha pytest>=7 + :pypi:`pytest-markdoctest` A pytest plugin to doctest your markdown files Jul 22, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-markdown` Test your markdown docs with pytest Jan 15, 2021 4 - Beta pytest (>=6.0.1,<7.0.0) + :pypi:`pytest-markdown-docs` Run markdown code fences through pytest Mar 09, 2023 N/A N/A + :pypi:`pytest-marker-bugzilla` py.test bugzilla integration plugin, using markers Jan 09, 2020 N/A N/A + :pypi:`pytest-markers-presence` A simple plugin to detect missed pytest tags and markers" Feb 04, 2021 4 - Beta pytest (>=6.0) + :pypi:`pytest-markfiltration` UNKNOWN Nov 08, 2011 3 - Alpha N/A + :pypi:`pytest-mark-no-py3` pytest plugin and bowler codemod to help migrate tests to Python 3 May 17, 2019 N/A pytest + :pypi:`pytest-marks` UNKNOWN Nov 23, 2012 3 - Alpha N/A + :pypi:`pytest-matcher` Match test output against patterns stored in files Dec 10, 2021 5 - Production/Stable N/A + :pypi:`pytest-match-skip` Skip matching marks. Matches partial marks using wildcards. May 15, 2019 4 - Beta pytest (>=4.4.1) + :pypi:`pytest-mat-report` this is report Jan 20, 2021 N/A N/A + :pypi:`pytest-matrix` Provide tools for generating tests from combinations of fixtures. Jun 24, 2020 5 - Production/Stable pytest (>=5.4.3,<6.0.0) + :pypi:`pytest-maybe-context` Simplify tests with warning and exception cases. Apr 16, 2023 N/A pytest (>=7,<8) + :pypi:`pytest-maybe-raises` Pytest fixture for optional exception testing. May 27, 2022 N/A pytest ; extra == 'dev' + :pypi:`pytest-mccabe` pytest plugin to run the mccabe code complexity checker. Jul 22, 2020 3 - Alpha pytest (>=5.4.0) + :pypi:`pytest-md` Plugin for generating Markdown reports for pytest results Jul 11, 2019 3 - Alpha pytest (>=4.2.1) + :pypi:`pytest-md-report` A pytest plugin to make a test results report with Markdown table format. May 28, 2023 4 - Beta pytest (!=6.0.0,<8,>=3.3.2) + :pypi:`pytest-memlog` Log memory usage during tests May 03, 2023 N/A pytest (>=7.3.0,<8.0.0) + :pypi:`pytest-memprof` Estimates memory consumption of test functions Mar 29, 2019 4 - Beta N/A + :pypi:`pytest-memray` A simple plugin to use with pytest Jun 06, 2023 N/A pytest>=7.2 + :pypi:`pytest-menu` A pytest plugin for console based interactive test selection just after the collection phase Oct 04, 2017 3 - Alpha pytest (>=2.4.2) + :pypi:`pytest-mercurial` pytest plugin to write integration tests for projects using Mercurial Python internals Nov 21, 2020 1 - Planning N/A + :pypi:`pytest-mesh` pytest_mesh插件 Aug 05, 2022 N/A pytest (==7.1.2) + :pypi:`pytest-message` Pytest plugin for sending report message of marked tests execution Aug 04, 2022 N/A pytest (>=6.2.5) + :pypi:`pytest-messenger` Pytest to Slack reporting plugin Nov 24, 2022 5 - Production/Stable N/A + :pypi:`pytest-metadata` pytest plugin for test session metadata May 27, 2023 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-metrics` Custom metrics report for pytest Apr 04, 2020 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin Jun 08, 2023 N/A pytest + :pypi:`pytest-mimesis` Mimesis integration with the pytest test runner Mar 21, 2020 5 - Production/Stable pytest (>=4.2) + :pypi:`pytest-minecraft` A pytest plugin for running tests against Minecraft releases Apr 06, 2022 N/A pytest (>=6.0.1) + :pypi:`pytest-mini` A plugin to test mp Feb 06, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A + :pypi:`pytest-mocha` pytest plugin to display test execution output like a mochajs Apr 02, 2020 4 - Beta pytest (>=5.4.0) + :pypi:`pytest-mock` Thin-wrapper around the mock package for easier use with pytest Jun 15, 2023 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-mock-api` A mock API server with configurable routes and responses available as a fixture. Feb 13, 2019 1 - Planning pytest (>=4.0.0) + :pypi:`pytest-mock-generator` A pytest fixture wrapper for https://pypi.org/project/mock-generator May 16, 2022 5 - Production/Stable N/A + :pypi:`pytest-mock-helper` Help you mock HTTP call and generate mock code Jan 24, 2018 N/A pytest + :pypi:`pytest-mockito` Base fixtures for mockito Jul 11, 2018 4 - Beta N/A + :pypi:`pytest-mockredis` An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. Jan 02, 2018 2 - Pre-Alpha N/A + :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Jun 09, 2023 N/A pytest (>=1.0) + :pypi:`pytest-mock-server` Mock server plugin for pytest Jan 09, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-mockservers` A set of fixtures to test your requests to HTTP/UDP servers Mar 31, 2020 N/A pytest (>=4.3.0) + :pypi:`pytest-mocktcp` A pytest plugin for testing TCP clients Oct 11, 2022 N/A pytest + :pypi:`pytest-modified-env` Pytest plugin to fail a test if it leaves modified \`os.environ\` afterwards. Jan 29, 2022 4 - Beta N/A + :pypi:`pytest-modifyjunit` Utility for adding additional properties to junit xml for IDM QE Jan 10, 2019 N/A N/A + :pypi:`pytest-modifyscope` pytest plugin to modify fixture scope Apr 12, 2020 N/A pytest + :pypi:`pytest-molecule` PyTest Molecule Plugin :: discover and run molecule tests Mar 29, 2022 5 - Production/Stable pytest (>=7.0.0) + :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Jun 07, 2021 5 - Production/Stable pytest + :pypi:`pytest-mongodb` pytest plugin for MongoDB fixtures May 16, 2023 5 - Production/Stable N/A + :pypi:`pytest-monitor` Pytest plugin for analyzing resource usage. Oct 22, 2022 5 - Production/Stable pytest + :pypi:`pytest-monkeyplus` pytest's monkeypatch subclass with extra functionalities Sep 18, 2012 5 - Production/Stable N/A + :pypi:`pytest-monkeytype` pytest-monkeytype: Generate Monkeytype annotations from your pytest tests. Jul 29, 2020 4 - Beta N/A + :pypi:`pytest-moto` Fixtures for integration tests of AWS services,uses moto mocking library. Aug 28, 2015 1 - Planning N/A + :pypi:`pytest-motor` A pytest plugin for motor, the non-blocking MongoDB driver. Jul 21, 2021 3 - Alpha pytest + :pypi:`pytest-mp` A test batcher for multiprocessed Pytest runs May 23, 2018 4 - Beta pytest + :pypi:`pytest-mpi` pytest plugin to collect information from tests Jan 08, 2022 3 - Alpha pytest + :pypi:`pytest-mpiexec` pytest plugin for running individual tests with mpiexec Apr 13, 2023 3 - Alpha pytest + :pypi:`pytest-mpl` pytest plugin to help with testing figures output from Matplotlib Jul 23, 2022 4 - Beta pytest + :pypi:`pytest-mproc` low-startup-overhead, scalable, distributed-testing pytest plugin Nov 15, 2022 4 - Beta pytest (>=6) + :pypi:`pytest-mqtt` pytest-mqtt supports testing systems based on MQTT Mar 15, 2023 4 - Beta pytest (<8) ; extra == 'test' + :pypi:`pytest-multihost` Utility for writing multi-host tests for pytest Apr 07, 2020 4 - Beta N/A + :pypi:`pytest-multilog` Multi-process logs handling and other helpers for pytest Jan 17, 2023 N/A pytest + :pypi:`pytest-multithreading` a pytest plugin for th and concurrent testing Dec 07, 2022 N/A N/A + :pypi:`pytest-multithreading-allure` pytest_multithreading_allure Nov 25, 2022 N/A N/A + :pypi:`pytest-mutagen` Add the mutation testing feature to pytest Jul 24, 2020 N/A pytest (>=5.4) + :pypi:`pytest-mypy` Mypy static type checker plugin for Pytest Dec 18, 2022 4 - Beta pytest (>=6.2) ; python_version >= "3.10" + :pypi:`pytest-mypyd` Mypy static type checker plugin for Pytest Aug 20, 2019 4 - Beta pytest (<4.7,>=2.8) ; python_version < "3.5" + :pypi:`pytest-mypy-plugins` pytest plugin for writing tests for mypy plugins May 05, 2023 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-mypy-plugins-shim` Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. Apr 12, 2021 N/A pytest>=6.0.0 + :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Feb 25, 2023 N/A pytest>=7,<8 + :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Mar 27, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-needle` pytest plugin for visual testing websites using selenium Dec 10, 2018 4 - Beta pytest (<5.0.0,>=3.0.0) + :pypi:`pytest-neo` pytest-neo is a plugin for pytest that shows tests like screen of Matrix. Jan 08, 2022 3 - Alpha pytest (>=6.2.0) + :pypi:`pytest-netdut` "Automated software testing for switches using pytest" Jun 05, 2023 N/A pytest (>=3.5.0) + :pypi:`pytest-network` A simple plugin to disable network on socket level. May 07, 2020 N/A N/A + :pypi:`pytest-network-endpoints` Network endpoints plugin for pytest Mar 06, 2022 N/A pytest + :pypi:`pytest-never-sleep` pytest plugin helps to avoid adding tests without mock \`time.sleep\` May 05, 2021 3 - Alpha pytest (>=3.5.1) + :pypi:`pytest-nginx` nginx fixture for pytest Aug 12, 2017 5 - Production/Stable N/A + :pypi:`pytest-nginx-iplweb` nginx fixture for pytest - iplweb temporary fork Mar 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-ngrok` Jan 20, 2022 3 - Alpha pytest + :pypi:`pytest-ngsfixtures` pytest ngs fixtures Sep 06, 2019 2 - Pre-Alpha pytest (>=5.0.0) + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Jun 07, 2023 N/A pytest (==6.2.5) + :pypi:`pytest-nice` A pytest plugin that alerts user of failed test cases with screen notifications May 04, 2019 4 - Beta pytest + :pypi:`pytest-nice-parametrize` A small snippet for nicer PyTest's Parametrize Apr 17, 2021 5 - Production/Stable N/A + :pypi:`pytest-nlcov` Pytest plugin to get the coverage of the new lines (based on git diff) only Jul 07, 2021 N/A N/A + :pypi:`pytest-nocustom` Run all tests without custom markers Jul 07, 2021 5 - Production/Stable N/A + :pypi:`pytest-nodev` Test-driven source code search for Python. Jul 21, 2016 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-nogarbage` Ensure a test produces no garbage Aug 29, 2021 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-notice` Send pytest execution result email Nov 05, 2020 N/A N/A + :pypi:`pytest-notification` A pytest plugin for sending a desktop notification and playing a sound upon completion of tests Jun 19, 2020 N/A pytest (>=4) + :pypi:`pytest-notifier` A pytest plugin to notify test result Jun 12, 2020 3 - Alpha pytest + :pypi:`pytest-notimplemented` Pytest markers for not implemented features and tests. Aug 27, 2019 N/A pytest (>=5.1,<6.0) + :pypi:`pytest-notion` A PyTest Reporter to send test runs to Notion.so Aug 07, 2019 N/A N/A + :pypi:`pytest-nunit` A pytest plugin for generating NUnit3 test result XML output Oct 20, 2022 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-oar` PyTest plugin for the OAR testing framework May 02, 2023 N/A pytest>=6.0.1 + :pypi:`pytest-object-getter` Import any object from a 3rd party module while mocking its namespace on demand. Jul 31, 2022 5 - Production/Stable pytest + :pypi:`pytest-ochrus` pytest results data-base and HTML reporter Feb 21, 2018 4 - Beta N/A + :pypi:`pytest-odoo` py.test plugin to run Odoo tests Nov 17, 2022 4 - Beta pytest (>=7.2.0) + :pypi:`pytest-odoo-fixtures` Project description Jun 25, 2019 N/A N/A + :pypi:`pytest-oerp` pytest plugin to test OpenERP modules Feb 28, 2012 3 - Alpha N/A + :pypi:`pytest-offline` Mar 09, 2023 1 - Planning pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-ogsm-plugin` 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 May 16, 2023 N/A N/A + :pypi:`pytest-ok` The ultimate pytest output plugin Apr 01, 2019 4 - Beta N/A + :pypi:`pytest-only` Use @pytest.mark.only to run a single test Jun 14, 2022 5 - Production/Stable pytest (<7.1); python_version <= "3.6" + :pypi:`pytest-oot` Run object-oriented tests in a simple format Sep 18, 2016 4 - Beta N/A + :pypi:`pytest-openfiles` Pytest plugin for detecting inadvertent open file handles Apr 16, 2020 3 - Alpha pytest (>=4.6) + :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Mar 15, 2023 N/A pytest + :pypi:`pytest-opentmi` pytest plugin for publish results to opentmi Jun 02, 2022 5 - Production/Stable pytest (>=5.0) + :pypi:`pytest-operator` Fixtures for Operators Sep 28, 2022 N/A pytest + :pypi:`pytest-optional` include/exclude values of fixtures in pytest Oct 07, 2015 N/A N/A + :pypi:`pytest-optional-tests` Easy declaration of optional tests (i.e., that are not run by default) Jul 09, 2019 4 - Beta pytest (>=4.5.0) + :pypi:`pytest-orchestration` A pytest plugin for orchestrating tests Jul 18, 2019 N/A N/A + :pypi:`pytest-order` pytest plugin to run your tests in a specific order Mar 10, 2023 4 - Beta pytest (>=5.0) ; python_version < "3.10" + :pypi:`pytest-ordering` pytest plugin to run your tests in a specific order Nov 14, 2018 4 - Beta pytest + :pypi:`pytest-order-modify` 新增run_marker 来自定义用例的执行顺序 Nov 04, 2022 N/A N/A + :pypi:`pytest-osxnotify` OS X notifications for py.test results. May 15, 2015 N/A N/A + :pypi:`pytest-otel` pytest-otel report OpenTelemetry traces about test executed Jan 18, 2023 N/A N/A + :pypi:`pytest-override-env-var` Pytest mark to override a value of an environment variable. Feb 25, 2023 N/A N/A + :pypi:`pytest-owner` Add owner mark for tests Apr 25, 2022 N/A N/A + :pypi:`pytest-pact` A simple plugin to use with pytest Jan 07, 2019 4 - Beta N/A + :pypi:`pytest-pahrametahrize` Parametrize your tests with a Boston accent. Nov 24, 2021 4 - Beta pytest (>=6.0,<7.0) + :pypi:`pytest-parallel` a pytest plugin for parallel and concurrent testing Oct 10, 2021 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-parallel-39` a pytest plugin for parallel and concurrent testing Jul 12, 2021 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-parallelize-tests` pytest plugin that parallelizes test execution across multiple hosts Jan 27, 2023 4 - Beta N/A + :pypi:`pytest-param` pytest plugin to test all, first, last or random params Sep 11, 2016 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-paramark` Configure pytest fixtures using a combination of"parametrize" and markers Jan 10, 2020 4 - Beta pytest (>=4.5.0) + :pypi:`pytest-parametrization` Simpler PyTest parametrization May 22, 2022 5 - Production/Stable N/A + :pypi:`pytest-parametrize-cases` A more user-friendly way to write parametrized tests. Mar 13, 2022 N/A pytest (>=6.1.2) + :pypi:`pytest-parametrized` Pytest decorator for parametrizing tests with default iterables. Sep 13, 2022 5 - Production/Stable pytest + :pypi:`pytest-parametrize-suite` A simple pytest extension for creating a named test suite. Jan 19, 2023 5 - Production/Stable pytest + :pypi:`pytest-parawtf` Finally spell paramete?ri[sz]e correctly Dec 03, 2018 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-pass` Check out https://github.com/elilutsky/pytest-pass Dec 04, 2019 N/A N/A + :pypi:`pytest-passrunner` Pytest plugin providing the 'run_on_pass' marker Feb 10, 2021 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-paste-config` Allow setting the path to a paste config file Sep 18, 2013 3 - Alpha N/A + :pypi:`pytest-patch` An automagic \`patch\` fixture that can patch objects directly or by name. Apr 29, 2023 3 - Alpha pytest (>=7.0.0) + :pypi:`pytest-patches` A contextmanager pytest fixture for handling multiple mock patches Aug 30, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-pdb` pytest plugin which adds pdb helper commands related to pytest. Jul 31, 2018 N/A N/A + :pypi:`pytest-peach` pytest plugin for fuzzing with Peach API Security Apr 12, 2019 4 - Beta pytest (>=2.8.7) + :pypi:`pytest-pep257` py.test plugin for pep257 Jul 09, 2016 N/A N/A + :pypi:`pytest-pep8` pytest plugin to check PEP8 requirements Apr 27, 2014 N/A N/A + :pypi:`pytest-percent` Change the exit code of pytest test sessions when a required percent of tests pass. May 21, 2020 N/A pytest (>=5.2.0) + :pypi:`pytest-perf` Run performance tests against the mainline code. Jun 02, 2023 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-performance` A simple plugin to ensure the execution of critical sections of code has not been impacted Sep 11, 2020 5 - Production/Stable pytest (>=3.7.0) + :pypi:`pytest-persistence` Pytest tool for persistent objects Jun 14, 2023 N/A N/A + :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker May 04, 2023 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-pgsql` Pytest plugins and helpers for tests using a Postgres database. May 13, 2020 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-phmdoctest` pytest plugin to test Python examples in Markdown using phmdoctest. Apr 15, 2022 4 - Beta pytest (>=5.4.3) + :pypi:`pytest-picked` Run the tests related to the changed files Dec 23, 2020 N/A pytest (>=3.5.0) + :pypi:`pytest-pigeonhole` Jun 25, 2018 5 - Production/Stable pytest (>=3.4) + :pypi:`pytest-pikachu` Show surprise when tests are passing Aug 05, 2021 5 - Production/Stable pytest + :pypi:`pytest-pilot` Slice in your test base thanks to powerful markers. Oct 09, 2020 5 - Production/Stable N/A + :pypi:`pytest-pingguo-pytest-plugin` pingguo test Oct 26, 2022 4 - Beta N/A + :pypi:`pytest-pings` 🦊 The pytest plugin for Firefox Telemetry 📊 Jun 29, 2019 3 - Alpha pytest (>=5.0.0) + :pypi:`pytest-pinned` A simple pytest plugin for pinning tests Sep 17, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-pinpoint` A pytest plugin which runs SBFL algorithms to detect faults. Sep 25, 2020 N/A pytest (>=4.4.0) + :pypi:`pytest-pipeline` Pytest plugin for functional testing of data analysispipelines Jan 24, 2017 3 - Alpha N/A + :pypi:`pytest-platform-markers` Markers for pytest to skip tests on specific platforms Sep 09, 2019 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-play` pytest plugin that let you automate actions and assertions with test metrics reporting executing plain YAML files Jun 12, 2019 5 - Production/Stable N/A + :pypi:`pytest-playbook` Pytest plugin for reading playbooks. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Apr 24, 2023 N/A pytest (<8.0.0,>=6.2.4) + :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A + :pypi:`pytest-playwright-snapshot` A pytest wrapper for snapshot testing with playwright Aug 19, 2021 N/A N/A + :pypi:`pytest-playwright-visual` A pytest fixture for visual testing with Playwright Apr 28, 2022 N/A N/A + :pypi:`pytest-plone` Pytest plugin to test Plone addons Jan 05, 2023 3 - Alpha pytest + :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Aug 17, 2020 5 - Production/Stable pytest + :pypi:`pytest-plugin-helpers` A plugin to help developing and testing other plugins Nov 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-plus` PyTest Plus Plugin :: extends pytest functionality Dec 24, 2022 5 - Production/Stable pytest (>=6.0.1) + :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A + :pypi:`pytest-pointers` Pytest plugin to define functions you test with special marks for better navigation and reports Dec 26, 2022 N/A N/A + :pypi:`pytest-pokie` Pokie plugin for pytest May 22, 2023 5 - Production/Stable N/A + :pypi:`pytest-polarion-cfme` pytest plugin for collecting test cases and recording test results Nov 13, 2017 3 - Alpha N/A + :pypi:`pytest-polarion-collect` pytest plugin for collecting polarion test cases data Jun 18, 2020 3 - Alpha pytest + :pypi:`pytest-polecat` Provides Polecat pytest fixtures Aug 12, 2019 4 - Beta N/A + :pypi:`pytest-ponyorm` PonyORM in Pytest Oct 31, 2018 N/A pytest (>=3.1.1) + :pypi:`pytest-poo` Visualize your crappy tests Mar 25, 2021 5 - Production/Stable pytest (>=2.3.4) + :pypi:`pytest-poo-fail` Visualize your failed tests with poo Feb 12, 2015 5 - Production/Stable N/A + :pypi:`pytest-pop` A pytest plugin to help with testing pop projects May 09, 2023 5 - Production/Stable pytest + :pypi:`pytest-portion` Select a portion of the collected tests Jan 28, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-postgres` Run PostgreSQL in Docker container in Pytest. Mar 22, 2020 N/A pytest + :pypi:`pytest-postgresql` Postgresql fixtures and fixture factories for Pytest. May 20, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-pot` A package for enhancing pytest Nov 20, 2022 N/A N/A + :pypi:`pytest-power` pytest plugin with powerful fixtures Dec 31, 2020 N/A pytest (>=5.4) + :pypi:`pytest-prefer-nested-dup-tests` A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. Apr 27, 2022 4 - Beta pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-pretty` pytest plugin for printing summary data as I want it Apr 05, 2023 5 - Production/Stable pytest>=7 + :pypi:`pytest-pretty-terminal` pytest plugin for generating prettier terminal output Jan 31, 2022 N/A pytest (>=3.4.1) + :pypi:`pytest-pride` Minitest-style test colors Apr 02, 2016 3 - Alpha N/A + :pypi:`pytest-print` pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) Jun 16, 2023 5 - Production/Stable pytest>=7.3.2 + :pypi:`pytest-profiles` pytest plugin for configuration profiles Dec 09, 2021 4 - Beta pytest (>=3.7.0) + :pypi:`pytest-profiling` Profiling plugin for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-progress` pytest plugin for instant test progress status Jan 31, 2022 5 - Production/Stable N/A + :pypi:`pytest-prometheus` Report test pass / failures to a Prometheus PushGateway Oct 03, 2017 N/A N/A + :pypi:`pytest-prometheus-pushgateway` Pytest report plugin for Zulip Sep 27, 2022 5 - Production/Stable pytest + :pypi:`pytest-prosper` Test helpers for Prosper projects Sep 24, 2018 N/A N/A + :pypi:`pytest-pspec` A rspec format reporter for Python ptest Jun 02, 2020 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) + :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-pudb` Pytest PuDB debugger integration Oct 25, 2018 3 - Alpha pytest (>=2.0) + :pypi:`pytest-pumpkin-spice` A pytest plugin that makes your test reporting pumpkin-spiced Sep 18, 2022 4 - Beta N/A + :pypi:`pytest-purkinje` py.test plugin for purkinje test runner Oct 28, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-pusher` pytest plugin for push report to minio Jan 06, 2023 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-py125` Dec 03, 2022 N/A N/A + :pypi:`pytest-pycharm` Plugin for py.test to enter PyCharm debugger on uncaught exceptions Aug 13, 2020 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-pycodestyle` pytest plugin to run pycodestyle Oct 28, 2022 3 - Alpha N/A + :pypi:`pytest-pydev` py.test plugin to connect to a remote debug server with PyDev or PyCharm. Nov 15, 2017 3 - Alpha N/A + :pypi:`pytest-pydocstyle` pytest plugin to run pydocstyle Jan 05, 2023 3 - Alpha N/A + :pypi:`pytest-pylint` pytest plugin to check source code with pylint Sep 10, 2022 5 - Production/Stable pytest (>=5.4) + :pypi:`pytest-pymysql-autorecord` Record PyMySQL queries and mock with the stored data. Sep 02, 2022 N/A N/A + :pypi:`pytest-pyodide` "Pytest plugin for testing applications that use Pyodide" Jun 11, 2023 N/A pytest + :pypi:`pytest-pypi` Easily test your HTTP library against a local copy of pypi Mar 04, 2018 3 - Alpha N/A + :pypi:`pytest-pypom-navigation` Core engine for cookiecutter-qa and pytest-play packages Feb 18, 2019 4 - Beta pytest (>=3.0.7) + :pypi:`pytest-pyppeteer` A plugin to run pyppeteer in pytest Apr 28, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-pyq` Pytest fixture "q" for pyq Mar 10, 2020 5 - Production/Stable N/A + :pypi:`pytest-pyramid` pytest_pyramid - provides fixtures for testing pyramid applications with pytest test suite Dec 13, 2022 5 - Production/Stable pytest + :pypi:`pytest-pyramid-server` Pyramid server fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-pyreport` PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report May 08, 2023 N/A pytest (>=7.3.1) + :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Nov 20, 2022 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-pyspec` A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". Mar 12, 2023 5 - Production/Stable pytest (>=7.2.1,<8.0.0) + :pypi:`pytest-pystack` Plugin to run pystack after a timeout for a test suite. May 07, 2023 N/A pytest (>=3.5.0) + :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) + :pypi:`pytest-pythonpath` pytest plugin for adding to the PYTHONPATH from command line or configs. Feb 10, 2022 5 - Production/Stable pytest (<7,>=2.5.2) + :pypi:`pytest-pytorch` pytest plugin for a better developer experience when working with the PyTorch test suite May 25, 2021 4 - Beta pytest + :pypi:`pytest-pyvista` Pytest-pyvista package Mar 19, 2023 4 - Beta pytest>=3.5.0 + :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 11, 2023 4 - Beta pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-qasync` Pytest support for qasync. Jul 12, 2021 4 - Beta pytest (>=5.4.0) + :pypi:`pytest-qatouch` Pytest plugin for uploading test results to your QA Touch Testrun. Feb 14, 2023 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Jun 09, 2023 5 - Production/Stable pytest (>=6.2.5) + :pypi:`pytest-qml` Run QML Tests with pytest Dec 02, 2020 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-qr` pytest plugin to generate test result QR codes Nov 25, 2021 4 - Beta N/A + :pypi:`pytest-qt` pytest support for PyQt and PySide applications Oct 25, 2022 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-qt-app` QT app fixture for py.test Dec 23, 2015 5 - Production/Stable N/A + :pypi:`pytest-quarantine` A plugin for pytest to manage expected test failures Nov 24, 2019 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-quickcheck` pytest plugin to generate random data inspired by QuickCheck Nov 05, 2022 4 - Beta pytest (>=4.0) + :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Jun 16, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-race` Race conditions tester for pytest Jun 07, 2022 4 - Beta N/A + :pypi:`pytest-rage` pytest plugin to implement PEP712 Oct 21, 2011 3 - Alpha N/A + :pypi:`pytest-rail` pytest plugin for creating TestRail runs and adding results May 02, 2022 N/A pytest (>=3.6) + :pypi:`pytest-railflow-testrail-reporter` Generate json reports along with specified metadata defined in test markers. Jun 29, 2022 5 - Production/Stable pytest + :pypi:`pytest-raises` An implementation of pytest.raises as a pytest.mark fixture Apr 23, 2020 N/A pytest (>=3.2.2) + :pypi:`pytest-raisesregexp` Simple pytest plugin to look for regex in Exceptions Dec 18, 2015 N/A N/A + :pypi:`pytest-raisin` Plugin enabling the use of exception instances with pytest.raises Feb 06, 2022 N/A pytest + :pypi:`pytest-random` py.test plugin to randomize tests Apr 28, 2013 3 - Alpha N/A + :pypi:`pytest-randomly` Pytest plugin to randomly order tests and control random.seed. May 11, 2022 5 - Production/Stable pytest + :pypi:`pytest-randomness` Pytest plugin about random seed management May 30, 2019 3 - Alpha N/A + :pypi:`pytest-random-num` Randomise the order in which pytest tests are run with some control over the randomness Oct 19, 2020 5 - Production/Stable N/A + :pypi:`pytest-random-order` Randomise the order in which pytest tests are run with some control over the randomness Dec 03, 2022 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A + :pypi:`pytest-reana` Pytest fixtures for REANA. Dec 13, 2022 3 - Alpha N/A + :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Mar 30, 2023 N/A N/A + :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Feb 16, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-recordings` Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal Aug 13, 2020 N/A N/A + :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Apr 19, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest + :pypi:`pytest-redmine` Pytest plugin for redmine Mar 19, 2018 1 - Planning N/A + :pypi:`pytest-ref` A plugin to store reference files to ease regression testing Nov 23, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reference-formatter` Conveniently run pytest with a dot-formatted test reference. Oct 01, 2019 4 - Beta N/A + :pypi:`pytest-regex` Select pytest tests with regular expressions May 29, 2023 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-regex-dependency` Management of Pytest dependencies via regex patterns Jun 12, 2022 N/A pytest + :pypi:`pytest-regressions` Easy to use fixtures to write regression tests. Jan 13, 2023 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-regtest` pytest plugin for regression tests Jul 08, 2022 N/A N/A + :pypi:`pytest-relative-order` a pytest plugin that sorts tests using "before" and "after" markers May 17, 2021 4 - Beta N/A + :pypi:`pytest-relaxed` Relaxed test discovery/organization for pytest May 23, 2023 5 - Production/Stable pytest (>=7) + :pypi:`pytest-remfiles` Pytest plugin to create a temporary directory with remote files Jul 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-remotedata` Pytest plugin for controlling remote data access. Dec 12, 2022 3 - Alpha pytest (>=4.6) + :pypi:`pytest-remote-response` Pytest plugin for capturing and mocking connection requests. Apr 26, 2023 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-remove-stale-bytecode` py.test plugin to remove stale byte code files. Mar 04, 2020 4 - Beta pytest + :pypi:`pytest-reorder` Reorder tests depending on their paths and names. May 31, 2018 4 - Beta pytest + :pypi:`pytest-repeat` pytest plugin for repeating tests Oct 31, 2020 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-replay` Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests Jun 09, 2021 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-repo-health` A pytest plugin to report on repository standards conformance Apr 17, 2023 3 - Alpha pytest + :pypi:`pytest-report` Creates json report that is compatible with atom.io's linter message format May 11, 2016 4 - Beta N/A + :pypi:`pytest-reporter` Generate Pytest reports with templates Jul 22, 2021 4 - Beta pytest + :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Jun 05, 2023 4 - Beta N/A + :pypi:`pytest-reporter-html-dots` A basic HTML report for pytest using Jinja2 template engine. Jan 22, 2023 N/A N/A + :pypi:`pytest-reportinfra` Pytest plugin for reportinfra Aug 11, 2019 3 - Alpha N/A + :pypi:`pytest-reporting` A plugin to report summarized results in a table format Oct 25, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reportlog` Replacement for the --resultlog option, focused in simplicity and extensibility May 22, 2023 3 - Alpha pytest + :pypi:`pytest-report-me` A pytest plugin to generate report. Dec 31, 2020 N/A pytest + :pypi:`pytest-report-parameters` pytest plugin for adding tests' parameters to junit report Jun 18, 2020 3 - Alpha pytest (>=2.4.2) + :pypi:`pytest-reportportal` Agent for Reporting results of tests to the Report Portal Jun 08, 2023 N/A pytest (>=3.8.0) + :pypi:`pytest-reports` An interesting python package Jun 07, 2023 N/A N/A + :pypi:`pytest-reqs` pytest plugin to check pinned requirements May 12, 2019 N/A pytest (>=2.4.2) + :pypi:`pytest-requests` A simple plugin to use with pytest Jun 24, 2019 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-requestselapsed` collect and show http requests elapsed time Aug 14, 2022 N/A N/A + :pypi:`pytest-requests-futures` Pytest Plugin to Mock Requests Futures Jul 06, 2022 5 - Production/Stable pytest + :pypi:`pytest-requires` A pytest plugin to elegantly skip tests with optional requirements Dec 21, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-reraise` Make multi-threaded pytest test cases fail when they should Sep 20, 2022 5 - Production/Stable pytest (>=4.6) + :pypi:`pytest-rerun` Re-run only changed files in specified branch Jul 08, 2019 N/A pytest (>=3.6) + :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Mar 09, 2023 5 - Production/Stable pytest (>=5.3) + :pypi:`pytest-rerunfailures-all-logs` pytest plugin to re-run tests to eliminate flaky failures Mar 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-reserial` Pytest fixture for recording and replaying serial port traffic. Apr 26, 2023 4 - Beta pytest + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Jun 01, 2023 N/A pytest (~=4.6) ; python_version == "2.7" + :pypi:`pytest-resource` Load resource fixture plugin to use with pytest Nov 14, 2018 4 - Beta N/A + :pypi:`pytest-resource-path` Provides path for uniform access to test resources in isolated directory May 01, 2021 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-resource-usage` Pytest plugin for reporting running time and peak memory usage Nov 06, 2022 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-responsemock` Simplified requests calls mocking for pytest Mar 10, 2022 5 - Production/Stable N/A + :pypi:`pytest-responses` py.test integration for responses Oct 11, 2022 N/A pytest (>=2.5) + :pypi:`pytest-rest-api` Aug 08, 2022 N/A pytest (>=7.1.2,<8.0.0) + :pypi:`pytest-restrict` Pytest plugin to restrict the test types allowed Jun 16, 2023 5 - Production/Stable pytest + :pypi:`pytest-result-log` Write the execution result of the case to the log Apr 17, 2023 N/A pytest>=7.2.0 + :pypi:`pytest-result-sender` Apr 20, 2023 N/A pytest>=7.3.1 + :pypi:`pytest-resume` A Pytest plugin to resuming from the last run test Apr 22, 2023 4 - Beta pytest (>=7.0) + :pypi:`pytest-rethinkdb` A RethinkDB plugin for pytest. Jul 24, 2016 4 - Beta N/A + :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments Aug 16, 2022 N/A pytest (>=7.0.0) + :pypi:`pytest-retry-class` A pytest plugin to rerun entire class on failure Mar 25, 2023 N/A pytest (>=5.3) + :pypi:`pytest-reusable-testcases` Apr 28, 2023 N/A N/A + :pypi:`pytest-reverse` Pytest plugin to reverse test order. Jun 16, 2023 5 - Production/Stable pytest + :pypi:`pytest-rich` Leverage rich for richer test session output Mar 03, 2022 4 - Beta pytest (>=7.0) + :pypi:`pytest-rich-reporter` A pytest plugin using Rich for beautiful test result formatting. Feb 17, 2022 1 - Planning pytest (>=5.0.0) + :pypi:`pytest-richtrace` Nov 05, 2022 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-ringo` pytest plugin to test webapplications using the Ringo webframework Sep 27, 2017 3 - Alpha N/A + :pypi:`pytest-rmsis` Sycronise pytest results to Jira RMsis Aug 10, 2022 N/A pytest (>=5.3.5) + :pypi:`pytest-rng` Fixtures for seeding tests and making randomness reproducible Aug 08, 2019 5 - Production/Stable pytest + :pypi:`pytest-roast` pytest plugin for ROAST configuration override and fixtures Nov 09, 2022 5 - Production/Stable pytest + :pypi:`pytest-rocketchat` Pytest to Rocket.Chat reporting plugin Apr 18, 2021 5 - Production/Stable N/A + :pypi:`pytest-rotest` Pytest integration with rotest Sep 08, 2019 N/A pytest (>=3.5.0) + :pypi:`pytest-rpc` Extend py.test for RPC OpenStack testing. Feb 22, 2019 4 - Beta pytest (~=3.6) + :pypi:`pytest-rst` Test code from RST documents with pytest Jan 26, 2023 N/A N/A + :pypi:`pytest-rt` pytest data collector plugin for Testgr May 05, 2022 N/A N/A + :pypi:`pytest-rts` Coverage-based regression test selection (RTS) plugin for pytest May 17, 2021 N/A pytest + :pypi:`pytest-ruff` pytest plugin to check ruff requirements. Jun 08, 2023 4 - Beta N/A + :pypi:`pytest-run-changed` Pytest plugin that runs changed tests only Apr 02, 2021 3 - Alpha pytest + :pypi:`pytest-runfailed` implement a --failed option for pytest Mar 24, 2016 N/A N/A + :pypi:`pytest-runner` Invoke py.test as distutils command with dependency resolution Feb 25, 2022 5 - Production/Stable pytest (>=6) ; extra == 'testing' + :pypi:`pytest-run-subprocess` Pytest Plugin for running and testing subprocesses. Nov 12, 2022 5 - Production/Stable pytest + :pypi:`pytest-runtime-types` Checks type annotations on runtime while running tests. Feb 09, 2023 N/A pytest + :pypi:`pytest-runtime-xfail` Call runtime_xfail() to mark running test as xfail. Aug 26, 2021 N/A pytest>=5.0.0 + :pypi:`pytest-runtime-yoyo` run case mark timeout Jun 12, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-ry-demo1` 测试 Mar 26, 2023 N/A N/A + :pypi:`pytest-saccharin` pytest-saccharin is a updated fork of pytest-sugar, a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Oct 31, 2022 3 - Alpha N/A + :pypi:`pytest-salt` Pytest Salt Plugin Jan 27, 2020 4 - Beta N/A + :pypi:`pytest-salt-containers` A Pytest plugin that builds and creates docker containers Nov 09, 2016 4 - Beta N/A + :pypi:`pytest-salt-factories` Pytest Salt Plugin Dec 15, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-salt-from-filenames` Simple PyTest Plugin For Salt's Test Suite Specifically Jan 29, 2019 4 - Beta pytest (>=4.1) + :pypi:`pytest-salt-runtests-bridge` Simple PyTest Plugin For Salt's Test Suite Specifically Dec 05, 2019 4 - Beta pytest (>=4.1) + :pypi:`pytest-sanic` a pytest plugin for Sanic Oct 25, 2021 N/A pytest (>=5.2) + :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A + :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jun 13, 2023 5 - Production/Stable N/A + :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A + :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A + :pypi:`pytest-schema` 👍 Validate return values against a schema-like object in testing Mar 14, 2022 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-securestore` An encrypted password store for use within pytest cases Nov 08, 2021 4 - Beta N/A + :pypi:`pytest-select` A pytest plugin which allows to (de-)select tests from a file. Jan 18, 2019 3 - Alpha pytest (>=3.0) + :pypi:`pytest-selenium` pytest plugin for Selenium May 28, 2023 5 - Production/Stable pytest>=6.0.0 + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jun 13, 2023 5 - Production/Stable N/A + :pypi:`pytest-selenium-enhancer` pytest plugin for Selenium Apr 29, 2022 5 - Production/Stable N/A + :pypi:`pytest-selenium-pdiff` A pytest package implementing perceptualdiff for Selenium tests. Apr 06, 2017 2 - Pre-Alpha N/A + :pypi:`pytest-send-email` Send pytest execution result email Dec 04, 2019 N/A N/A + :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Jan 05, 2023 N/A N/A + :pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-server-fixtures` Extensible server fixures for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A + :pypi:`pytest-servers` pytest servers Apr 15, 2023 3 - Alpha pytest (>=6.2) + :pypi:`pytest-services` Services plugin for pytest testing framework Oct 30, 2020 6 - Mature N/A + :pypi:`pytest-session2file` pytest-session2file (aka: pytest-session_to_file for v0.1.0 - v0.1.2) is a py.test plugin for capturing and saving to file the stdout of py.test. Jan 26, 2021 3 - Alpha pytest + :pypi:`pytest-session-fixture-globalize` py.test plugin to make session fixtures behave as if written in conftest, even if it is written in some modules May 15, 2018 4 - Beta N/A + :pypi:`pytest-session_to_file` pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. Oct 01, 2015 3 - Alpha N/A + :pypi:`pytest-setupinfo` Displaying setup info during pytest command run Jan 23, 2023 N/A N/A + :pypi:`pytest-sftpserver` py.test plugin to locally test sftp server connections. Sep 16, 2019 4 - Beta N/A + :pypi:`pytest-shard` Dec 11, 2020 4 - Beta pytest + :pypi:`pytest-share-hdf` Plugin to save test data in HDF files and retrieve them for comparison Sep 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-sharkreport` this is pytest report plugin. Jul 11, 2022 N/A pytest (>=3.5) + :pypi:`pytest-shell` A pytest plugin to help with testing shell scripts / black box commands Mar 27, 2022 N/A N/A + :pypi:`pytest-shell-utilities` Pytest plugin to simplify running shell commands against the system Sep 23, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-sheraf` Versatile ZODB abstraction layer - pytest fixtures Feb 11, 2020 N/A pytest + :pypi:`pytest-sherlock` pytest plugin help to find coupled tests Jan 16, 2023 5 - Production/Stable pytest (>=3.5.1) + :pypi:`pytest-shortcuts` Expand command-line shortcuts listed in pytest configuration Oct 29, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-shutil` A goodie-bag of unix shell and environment tools for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-simplehttpserver` Simple pytest fixture to spin up an HTTP server Jun 24, 2021 4 - Beta N/A + :pypi:`pytest-simple-plugin` Simple pytest plugin Nov 27, 2019 N/A N/A + :pypi:`pytest-simple-settings` simple-settings plugin for pytest Nov 17, 2020 4 - Beta pytest + :pypi:`pytest-single-file-logging` Allow for multiple processes to log to a single file May 05, 2016 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-skip-markers` Pytest Salt Plugin Dec 20, 2022 5 - Production/Stable pytest (>=7.1.0) + :pypi:`pytest-skipper` A plugin that selects only tests with changes in execution path Mar 26, 2017 3 - Alpha pytest (>=3.0.6) + :pypi:`pytest-skippy` Automatically skip tests that don't need to run! Jan 27, 2018 3 - Alpha pytest (>=2.3.4) + :pypi:`pytest-skip-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Feb 09, 2023 N/A pytest>=6.2.0 + :pypi:`pytest-slack` Pytest to Slack reporting plugin Dec 15, 2020 5 - Production/Stable N/A + :pypi:`pytest-slow` A pytest plugin to skip \`@pytest.mark.slow\` tests by default. Sep 28, 2021 N/A N/A + :pypi:`pytest-slowest-first` Sort tests by their last duration, slowest first Dec 11, 2022 4 - Beta N/A + :pypi:`pytest-slow-last` Run tests in order of execution time (faster tests first) Dec 10, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-smartcollect` A plugin for collecting tests that touch changed code Oct 04, 2018 N/A pytest (>=3.5.0) + :pypi:`pytest-smartcov` Smart coverage plugin for pytest. Sep 30, 2017 3 - Alpha N/A + :pypi:`pytest-smell` Automated bad smell detection tool for Pytest Jun 26, 2022 N/A N/A + :pypi:`pytest-smtp` Send email with pytest execution result Feb 20, 2021 N/A pytest + :pypi:`pytest-smtpd` An SMTP server for testing built on aiosmtpd May 15, 2023 N/A pytest + :pypi:`pytest-snail` Plugin for adding a marker to slow running tests. 🐌 Nov 04, 2019 3 - Alpha pytest (>=5.0.1) + :pypi:`pytest-snapci` py.test plugin for Snap-CI Nov 12, 2015 N/A N/A + :pypi:`pytest-snapshot` A plugin for snapshot testing with pytest. Apr 23, 2022 4 - Beta pytest (>=3.0.0) + :pypi:`pytest-snmpserver` May 12, 2021 N/A N/A + :pypi:`pytest-snowflake-bdd` Setup test data and run tests on snowflake in BDD style! Jan 05, 2022 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-socket` Pytest Plugin to disable socket calls during tests Feb 03, 2023 4 - Beta pytest (>=3.6.3) + :pypi:`pytest-sofaepione` Test the installation of SOFA and the SofaEpione plugin. Aug 17, 2022 N/A N/A + :pypi:`pytest-soft-assertions` May 05, 2020 3 - Alpha pytest + :pypi:`pytest-solidity` A PyTest library plugin for Solidity language. Jan 15, 2022 1 - Planning pytest (<7,>=6.0.1) ; extra == 'tests' + :pypi:`pytest-solr` Solr process and client fixtures for py.test. May 11, 2020 3 - Alpha pytest (>=3.0.0) + :pypi:`pytest-sorter` A simple plugin to first execute tests that historically failed more Apr 20, 2021 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-sosu` Unofficial PyTest plugin for Sauce Labs Feb 14, 2023 2 - Pre-Alpha pytest + :pypi:`pytest-sourceorder` Test-ordering plugin for pytest Sep 01, 2021 4 - Beta pytest + :pypi:`pytest-spark` pytest plugin to run the tests with support of pyspark. Feb 23, 2020 4 - Beta pytest + :pypi:`pytest-spawner` py.test plugin to spawn process and communicate with them. Jul 31, 2015 4 - Beta N/A + :pypi:`pytest-spec` Library pytest-spec is a pytest plugin to display test execution output like a SPECIFICATION. May 04, 2021 N/A N/A + :pypi:`pytest-spec2md` Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. Jun 26, 2022 N/A pytest (>7.0) + :pypi:`pytest-speed` Modern benchmarking library for python with pytest integration. Jan 22, 2023 3 - Alpha pytest>=7 + :pypi:`pytest-sphinx` Doctest plugin for pytest with support for Sphinx-specific doctest-directives Sep 06, 2022 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-spiratest` Exports unit tests as test runs in SpiraTest/Team/Plan Feb 08, 2022 N/A N/A + :pypi:`pytest-splinter` Splinter plugin for pytest testing framework Sep 09, 2022 6 - Mature pytest (>=3.0.0) + :pypi:`pytest-splinter4` Pytest plugin for the splinter automation library Jun 11, 2022 6 - Mature pytest (<8.0,>=7.1.2) + :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Apr 12, 2023 4 - Beta pytest (>=5,<8) + :pypi:`pytest-splitio` Split.io SDK integration for e2e tests Sep 22, 2020 N/A pytest (<7,>=5.0) + :pypi:`pytest-split-tests` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Forked from Mark Adams' original project pytest-test-groups. Jul 30, 2021 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-split-tests-tresorit` Feb 22, 2021 1 - Planning N/A + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Feb 22, 2023 N/A pytest (>5.4.0,<7.3) + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Mar 07, 2023 N/A N/A + :pypi:`pytest-splunk-env` pytest fixtures for interaction with Splunk Enterprise and Splunk Cloud Oct 22, 2020 N/A pytest (>=6.1.1,<7.0.0) + :pypi:`pytest-sqitch` sqitch for pytest Apr 06, 2020 4 - Beta N/A + :pypi:`pytest-sqlalchemy` pytest plugin with sqlalchemy related fixtures Mar 13, 2018 3 - Alpha N/A + :pypi:`pytest-sqlalchemy-mock` pytest sqlalchemy plugin for mock Mar 15, 2023 3 - Alpha pytest (>=2.0) + :pypi:`pytest-sqlalchemy-session` A pytest plugin for preserving test isolation that use SQLAlchemy. May 19, 2023 4 - Beta pytest (>=7.0) + :pypi:`pytest-sql-bigquery` Yet another SQL-testing framework for BigQuery provided by pytest plugin Dec 19, 2019 N/A pytest + :pypi:`pytest-sqlfluff` A pytest plugin to use sqlfluff to enable format checking of sql files. Dec 21, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-squadcast` Pytest report plugin for Squadcast Feb 22, 2022 5 - Production/Stable pytest + :pypi:`pytest-srcpaths` Add paths to sys.path Oct 15, 2021 N/A pytest>=6.2.0 + :pypi:`pytest-ssh` pytest plugin for ssh command run May 27, 2019 N/A pytest + :pypi:`pytest-start-from` Start pytest run from a given point Apr 11, 2016 N/A N/A + :pypi:`pytest-star-track-issue` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :pypi:`pytest-static` pytest-static May 07, 2023 1 - Planning N/A + :pypi:`pytest-statsd` pytest plugin for reporting to graphite Nov 30, 2018 5 - Production/Stable pytest (>=3.0.0) + :pypi:`pytest-stepfunctions` A small description May 08, 2021 4 - Beta pytest + :pypi:`pytest-steps` Create step-wise / incremental tests in pytest. Sep 23, 2021 5 - Production/Stable N/A + :pypi:`pytest-stepwise` Run a test suite one failing test at a time. Dec 01, 2015 4 - Beta N/A + :pypi:`pytest-stf` pytest plugin for openSTF Dec 04, 2022 N/A pytest (>=5.0) + :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A + :pypi:`pytest-stress` A Pytest plugin that allows you to loop tests for a user defined amount of time. Dec 07, 2019 4 - Beta pytest (>=3.6.0) + :pypi:`pytest-structlog` Structured logging assertions Dec 18, 2022 N/A pytest + :pypi:`pytest-structmpd` provide structured temporary directory Oct 17, 2018 N/A N/A + :pypi:`pytest-stub` Stub packages, modules and attributes. Apr 28, 2020 5 - Production/Stable N/A + :pypi:`pytest-stubprocess` Provide stub implementations for subprocesses in Python tests Sep 17, 2018 3 - Alpha pytest (>=3.5.0) + :pypi:`pytest-study` A pytest plugin to organize long run tests (named studies) without interfering the regular tests Sep 26, 2017 3 - Alpha pytest (>=2.0) + :pypi:`pytest-subprocess` A plugin to fake subprocess for pytest Jan 28, 2023 5 - Production/Stable pytest (>=4.0.0) + :pypi:`pytest-subtesthack` A hack to explicitly set up and tear down fixtures. Jul 16, 2022 N/A N/A + :pypi:`pytest-subtests` unittest subTest() support and subtests fixture May 15, 2023 4 - Beta pytest (>=7.0) + :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Aug 29, 2017 N/A N/A + :pypi:`pytest-sugar` pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). Apr 10, 2023 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-suitemanager` A simple plugin to use with pytest Apr 28, 2023 4 - Beta N/A + :pypi:`pytest-svn` SVN repository fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-symbols` pytest-symbols is a pytest plugin that adds support for passing test environment symbols into pytest tests. Nov 20, 2017 3 - Alpha N/A + :pypi:`pytest-system-statistics` Pytest plugin to track and report system usage statistics Feb 16, 2022 5 - Production/Stable pytest (>=6.0.0) + :pypi:`pytest-system-test-plugin` Pyst - Pytest System-Test Plugin Feb 03, 2022 N/A N/A + :pypi:`pytest-tagging` a pytest plugin to tag tests Apr 01, 2023 N/A pytest (>=7.1.3,<8.0.0) + :pypi:`pytest-takeltest` Fixtures for ansible, testinfra and molecule Feb 15, 2023 N/A N/A + :pypi:`pytest-talisker` Nov 28, 2021 N/A N/A + :pypi:`pytest-tally` A Pytest plugin to generate realtime summary stats, and display them in-console using a text-based dashboard. May 22, 2023 4 - Beta pytest (>=6.2.5) + :pypi:`pytest-tap` Test Anything Protocol (TAP) reporting plugin for pytest Oct 27, 2021 5 - Production/Stable pytest (>=3.0) + :pypi:`pytest-tape` easy assertion with expected results saved to yaml files Mar 17, 2021 4 - Beta N/A + :pypi:`pytest-target` Pytest plugin for remote target orchestration. Jan 21, 2021 3 - Alpha pytest (>=6.1.2,<7.0.0) + :pypi:`pytest-tblineinfo` tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used Dec 01, 2015 3 - Alpha pytest (>=2.0) + :pypi:`pytest-tcpclient` A pytest plugin for testing TCP clients Nov 16, 2022 N/A pytest (<8,>=7.1.3) + :pypi:`pytest-teamcity-logblock` py.test plugin to introduce block structure in teamcity build log, if output is not captured May 15, 2018 4 - Beta N/A + :pypi:`pytest-telegram` Pytest to Telegram reporting plugin Dec 10, 2020 5 - Production/Stable N/A + :pypi:`pytest-telegram-notifier` Telegram notification plugin for Pytest Mar 17, 2023 5 - Production/Stable N/A + :pypi:`pytest-tempdir` Predictable and repeatable tempdir support. Oct 11, 2019 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-terra-fixt` Terraform and Terragrunt fixtures for pytest Sep 15, 2022 N/A pytest (==6.2.5) + :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures Sep 01, 2022 N/A pytest (>=6.0) + :pypi:`pytest-terraform-fixture` generate terraform resources to use with pytest Nov 14, 2018 4 - Beta N/A + :pypi:`pytest-testbook` A plugin to run tests written in Jupyter notebook Dec 11, 2016 3 - Alpha N/A + :pypi:`pytest-testconfig` Test configuration plugin for pytest. Jan 11, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testdirectory` A py.test plugin providing temporary directories in unit tests. May 02, 2023 5 - Production/Stable pytest + :pypi:`pytest-testdox` A testdox format reporter for pytest Apr 19, 2022 5 - Production/Stable pytest (>=4.6.0) + :pypi:`pytest-test-grouping` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Feb 01, 2023 5 - Production/Stable pytest (>=2.5) + :pypi:`pytest-test-groups` A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. Oct 25, 2016 5 - Production/Stable N/A + :pypi:`pytest-testinfra` Test infrastructures May 21, 2023 5 - Production/Stable pytest (!=3.0.2) + :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) + :pypi:`pytest-testmon` selects tests affected by changed files and methods Jun 16, 2023 4 - Beta N/A + :pypi:`pytest-testmon-dev` selects tests affected by changed files and methods Mar 30, 2023 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testmon-oc` nOly selects tests affected by changed files and methods Jun 01, 2022 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testmon-skip-libraries` selects tests affected by changed files and methods Mar 03, 2023 4 - Beta pytest (<8,>=5) + :pypi:`pytest-testobject` Plugin to use TestObject Suites with Pytest Sep 24, 2019 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-testpluggy` set your encoding Jan 07, 2022 N/A pytest + :pypi:`pytest-testrail` pytest plugin for creating TestRail runs and adding results Aug 27, 2020 N/A pytest (>=3.6) + :pypi:`pytest-testrail2` A pytest plugin to upload results to TestRail. Feb 10, 2023 N/A pytest (<8.0,>=7.2.0) + :pypi:`pytest-testrail-api-client` TestRail Api Python Client Dec 14, 2021 N/A pytest + :pypi:`pytest-testrail-appetize` pytest plugin for creating TestRail runs and adding results Sep 29, 2021 N/A N/A + :pypi:`pytest-testrail-client` pytest plugin for Testrail Sep 29, 2020 5 - Production/Stable N/A + :pypi:`pytest-testrail-e2e` pytest plugin for creating TestRail runs and adding results Oct 11, 2021 N/A pytest (>=3.6) + :pypi:`pytest-testrail-integrator` Pytest plugin for sending report to testrail system. Aug 01, 2022 N/A pytest (>=6.2.5) + :pypi:`pytest-testrail-ns` pytest plugin for creating TestRail runs and adding results Aug 12, 2022 N/A N/A + :pypi:`pytest-testrail-plugin` PyTest plugin for TestRail Apr 21, 2020 3 - Alpha pytest + :pypi:`pytest-testrail-reporter` Sep 10, 2018 N/A N/A + :pypi:`pytest-testreport` Dec 01, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testreport-new` Aug 15, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-testslide` TestSlide fixture for pytest Jan 07, 2021 5 - Production/Stable pytest (~=6.2) + :pypi:`pytest-test-this` Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply Sep 15, 2019 2 - Pre-Alpha pytest (>=2.3) + :pypi:`pytest-test-utils` Jul 14, 2022 N/A pytest (>=5) + :pypi:`pytest-tesults` Tesults plugin for pytest Dec 23, 2022 5 - Production/Stable pytest (>=3.5.0) + :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-th2-bdd` pytest_th2_bdd May 13, 2022 N/A N/A + :pypi:`pytest-thawgun` Pytest plugin for time travel May 26, 2020 3 - Alpha N/A + :pypi:`pytest-threadleak` Detects thread leaks Jul 03, 2022 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-tick` Ticking on tests Aug 31, 2021 5 - Production/Stable pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-time` Jun 16, 2023 3 - Alpha pytest + :pypi:`pytest-timeit` A pytest plugin to time test function runs Oct 13, 2016 4 - Beta N/A + :pypi:`pytest-timeout` pytest plugin to abort hanging tests Jan 18, 2022 5 - Production/Stable pytest (>=5.0.0) + :pypi:`pytest-timeouts` Linux-only Pytest plugin to control durations of various test case execution phases Sep 21, 2019 5 - Production/Stable N/A + :pypi:`pytest-timer` A timer plugin for pytest Jun 02, 2021 N/A N/A + :pypi:`pytest-timestamper` Pytest plugin to add a timestamp prefix to the pytest output Jun 06, 2021 N/A N/A + :pypi:`pytest-timestamps` A simple plugin to view timestamps for each test Apr 01, 2023 N/A pytest (>=5.2) + :pypi:`pytest-tipsi-django` Nov 17, 2021 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-tipsi-testing` Better fixtures management. Various helpers Nov 04, 2020 4 - Beta pytest (>=3.3.0) + :pypi:`pytest-tldr` A pytest plugin that limits the output to just the things you need. Oct 26, 2022 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-tm4j-reporter` Cloud Jira Test Management (TM4J) PyTest reporter plugin Sep 01, 2020 N/A pytest + :pypi:`pytest-tmnet` A small example package Mar 01, 2022 N/A N/A + :pypi:`pytest-tmp-files` Utilities to create temporary file hierarchies in pytest. Apr 03, 2022 N/A pytest + :pypi:`pytest-tmpfs` A pytest plugin that helps you on using a temporary filesystem for testing. Aug 29, 2022 N/A pytest + :pypi:`pytest-tmreport` this is a vue-element ui report for pytest Aug 12, 2022 N/A N/A + :pypi:`pytest-tmux` A pytest plugin that enables tmux driven tests Apr 22, 2023 4 - Beta N/A + :pypi:`pytest-todo` A small plugin for the pytest testing framework, marking TODO comments as failure May 23, 2019 4 - Beta pytest + :pypi:`pytest-tomato` Mar 01, 2019 5 - Production/Stable N/A + :pypi:`pytest-toolbelt` This is just a collection of utilities for pytest, but don't really belong in pytest proper. Aug 12, 2019 3 - Alpha N/A + :pypi:`pytest-toolbox` Numerous useful plugins for pytest. Apr 07, 2018 N/A pytest (>=3.5.0) + :pypi:`pytest-tools` Pytest tools Oct 21, 2022 4 - Beta N/A + :pypi:`pytest-tornado` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Jun 17, 2020 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-tornado5` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Nov 16, 2018 5 - Production/Stable pytest (>=3.6) + :pypi:`pytest-tornado-yen3` A py.test plugin providing fixtures and markers to simplify testing of asynchronous tornado applications. Oct 15, 2018 5 - Production/Stable N/A + :pypi:`pytest-tornasync` py.test plugin for testing Python 3.5+ Tornado code Jul 15, 2019 3 - Alpha pytest (>=3.0) + :pypi:`pytest-trace` Save OpenTelemetry spans generated during testing Jun 19, 2022 N/A pytest (>=4.6) + :pypi:`pytest-track` Feb 26, 2021 3 - Alpha pytest (>=3.0) + :pypi:`pytest-translations` Test your translation files. Nov 05, 2021 5 - Production/Stable N/A + :pypi:`pytest-travis-fold` Folds captured output sections in Travis CI build log Nov 29, 2017 4 - Beta pytest (>=2.6.0) + :pypi:`pytest-trello` Plugin for py.test that integrates trello using markers Nov 20, 2015 5 - Production/Stable N/A + :pypi:`pytest-trepan` Pytest plugin for trepan debugger. Jul 28, 2018 5 - Production/Stable N/A + :pypi:`pytest-trialtemp` py.test plugin for using the same _trial_temp working directory as trial Jun 08, 2015 N/A N/A + :pypi:`pytest-trio` Pytest plugin for trio Nov 01, 2022 N/A pytest (>=7.2.0) + :pypi:`pytest-trytond` Pytest plugin for the Tryton server framework Nov 04, 2022 4 - Beta pytest (>=5) + :pypi:`pytest-tspwplib` A simple plugin to use with tspwplib Jan 08, 2021 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-tst` Customize pytest options, output and exit code to make it compatible with tst Apr 27, 2022 N/A pytest (>=5.0.0) + :pypi:`pytest-tstcls` Test Class Base Mar 23, 2020 5 - Production/Stable N/A + :pypi:`pytest-tui` Text User Interface (TUI) and HTML report for Pytest test runs Jun 12, 2023 4 - Beta N/A + :pypi:`pytest-tutorials` Mar 11, 2023 N/A N/A + :pypi:`pytest-twilio-conversations-client-mock` Aug 02, 2022 N/A N/A + :pypi:`pytest-twisted` A twisted plugin for pytest. Oct 16, 2022 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-typechecker` Run type checkers on specified test files Feb 04, 2022 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-typhoon-config` A Typhoon HIL plugin that facilitates test parameter configuration at runtime Apr 07, 2022 5 - Production/Stable N/A + :pypi:`pytest-typhoon-xray` Typhoon HIL plugin for pytest Jun 10, 2023 4 - Beta N/A + :pypi:`pytest-tytest` Typhoon HIL plugin for pytest May 25, 2020 4 - Beta pytest (>=5.4.2) + :pypi:`pytest-ubersmith` Easily mock calls to ubersmith at the \`requests\` level. Apr 13, 2015 N/A N/A + :pypi:`pytest-ui` Text User Interface for running python tests Jul 05, 2021 4 - Beta pytest + :pypi:`pytest-ui-failed-screenshot` UI自动测试失败时自动截图,并将截图加入到测试报告中 Dec 06, 2022 N/A N/A + :pypi:`pytest-ui-failed-screenshot-allure` UI自动测试失败时自动截图,并将截图加入到Allure测试报告中 Dec 06, 2022 N/A N/A + :pypi:`pytest-unflakable` Unflakable plugin for PyTest Mar 24, 2023 4 - Beta pytest (>=6.2.0) + :pypi:`pytest-unhandled-exception-exit-code` Plugin for py.test set a different exit code on uncaught exceptions Jun 22, 2020 5 - Production/Stable pytest (>=2.3) + :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) + :pypi:`pytest-unmarked` Run only unmarked tests Aug 27, 2019 5 - Production/Stable N/A + :pypi:`pytest-unordered` Test equality of unordered collections in pytest Nov 28, 2022 4 - Beta pytest (>=6.0.0) + :pypi:`pytest-unstable` Set a test as unstable to return 0 even if it failed Sep 27, 2022 4 - Beta N/A + :pypi:`pytest-unused-fixtures` A pytest plugin to list unused fixtures after a test run. Jun 15, 2023 4 - Beta pytest (>=7.3.2,<8.0.0) + :pypi:`pytest-upload-report` pytest-upload-report is a plugin for pytest that upload your test report for test results. Jun 18, 2021 5 - Production/Stable N/A + :pypi:`pytest-utils` Some helpers for pytest. Feb 02, 2023 4 - Beta pytest (>=7.0.0,<8.0.0) + :pypi:`pytest-vagrant` A py.test plugin providing access to vagrant. Sep 07, 2021 5 - Production/Stable pytest + :pypi:`pytest-valgrind` May 19, 2021 N/A N/A + :pypi:`pytest-variables` pytest plugin for providing variables to tests/fixtures May 27, 2023 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-variant` Variant support for Pytest Jun 06, 2022 N/A N/A + :pypi:`pytest-vcr` Plugin for managing VCR.py cassettes Apr 26, 2019 5 - Production/Stable pytest (>=3.6.0) + :pypi:`pytest-vcr-delete-on-fail` A pytest plugin that automates vcrpy cassettes deletion on test failure. Jun 20, 2022 5 - Production/Stable pytest (>=6.2.2) + :pypi:`pytest-vcrpandas` Test from HTTP interactions to dataframe processed. Jan 12, 2019 4 - Beta pytest + :pypi:`pytest-vcs` Sep 22, 2022 4 - Beta N/A + :pypi:`pytest-venv` py.test fixture for creating a virtual environment Aug 04, 2020 4 - Beta pytest + :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Mar 22, 2023 4 - Beta N/A + :pypi:`pytest-verbose-parametrize` More descriptive output for parametrized py.test tests May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-vimqf` A simple pytest plugin that will shrink pytest output when specified, to fit vim quickfix window. Feb 08, 2021 4 - Beta pytest (>=6.2.2,<7.0.0) + :pypi:`pytest-virtualenv` Virtualenv fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-vnc` VNC client for Pytest Feb 25, 2023 N/A pytest + :pypi:`pytest-voluptuous` Pytest plugin for asserting data against voluptuous schema. Jun 09, 2020 N/A pytest + :pypi:`pytest-vscodedebug` A pytest plugin to easily enable debugging tests within Visual Studio Code Dec 04, 2020 4 - Beta N/A + :pypi:`pytest-vscode-pycharm-cls` A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used. Feb 01, 2023 N/A pytest + :pypi:`pytest-vts` pytest plugin for automatic recording of http stubbed tests Jun 05, 2019 N/A pytest (>=2.3) + :pypi:`pytest-vulture` A pytest plugin to checks dead code with vulture Jun 01, 2023 N/A pytest (>=7.0.0) + :pypi:`pytest-vw` pytest-vw makes your failing test cases succeed under CI tools scrutiny Oct 07, 2015 4 - Beta N/A + :pypi:`pytest-vyper` Plugin for the vyper smart contract language. May 28, 2020 2 - Pre-Alpha N/A + :pypi:`pytest-wa-e2e-plugin` Pytest plugin for testing whatsapp bots with end to end tests Feb 18, 2020 4 - Beta pytest (>=3.5.0) + :pypi:`pytest-wake` May 11, 2023 N/A pytest + :pypi:`pytest-watch` Local continuous test runner with pytest and watchdog. May 20, 2018 N/A N/A + :pypi:`pytest-watcher` Automatically rerun your tests on file modifications Jun 11, 2023 4 - Beta N/A + :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A + :pypi:`pytest-web3-data` Sep 15, 2022 4 - Beta pytest + :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest + :pypi:`pytest-wetest` Welian API Automation test framework pytest plugin Nov 10, 2018 4 - Beta N/A + :pypi:`pytest-when` Utility which makes mocking more readable and controllable Jun 05, 2023 N/A pytest>=7.3.1 + :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A + :pypi:`pytest-wholenodeid` pytest addon for displaying the whole node id for failures Aug 26, 2015 4 - Beta pytest (>=2.0) + :pypi:`pytest-win32consoletitle` Pytest progress in console title (Win32 only) Aug 08, 2021 N/A N/A + :pypi:`pytest-winnotify` Windows tray notifications for py.test results. Apr 22, 2016 N/A N/A + :pypi:`pytest-wiremock` A pytest plugin for programmatically using wiremock in integration tests Mar 27, 2022 N/A pytest (>=7.1.1,<8.0.0) + :pypi:`pytest-with-docker` pytest with docker helpers. Nov 09, 2021 N/A pytest + :pypi:`pytest-workflow` A pytest plugin for configuring workflow/pipeline tests using YAML files Jan 13, 2023 5 - Production/Stable pytest (>=7.0.0) + :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs May 19, 2023 5 - Production/Stable pytest (>=6.2.0) + :pypi:`pytest-xdist-debug-for-graingert` pytest xdist plugin for distributed testing and loop-on-failing modes Jul 24, 2019 5 - Production/Stable pytest (>=4.4.0) + :pypi:`pytest-xdist-forked` forked from pytest-xdist Feb 10, 2020 5 - Production/Stable pytest (>=4.4.0) + :pypi:`pytest-xdist-tracker` pytest plugin helps to reproduce failures for particular xdist node Nov 18, 2021 3 - Alpha pytest (>=3.5.1) + :pypi:`pytest-xdist-worker-stats` A pytest plugin to list worker statistics after a xdist run. Jun 15, 2023 4 - Beta pytest (>=7.3.2,<8.0.0) + :pypi:`pytest-xfaillist` Maintain a xfaillist in an additional file to avoid merge-conflicts. Sep 17, 2021 N/A pytest (>=6.2.2,<7.0.0) + :pypi:`pytest-xfiles` Pytest fixtures providing data read from function, module or package related (x)files. Feb 27, 2018 N/A N/A + :pypi:`pytest-xlog` Extended logging for test and decorators May 31, 2020 4 - Beta N/A + :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Mar 01, 2023 N/A pytest>=7.2.0 + :pypi:`pytest-xpara` An extended parametrizing plugin of pytest. Oct 30, 2017 3 - Alpha pytest + :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. Jan 05, 2023 4 - Beta pytest (>=2.8) + :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A + :pypi:`pytest-xrayjira` Mar 17, 2020 3 - Alpha pytest (==4.3.1) + :pypi:`pytest-xray-server` May 03, 2022 3 - Alpha pytest (>=5.3.1) + :pypi:`pytest-xskynet` A package to prevent Dependency Confusion attacks against Yandex. Feb 10, 2023 N/A N/A + :pypi:`pytest-xvfb` A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. May 29, 2023 4 - Beta pytest (>=2.8.1) + :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml May 28, 2023 N/A pytest>=7.2.0 + :pypi:`pytest-yamltree` Create or check file/directory trees described by YAML Mar 02, 2020 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yamlwsgi` Run tests against wsgi apps defined in yaml May 11, 2010 N/A N/A + :pypi:`pytest-yaml-yoyo` http/https API run by yaml Jun 08, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-yapf` Run yapf Jul 06, 2017 4 - Beta pytest (>=3.1.1) + :pypi:`pytest-yapf3` Validate your Python file format with yapf Mar 29, 2023 5 - Production/Stable pytest (>=7) + :pypi:`pytest-yield` PyTest plugin to run tests concurrently, each \`yield\` switch context to other one Jan 23, 2019 N/A N/A + :pypi:`pytest-yls` Pytest plugin to test the YLS as a whole. Mar 29, 2023 N/A pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-yuk` Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. Mar 26, 2021 N/A pytest>=5.0.0 + :pypi:`pytest-zafira` A Zafira plugin for pytest Sep 18, 2019 5 - Production/Stable pytest (==4.1.1) + :pypi:`pytest-zap` OWASP ZAP plugin for py.test. May 12, 2014 4 - Beta N/A + :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Dec 12, 2022 5 - Production/Stable pytest (>=4.5.0) + :pypi:`pytest-zest` Zesty additions to pytest. Nov 17, 2022 N/A N/A + :pypi:`pytest-zigzag` Extend py.test for RPC OpenStack testing. Feb 27, 2019 4 - Beta pytest (~=3.6) + :pypi:`pytest-zulip` Pytest report plugin for Zulip May 07, 2022 5 - Production/Stable pytest + =============================================== ======================================================================================================================================================================================================== ============== ===================== ================================================ .. only:: latex + :pypi:`logassert` + *last release*: May 20, 2022, + *status*: 5 - Production/Stable, + *requires*: N/A + + Simple but powerful assertion and verification of logged lines. + + :pypi:`pytest-abq` + *last release*: Apr 07, 2023, + *status*: N/A, + *requires*: N/A + + Pytest integration for the ABQ universal test runner. + :pypi:`pytest-abstracts` *last release*: May 25, 2022, *status*: N/A, @@ -1149,14 +1314,14 @@ This list contains 1118 plugins. A contextmanager pytest fixture for handling multiple mock abstracts :pypi:`pytest-accept` - *last release*: Jan 07, 2022, + *last release*: Dec 21, 2022, *status*: N/A, *requires*: pytest (>=6,<8) A pytest-plugin for updating doctest outputs :pypi:`pytest-adaptavist` - *last release*: Jun 07, 2022, + *last release*: Oct 13, 2022, *status*: N/A, *requires*: pytest (>=5.4.0) @@ -1183,6 +1348,13 @@ This list contains 1118 plugins. Pytest plugin for writing Azure Data Factory integration tests + :pypi:`pytest-ads-testplan` + *last release*: Sep 15, 2022, + *status*: N/A, + *requires*: N/A + + Azure DevOps Test Case reporting for pytest tests + :pypi:`pytest-agent` *last release*: Nov 25, 2021, *status*: N/A, @@ -1198,7 +1370,7 @@ This list contains 1118 plugins. pytest plugin for pytest-repeat that generate aggregate report of the same test cases with additional statistics details. :pypi:`pytest-aio` - *last release*: Oct 20, 2021, + *last release*: Feb 03, 2023, *status*: 4 - Beta, *requires*: pytest @@ -1211,6 +1383,13 @@ This list contains 1118 plugins. pytest fixtures for writing aiofiles tests with pyfakefs + :pypi:`pytest-aiogram` + *last release*: May 06, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-aiohttp` *last release*: Feb 12, 2022, *status*: 4 - Beta, @@ -1219,14 +1398,14 @@ This list contains 1118 plugins. Pytest plugin for aiohttp support :pypi:`pytest-aiohttp-client` - *last release*: Nov 01, 2020, + *last release*: Jan 10, 2023, *status*: N/A, - *requires*: pytest (>=6) + *requires*: pytest (>=7.2.0,<8.0.0) Pytest \`client\` fixture for the Aiohttp :pypi:`pytest-aiomoto` - *last release*: Jul 10, 2022, + *last release*: Nov 09, 2022, *status*: N/A, *requires*: pytest (>=7.0,<8.0) @@ -1240,9 +1419,9 @@ This list contains 1118 plugins. py.test integration for aioresponses :pypi:`pytest-aioworkers` - *last release*: Dec 04, 2019, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *last release*: May 01, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=6.1.0 A plugin to test aioworkers project with pytest @@ -1261,9 +1440,9 @@ This list contains 1118 plugins. :pypi:`pytest-alembic` - *last release*: Aug 03, 2022, + *last release*: May 23, 2023, *status*: N/A, - *requires*: pytest (>=1.0) + *requires*: pytest (>=6.0) A pytest plugin for verifying alembic migrations. @@ -1289,7 +1468,7 @@ This list contains 1118 plugins. Plugin for py.test to generate allure xml reports :pypi:`pytest-allure-collection` - *last release*: Sep 08, 2022, + *last release*: Apr 13, 2023, *status*: N/A, *requires*: pytest @@ -1302,6 +1481,13 @@ This list contains 1118 plugins. pytest plugin to test case doc string dls instructions + :pypi:`pytest-allure-intersection` + *last release*: Oct 27, 2022, + *status*: N/A, + *requires*: pytest (<5) + + + :pypi:`pytest-allure-spec-coverage` *last release*: Oct 26, 2021, *status*: N/A, @@ -1338,11 +1524,11 @@ This list contains 1118 plugins. pytest-annotate: Generate PyAnnotate annotations from your pytest tests. :pypi:`pytest-ansible` - *last release*: May 25, 2021, + *last release*: May 15, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (<8.0.0,>=6) - Plugin for py.test to simplify calling ansible modules from tests or fixtures + Plugin for pytest to simplify calling ansible modules from tests or fixtures :pypi:`pytest-ansible-playbook` *last release*: Mar 08, 2019, @@ -1380,14 +1566,14 @@ This list contains 1118 plugins. The pytest anyio plugin is built into anyio. You don't need this package. :pypi:`pytest-anything` - *last release*: Feb 18, 2021, + *last release*: Oct 13, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest Pytest fixtures to assert anything and something :pypi:`pytest-aoc` - *last release*: Nov 23, 2021, + *last release*: Dec 08, 2022, *status*: N/A, *requires*: pytest ; extra == 'test' @@ -1442,6 +1628,20 @@ This list contains 1118 plugins. A plugin to use approvaltests with pytest + :pypi:`pytest-approvaltests-geo` + *last release*: Mar 04, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + Extension for ApprovalTests.Python specific to geo data verification + + :pypi:`pytest-archon` + *last release*: Jan 31, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2) + + Rule your architecture like a real developer + :pypi:`pytest-argus` *last release*: Jun 24, 2021, *status*: 5 - Production/Stable, @@ -1471,9 +1671,9 @@ This list contains 1118 plugins. test Answer Set Programming programs :pypi:`pytest-assertcount` - *last release*: May 22, 2022, + *last release*: Oct 23, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=5.0.0) Plugin to count actual number of asserts in pytest @@ -1548,14 +1748,14 @@ This list contains 1118 plugins. :pypi:`pytest-asyncio` - *last release*: Jul 15, 2022, + *last release*: Mar 19, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.1.0) + *requires*: pytest (>=7.0.0) Pytest support for asyncio :pypi:`pytest-asyncio-cooperative` - *last release*: Jul 11, 2022, + *last release*: May 31, 2023, *status*: N/A, *requires*: N/A @@ -1625,7 +1825,7 @@ This list contains 1118 plugins. pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. :pypi:`pytest-automock` - *last release*: Aug 04, 2022, + *last release*: May 16, 2023, *status*: N/A, *requires*: pytest ; extra == 'dev' @@ -1645,6 +1845,13 @@ This list contains 1118 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. + :pypi:`pytest-aviator` + *last release*: Nov 04, 2022, + *status*: 4 - Beta, + *requires*: pytest + + Aviator's Flakybot pytest plugin that automatically reruns flaky tests. + :pypi:`pytest-avoidance` *last release*: May 23, 2019, *status*: 4 - Beta, @@ -1673,6 +1880,13 @@ This list contains 1118 plugins. pytest plugin for axe-selenium-python + :pypi:`pytest-azure` + *last release*: Jan 18, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest utilities and mocks for Azure + :pypi:`pytest-azure-devops` *last release*: Jun 20, 2022, *status*: 4 - Beta, @@ -1681,8 +1895,8 @@ This list contains 1118 plugins. Simplifies using azure devops parallel strategy (https://docs.microsoft.com/en-us/azure/devops/pipelines/test/parallel-testing-any-test-runner) with pytest. :pypi:`pytest-azurepipelines` - *last release*: Mar 16, 2022, - *status*: 4 - Beta, + *last release*: Oct 20, 2022, + *status*: 5 - Production/Stable, *requires*: pytest (>=5.0.0) Formatting PyTest output for Azure Pipelines UI @@ -1694,6 +1908,13 @@ This list contains 1118 plugins. A bandit plugin for pytest + :pypi:`pytest-bandit-xayon` + *last release*: Oct 17, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A bandit plugin for pytest + :pypi:`pytest-base-url` *last release*: Mar 27, 2022, *status*: 5 - Production/Stable, @@ -1702,21 +1923,21 @@ This list contains 1118 plugins. pytest plugin for URL based testing :pypi:`pytest-bdd` - *last release*: Jul 07, 2022, + *last release*: Nov 08, 2022, *status*: 6 - Mature, - *requires*: pytest (>=5.0) + *requires*: pytest (>=6.2.0) BDD for pytest :pypi:`pytest-bdd-html` - *last release*: Jul 25, 2022, + *last release*: Nov 22, 2022, *status*: 3 - Alpha, *requires*: pytest (!=6.0.0,>=5.0) pytest plugin to display BDD info in HTML test report :pypi:`pytest-bdd-ng` - *last release*: Jul 24, 2022, + *last release*: Oct 06, 2022, *status*: 4 - Beta, *requires*: pytest (>=5.0) @@ -1757,6 +1978,13 @@ This list contains 1118 plugins. Fixtures for testing Google Appengine (GAE) apps + :pypi:`pytest-beeprint` + *last release*: Jun 09, 2023, + *status*: 4 - Beta, + *requires*: N/A + + use icdiff for better error messages in pytest assertions + :pypi:`pytest-bench` *last release*: Jul 21, 2014, *status*: 3 - Alpha, @@ -1765,12 +1993,19 @@ This list contains 1118 plugins. Benchmark utility that plugs into pytest. :pypi:`pytest-benchmark` - *last release*: Apr 17, 2021, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.8) A \`\`pytest\`\` fixture for benchmarking code. It will group the tests into rounds that are calibrated to the chosen timer. + :pypi:`pytest-better-datadir` + *last release*: Mar 13, 2023, + *status*: N/A, + *requires*: N/A + + A small example package + :pypi:`pytest-bg-process` *last release*: Jan 24, 2022, *status*: 4 - Beta, @@ -1786,7 +2021,7 @@ This list contains 1118 plugins. A BigchainDB plugin for pytest. :pypi:`pytest-bigquery-mock` - *last release*: Aug 05, 2021, + *last release*: Dec 28, 2022, *status*: N/A, *requires*: pytest (>=5.0) @@ -1806,6 +2041,13 @@ This list contains 1118 plugins. Allow '--black' on older Pythons + :pypi:`pytest-black-ng` + *last release*: Oct 20, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin to enable format checking with black + :pypi:`pytest-blame` *last release*: May 04, 2019, *status*: N/A, @@ -1814,7 +2056,7 @@ This list contains 1118 plugins. A pytest plugin helps developers to debug by providing useful commits history. :pypi:`pytest-blender` - *last release*: Jun 16, 2022, + *last release*: Jan 04, 2023, *status*: N/A, *requires*: pytest ; extra == 'dev' @@ -1855,6 +2097,13 @@ This list contains 1118 plugins. Local continuous test runner with pytest and watchdog. + :pypi:`pytest-boost-xml` + *last release*: Nov 30, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Plugin for pytest to generate boost xml reports + :pypi:`pytest-bootstrap` *last release*: Mar 04, 2022, *status*: N/A, @@ -1918,8 +2167,15 @@ This list contains 1118 plugins. \`\`py.test\`\` plugin to run \`\`BrowserStackLocal\`\` in background. + :pypi:`pytest-budosystems` + *last release*: May 07, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. + :pypi:`pytest-bug` - *last release*: Apr 13, 2022, + *last release*: Jan 29, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6.2.0) @@ -1975,8 +2231,8 @@ This list contains 1118 plugins. pytest plugin with mechanisms for caching across test runs :pypi:`pytest-cache-assert` - *last release*: May 02, 2022, - *status*: 4 - Beta, + *last release*: Feb 26, 2023, + *status*: 5 - Production/Stable, *requires*: pytest (>=5.0.0) Cache assertion data to simplify regression testing of complex serializable data @@ -1995,6 +2251,13 @@ This list contains 1118 plugins. Pytest support for cairo-lang and starknet + :pypi:`pytest-call-checker` + *last release*: Oct 16, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.3,<8.0.0) + + Small pytest utility to easily create test doubles + :pypi:`pytest-camel-collect` *last release*: Aug 02, 2020, *status*: N/A, @@ -2031,7 +2294,7 @@ This list contains 1118 plugins. pytest plugin to capture all warnings and put them in one file of your choice :pypi:`pytest-cases` - *last release*: May 20, 2022, + *last release*: Feb 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -2079,6 +2342,13 @@ This list contains 1118 plugins. A set of py.test fixtures for AWS Chalice + :pypi:`pytest-change-assert` + *last release*: Oct 19, 2022, + *status*: N/A, + *requires*: N/A + + 修改报错中文为英文 + :pypi:`pytest-change-demo` *last release*: Mar 02, 2022, *status*: N/A, @@ -2108,16 +2378,16 @@ This list contains 1118 plugins. A pytest fixture for changing current working directory :pypi:`pytest-check` - *last release*: Aug 25, 2022, - *status*: 5 - Production/Stable, - *requires*: N/A + *last release*: Jun 06, 2023, + *status*: N/A, + *requires*: pytest A pytest plugin that allows multiple failures per test. :pypi:`pytest-checkdocs` - *last release*: Jul 31, 2021, + *last release*: Oct 09, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.6) ; extra == 'testing' + *requires*: pytest (>=6) ; extra == 'testing' check the README when running tests @@ -2145,7 +2415,7 @@ This list contains 1118 plugins. :pypi:`pytest-check-links` *last release*: Jul 29, 2020, *status*: N/A, - *requires*: pytest (>=7.0) + *requires*: pytest>=7.0 Check links in files @@ -2156,6 +2426,20 @@ This list contains 1118 plugins. pytest plugin to test Check_MK checks + :pypi:`pytest-check-requirements` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + + :pypi:`pytest-chic-report` + *last release*: Jan 31, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + A pytest plugin to send a report and printing summary of tests. + :pypi:`pytest-chunks` *last release*: Jul 05, 2022, *status*: N/A, @@ -2171,7 +2455,7 @@ This list contains 1118 plugins. py.test plugin for CircleCI :pypi:`pytest-circleci-parallelized` - *last release*: Mar 26, 2019, + *last release*: Oct 20, 2022, *status*: N/A, *requires*: N/A @@ -2199,9 +2483,9 @@ This list contains 1118 plugins. A plugin providing an alternative, colourful diff output for failing assertions. :pypi:`pytest-cldf` - *last release*: May 06, 2019, + *last release*: Nov 07, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=3.6) Easy quality control for CLDF datasets using pytest @@ -2247,6 +2531,20 @@ This list contains 1118 plugins. Distribute tests to cloud machines without fuss + :pypi:`pytest-cmake` + *last release*: Jan 21, 2023, + *status*: N/A, + *requires*: pytest<8,>=4 + + Provide CMake module for Pytest + + :pypi:`pytest-cmake-presets` + *last release*: Dec 26, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + Execute CMake Presets via pytest + :pypi:`pytest-cobra` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -2269,7 +2567,7 @@ This list contains 1118 plugins. pytest plugin to add source code sanity checks (pep8 and friends) :pypi:`pytest-codecov` - *last release*: Apr 12, 2022, + *last release*: Nov 29, 2022, *status*: 4 - Beta, *requires*: pytest (>=4.6.0) @@ -2296,6 +2594,13 @@ This list contains 1118 plugins. pytest plugin to run pycodestyle + :pypi:`pytest-codspeed` + *last release*: Dec 02, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest>=3.8 + + Pytest plugin to create CodSpeed benchmarks + :pypi:`pytest-collect-formatter` *last release*: Mar 29, 2021, *status*: 5 - Production/Stable, @@ -2338,6 +2643,13 @@ This list contains 1118 plugins. pytest framework for testing different aspects of a common method + :pypi:`pytest-compare` + *last release*: Mar 30, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + pytest plugin for comparing call arguments. + :pypi:`pytest-concurrent` *last release*: Jan 12, 2019, *status*: 4 - Beta, @@ -2360,9 +2672,9 @@ This list contains 1118 plugins. Package stands for pytest plugin to upload results into Confluence page. :pypi:`pytest-console-scripts` - *last release*: Mar 18, 2022, + *last release*: May 31, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=4.0.0) Pytest plugin for testing console scripts @@ -2374,8 +2686,8 @@ This list contains 1118 plugins. pytest plugin with fixtures for testing consul aware apps :pypi:`pytest-container` - *last release*: Feb 01, 2022, - *status*: 3 - Alpha, + *last release*: Mar 21, 2023, + *status*: 4 - Beta, *requires*: pytest (>=3.10) Pytest fixtures for writing container based tests @@ -2395,9 +2707,9 @@ This list contains 1118 plugins. A plugin to run tests written with the Contexts framework using pytest :pypi:`pytest-cookies` - *last release*: May 24, 2021, + *last release*: Mar 22, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.3.0) + *requires*: pytest (>=3.9.0) The pytest plugin for your Cookiecutter templates. 🍪 @@ -2416,7 +2728,7 @@ This list contains 1118 plugins. count erros and send email :pypi:`pytest-cov` - *last release*: Oct 04, 2021, + *last release*: May 24, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) @@ -2443,6 +2755,13 @@ This list contains 1118 plugins. Coverage dynamic context support for PyTest, including sub-processes + :pypi:`pytest-coveragemarkers` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + Using pytest markers to track functional coverage and filtering of tests + :pypi:`pytest-cov-exclude` *last release*: Apr 29, 2016, *status*: 4 - Beta, @@ -2451,14 +2770,14 @@ This list contains 1118 plugins. Pytest plugin for excluding tests based on coverage data :pypi:`pytest-cpp` - *last release*: Aug 22, 2022, + *last release*: Jan 30, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=7.0) Use pytest's runner to discover and execute C++ tests :pypi:`pytest-cppython` - *last release*: Sep 10, 2022, + *last release*: Jun 14, 2023, *status*: N/A, *requires*: N/A @@ -2485,6 +2804,20 @@ This list contains 1118 plugins. Manages CrateDB instances during your integration tests + :pypi:`pytest-crayons` + *last release*: Mar 19, 2023, + *status*: N/A, + *requires*: pytest + + A pytest plugin for colorful print statements + + :pypi:`pytest-create` + *last release*: Feb 15, 2023, + *status*: 1 - Planning, + *requires*: N/A + + pytest-create + :pypi:`pytest-cricri` *last release*: Jan 27, 2018, *status*: N/A, @@ -2563,7 +2896,7 @@ This list contains 1118 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report :pypi:`pytest-cython` - *last release*: Mar 26, 2022, + *last release*: Feb 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6.0) @@ -2605,14 +2938,14 @@ This list contains 1118 plugins. Pytest plugin for remote Databricks notebooks testing :pypi:`pytest-datadir` - *last release*: Oct 22, 2019, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.7.0) + *requires*: pytest (>=5.0) pytest plugin for test data directories and files :pypi:`pytest-datadir-mgr` - *last release*: Aug 16, 2022, + *last release*: Apr 06, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=7.1) @@ -2625,6 +2958,13 @@ This list contains 1118 plugins. Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. + :pypi:`pytest-datadir-nng` + *last release*: Nov 09, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.0.0,<8.0.0) + + Fixtures for pytest allowing test functions/methods to easily retrieve test resources from the local filesystem. + :pypi:`pytest-data-extractor` *last release*: Jul 19, 2022, *status*: N/A, @@ -2640,11 +2980,11 @@ This list contains 1118 plugins. Fixture "data" and "case_data" for test from yaml file :pypi:`pytest-datafiles` - *last release*: May 01, 2022, + *last release*: Feb 24, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=3.6) - py.test plugin to create a 'tmpdir' containing predefined files/directories. + py.test plugin to create a 'tmp_path' containing predefined files/directories. :pypi:`pytest-datafixtures` *last release*: Dec 05, 2020, @@ -2668,14 +3008,14 @@ This list contains 1118 plugins. A pytest plugin for managing an archive of test data. :pypi:`pytest-datarecorder` - *last release*: Apr 20, 2020, + *last release*: Jan 08, 2023, *status*: 5 - Production/Stable, *requires*: pytest A py.test plugin recording and comparing test output. :pypi:`pytest-dataset` - *last release*: Sep 05, 2022, + *last release*: May 01, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -2716,6 +3056,13 @@ This list contains 1118 plugins. + :pypi:`pytest-dbt` + *last release*: Jun 08, 2023, + *status*: 2 - Pre-Alpha, + *requires*: pytest (>=7.0.0,<8.0.0) + + Unit test dbt models with standard python tooling + :pypi:`pytest-dbt-adapter` *last release*: Nov 24, 2021, *status*: N/A, @@ -2731,7 +3078,7 @@ This list contains 1118 plugins. A pytest plugin for linting a dbt project's conventions :pypi:`pytest-dbt-core` - *last release*: Jul 22, 2022, + *last release*: May 03, 2023, *status*: N/A, *requires*: pytest (>=6.2.5) ; extra == 'test' @@ -2744,6 +3091,13 @@ This list contains 1118 plugins. D-BUS notifications for pytest results. + :pypi:`pytest-dbx` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.3,<8.0.0) + + Pytest plugin to run unit tests for dbx (Databricks CLI extensions) related code + :pypi:`pytest-deadfixtures` *last release*: Jul 23, 2020, *status*: 5 - Production/Stable, @@ -2794,9 +3148,9 @@ This list contains 1118 plugins. Mark tests as testing a deprecated feature with a warning note. :pypi:`pytest-describe` - *last release*: Nov 13, 2021, - *status*: 4 - Beta, - *requires*: pytest (>=4.0.0) + *last release*: Apr 09, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (<8,>=4.6) Describe-style plugin for pytest @@ -2849,6 +3203,13 @@ This list contains 1118 plugins. A simple plugin to use with pytest + :pypi:`pytest-diffeo` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-diff-selector` *last release*: Feb 24, 2022, *status*: 4 - Beta, @@ -2856,6 +3217,13 @@ This list contains 1118 plugins. Get tests affected by code changes (using git) + :pypi:`pytest-difido` + *last release*: Oct 23, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=4.0.0) + + PyTest plugin for generating Difido reports + :pypi:`pytest-disable` *last release*: Sep 10, 2015, *status*: 4 - Beta, @@ -2871,7 +3239,7 @@ This list contains 1118 plugins. Disable plugins per test :pypi:`pytest-discord` - *last release*: Mar 27, 2022, + *last release*: Feb 05, 2023, *status*: 4 - Beta, *requires*: pytest (!=6.0.0,<8,>=3.3.2) @@ -2892,9 +3260,9 @@ This list contains 1118 plugins. A Django plugin for pytest. :pypi:`pytest-djangoapp` - *last release*: Aug 04, 2021, + *last release*: May 19, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest Nice pytest plugin to help you with Django pluggable application testing. @@ -2948,8 +3316,8 @@ This list contains 1118 plugins. Cleanup your Haystack indexes between tests :pypi:`pytest-django-ifactory` - *last release*: Feb 09, 2022, - *status*: 3 - Alpha, + *last release*: Jun 06, 2023, + *status*: 5 - Production/Stable, *requires*: N/A A model instance factory for pytest-django @@ -3039,7 +3407,7 @@ This list contains 1118 plugins. An RST Documentation Generator for pytest-based test suites :pypi:`pytest-docker` - *last release*: Jul 27, 2022, + *last release*: Sep 14, 2022, *status*: N/A, *requires*: pytest (<8.0,>=4.0) @@ -3081,9 +3449,9 @@ This list contains 1118 plugins. A plugin to use docker databases for pytests :pypi:`pytest-docker-fixtures` - *last release*: Jul 06, 2022, + *last release*: May 02, 2023, *status*: 3 - Alpha, - *requires*: N/A + *requires*: pytest pytest docker fixtures @@ -3130,9 +3498,9 @@ This list contains 1118 plugins. Pytest fixtures for testing with docker registries. :pypi:`pytest-docker-service` - *last release*: Mar 21, 2022, + *last release*: Feb 22, 2023, *status*: 3 - Alpha, - *requires*: pytest + *requires*: pytest (>=7.1.3) pytest plugin to start docker container @@ -3186,7 +3554,7 @@ This list contains 1118 plugins. A simple pytest plugin to import names and add them to the doctest namespace. :pypi:`pytest-doctestplus` - *last release*: Feb 25, 2022, + *last release*: Jun 08, 2023, *status*: 3 - Alpha, *requires*: pytest (>=4.6) @@ -3213,6 +3581,13 @@ This list contains 1118 plugins. A py.test plugin that parses environment files before running tests + :pypi:`pytest-draw` + *last release*: Mar 21, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin for randomly selecting a specific number of tests + :pypi:`pytest-drf` *last release*: Jul 12, 2022, *status*: 5 - Production/Stable, @@ -3270,7 +3645,7 @@ This list contains 1118 plugins. A pytest plugin to rerun tests dynamically based off of test outcome and output. :pypi:`pytest-dynamodb` - *last release*: Jun 03, 2021, + *last release*: Jun 12, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -3332,6 +3707,13 @@ This list contains 1118 plugins. pytest plugin with mechanisms for echoing environment variables, package version and generic attributes + :pypi:`pytest-ekstazi` + *last release*: Sep 10, 2022, + *status*: N/A, + *requires*: pytest + + Pytest plugin to select test using Ekstazi algorithm + :pypi:`pytest-elasticsearch` *last release*: Mar 01, 2022, *status*: 5 - Production/Stable, @@ -3368,56 +3750,56 @@ This list contains 1118 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Aug 23, 2022, - *status*: N/A, - *requires*: pytest (>=7.0) + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.0 - pytest embedded plugin + A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Aug 23, 2022, - *status*: N/A, + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for Arduino projects + Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Aug 23, 2022, - *status*: N/A, + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for esp-idf project + Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Aug 23, 2022, - *status*: N/A, + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for testing with jtag + Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Aug 23, 2022, - *status*: N/A, + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for qemu, not target chip + Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Aug 23, 2022, - *status*: N/A, + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for testing serial ports + Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Aug 23, 2022, - *status*: N/A, + *last release*: Jun 14, 2023, + *status*: 5 - Production/Stable, *requires*: N/A - pytest embedded plugin for testing espressif boards via serial ports + Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embrace` - *last release*: Aug 27, 2022, + *last release*: Mar 25, 2023, *status*: N/A, *requires*: pytest (>=7.0,<8.0) @@ -3431,14 +3813,14 @@ This list contains 1118 plugins. A pytest plugin that adds emojis to your test result report :pypi:`pytest-emoji-output` - *last release*: Apr 12, 2022, + *last release*: Apr 09, 2023, *status*: 4 - Beta, *requires*: pytest (==7.0.1) Pytest plugin to represent test output with emoji support :pypi:`pytest-enabler` - *last release*: Jun 22, 2022, + *last release*: May 12, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6) ; extra == 'testing' @@ -3458,6 +3840,13 @@ This list contains 1118 plugins. set your encoding and logger + :pypi:`pytest-enhanced-reports` + *last release*: Dec 15, 2022, + *status*: N/A, + *requires*: N/A + + Enhanced test reports for pytest + :pypi:`pytest-enhancements` *last release*: Oct 30, 2019, *status*: 4 - Beta, @@ -3466,9 +3855,9 @@ This list contains 1118 plugins. Improvements for pytest (rejected upstream) :pypi:`pytest-env` - *last release*: Jun 16, 2017, - *status*: 4 - Beta, - *requires*: N/A + *last release*: Jun 15, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.3.1 py.test plugin that allows you to add environment variables. @@ -3549,6 +3938,13 @@ This list contains 1118 plugins. Applies eventlet monkey-patch as a pytest plugin. + :pypi:`pytest-examples` + *last release*: May 05, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7 + + Pytest plugin for testing examples in docstrings and markdown files. + :pypi:`pytest-excel` *last release*: Jan 31, 2022, *status*: 5 - Production/Stable, @@ -3571,9 +3967,9 @@ This list contains 1118 plugins. Walk your code through exception script to check it's resiliency to failures. :pypi:`pytest-executable` - *last release*: Nov 10, 2021, - *status*: 4 - Beta, - *requires*: pytest (<6.3,>=4.3) + *last release*: Mar 25, 2023, + *status*: N/A, + *requires*: pytest (<8,>=4.3) pytest plugin for testing executables @@ -3591,8 +3987,15 @@ This list contains 1118 plugins. py.test plugin to store test expectations and mark tests based on them + :pypi:`pytest-expectdir` + *last release*: Mar 19, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=5.0) + + A pytest plugin to provide initial/expected directories, and check a test transforms the initial directory to the expected one + :pypi:`pytest-expecter` - *last release*: Jan 10, 2022, + *last release*: Sep 18, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -3605,6 +4008,13 @@ This list contains 1118 plugins. This plugin is used to expect multiple assert using pytest framework. + :pypi:`pytest-expect-test` + *last release*: Apr 10, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A fixture to support expect tests in pytest + :pypi:`pytest-experiments` *last release*: Dec 13, 2021, *status*: 4 - Beta, @@ -3647,6 +4057,13 @@ This list contains 1118 plugins. A pytest plugin to get durations on a per-function basis and per module basis. + :pypi:`pytest-extra-markers` + *last release*: Mar 05, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Additional pytest markers to dynamically enable/disable tests viia CLI flags + :pypi:`pytest-fabric` *last release*: Sep 12, 2018, *status*: 5 - Production/Stable, @@ -3654,6 +4071,13 @@ This list contains 1118 plugins. Provides test utilities to run fabric task tests by using docker containers + :pypi:`pytest-factor` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-factory` *last release*: Sep 06, 2020, *status*: 3 - Alpha, @@ -3662,7 +4086,7 @@ This list contains 1118 plugins. Use factories for test setup with py.test :pypi:`pytest-factoryboy` - *last release*: Jun 14, 2022, + *last release*: Dec 01, 2022, *status*: 6 - Mature, *requires*: pytest (>=5.0.0) @@ -3682,6 +4106,13 @@ This list contains 1118 plugins. Simple factoryboy random state management + :pypi:`pytest-failed-screen-record` + *last release*: Jan 05, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.1.2d,<8.0.0) + + Create a video of the screen when pytest fails + :pypi:`pytest-failed-screenshot` *last release*: Apr 21, 2021, *status*: N/A, @@ -3746,14 +4177,14 @@ This list contains 1118 plugins. A fixture which allows easy replacement of fastapi dependencies for testing :pypi:`pytest-fastest` - *last release*: Mar 05, 2020, - *status*: N/A, - *requires*: N/A + *last release*: Jun 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=4.4) Use SCM and coverage to run only needed tests :pypi:`pytest-fast-first` - *last release*: Apr 02, 2021, + *last release*: Jan 19, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -3801,6 +4232,13 @@ This list contains 1118 plugins. A pytest plugin that runs marked tests when files change. + :pypi:`pytest-file-watcher` + *last release*: Mar 23, 2023, + *status*: N/A, + *requires*: pytest + + Pytest-File-Watcher is a CLI tool that watches for changes in your code and runs pytest on the changed files. + :pypi:`pytest-filter-case` *last release*: Nov 05, 2020, *status*: N/A, @@ -3809,7 +4247,7 @@ This list contains 1118 plugins. run test cases filter by mark :pypi:`pytest-filter-subpackage` - *last release*: Jan 09, 2020, + *last release*: Dec 12, 2022, *status*: 3 - Alpha, *requires*: pytest (>=3.0) @@ -3836,6 +4274,13 @@ This list contains 1118 plugins. pytest plugin to manipulate firefox + :pypi:`pytest-fixture-classes` + *last release*: Jan 20, 2023, + *status*: 4 - Beta, + *requires*: pytest + + Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers + :pypi:`pytest-fixture-config` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -3864,6 +4309,13 @@ This list contains 1118 plugins. pytest plugin to control fixture evaluation order + :pypi:`pytest-fixture-ref` + *last release*: Nov 17, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Lets users reference fixtures without name matching magic. + :pypi:`pytest-fixture-rtttg` *last release*: Feb 23, 2022, *status*: N/A, @@ -3900,7 +4352,7 @@ This list contains 1118 plugins. pytest plugin to check FLAKE8 requirements :pypi:`pytest-flake8-path` - *last release*: May 11, 2022, + *last release*: Jun 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -3914,7 +4366,7 @@ This list contains 1118 plugins. pytest plugin to check FLAKE8 requirements :pypi:`pytest-flakefinder` - *last release*: Jul 28, 2020, + *last release*: Oct 26, 2022, *status*: 4 - Beta, *requires*: pytest (>=2.7.1) @@ -3942,9 +4394,9 @@ This list contains 1118 plugins. A set of py.test fixtures to test Flask applications. :pypi:`pytest-flask-ligand` - *last release*: Aug 18, 2022, + *last release*: Apr 25, 2023, *status*: 4 - Beta, - *requires*: pytest (~=7.1) + *requires*: pytest (~=7.3) Pytest fixtures and helper functions to use for testing flask-ligand microservices. @@ -3962,6 +4414,13 @@ This list contains 1118 plugins. Run tests in transactions using pytest, Flask, and SQLalchemy. + :pypi:`pytest-flexreport` + *last release*: Apr 15, 2023, + *status*: 4 - Beta, + *requires*: pytest + + + :pypi:`pytest-fluent` *last release*: Jul 12, 2022, *status*: 4 - Beta, @@ -3983,6 +4442,13 @@ This list contains 1118 plugins. A pytest plugin that alerts user of failed test cases with screen notifications + :pypi:`pytest-forbid` + *last release*: Mar 07, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.2,<8.0.0) + + + :pypi:`pytest-forcefail` *last release*: May 15, 2018, *status*: 4 - Beta, @@ -4004,6 +4470,13 @@ This list contains 1118 plugins. A pytest plugin to shim pytest commandline options for fowards compatibility + :pypi:`pytest-frappe` + *last release*: May 03, 2023, + *status*: 4 - Beta, + *requires*: pytest>=7.0.0 + + Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications + :pypi:`pytest-freezegun` *last release*: Jul 19, 2020, *status*: 4 - Beta, @@ -4011,6 +4484,13 @@ This list contains 1118 plugins. Wrap tests with fixtures in freeze_time + :pypi:`pytest-freezer` + *last release*: Jun 17, 2023, + *status*: N/A, + *requires*: pytest>=3.6 + + Pytest plugin providing a fixture interface for spulec/freezegun + :pypi:`pytest-freeze-reqs` *last release*: Apr 29, 2021, *status*: N/A, @@ -4110,7 +4590,7 @@ This list contains 1118 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Aug 23, 2022, + *last release*: Jun 14, 2023, *status*: N/A, *requires*: N/A @@ -4145,8 +4625,8 @@ This list contains 1118 plugins. Plugin for py.test that associates tests with github issues using a marker. :pypi:`pytest-github-actions-annotate-failures` - *last release*: Jul 02, 2022, - *status*: N/A, + *last release*: May 04, 2023, + *status*: 5 - Production/Stable, *requires*: pytest (>=4.0.0) pytest plugin to annotate failed tests with a workflow command for GitHub Actions @@ -4165,6 +4645,20 @@ This list contains 1118 plugins. py.test plugin to ignore the same files as git + :pypi:`pytest-gitlabci-parallelized` + *last release*: Mar 08, 2023, + *status*: N/A, + *requires*: N/A + + Parallelize pytest across GitLab CI workers. + + :pypi:`pytest-git-selector` + *last release*: Nov 17, 2022, + *status*: N/A, + *requires*: N/A + + Utility to select tests that have had its dependencies modified (as identified by git diff) + :pypi:`pytest-glamor-allure` *last release*: Jul 22, 2022, *status*: 4 - Beta, @@ -4186,6 +4680,13 @@ This list contains 1118 plugins. Plugin for pytest that offloads expected outputs to data files + :pypi:`pytest-goldie` + *last release*: May 23, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin to support golden tests with pytest. + :pypi:`pytest-google-chat` *last release*: Mar 27, 2022, *status*: 4 - Beta, @@ -4221,6 +4722,13 @@ This list contains 1118 plugins. pytest plugin for grpc + :pypi:`pytest-grunnur` + *last release*: Feb 05, 2023, + *status*: N/A, + *requires*: N/A + + Py.Test plugin for Grunnur-based packages. + :pypi:`pytest-hammertime` *last release*: Jul 28, 2018, *status*: N/A, @@ -4228,6 +4736,13 @@ This list contains 1118 plugins. Display "🔨 " instead of "." for passed pytest tests. + :pypi:`pytest-harmony` + *last release*: Jan 17, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + Chain tests and data with pytest + :pypi:`pytest-harvest` *last release*: Jun 10, 2022, *status*: 5 - Production/Stable, @@ -4243,7 +4758,7 @@ This list contains 1118 plugins. A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. :pypi:`pytest-helm-charts` - *last release*: Aug 03, 2022, + *last release*: Mar 08, 2023, *status*: 4 - Beta, *requires*: pytest (>=7.1.2,<8.0.0) @@ -4299,9 +4814,9 @@ This list contains 1118 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Sep 09, 2022, + *last release*: Jun 16, 2023, *status*: 3 - Alpha, - *requires*: pytest (==7.1.2) + *requires*: pytest (==7.3.1) Experimental package to automatically extract test plugins for Home Assistant custom components @@ -4319,29 +4834,43 @@ This list contains 1118 plugins. Report on tests that honor constraints, and guard against regressions + :pypi:`pytest-hot-reloading` + *last release*: Jun 16, 2023, + *status*: N/A, + *requires*: N/A + + + + :pypi:`pytest-hot-test` + *last release*: Dec 10, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin that tracks test changes + :pypi:`pytest-hoverfly` - *last release*: Mar 28, 2022, + *last release*: Jan 30, 2023, *status*: N/A, *requires*: pytest (>=5.0) Simplify working with Hoverfly from pytest :pypi:`pytest-hoverfly-wrapper` - *last release*: Feb 22, 2022, + *last release*: Feb 27, 2023, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest (>=3.7.0) Integrates the Hoverfly HTTP proxy into Pytest :pypi:`pytest-hpfeeds` - *last release*: Aug 27, 2021, + *last release*: Feb 28, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.4,<7.0.0) Helpers for testing hpfeeds in your python project :pypi:`pytest-html` - *last release*: Dec 13, 2020, + *last release*: Apr 08, 2023, *status*: 5 - Production/Stable, *requires*: pytest (!=6.0.0,>=5.0) @@ -4404,12 +4933,19 @@ This list contains 1118 plugins. Fixture "http" for http requests :pypi:`pytest-httpbin` - *last release*: Mar 16, 2022, + *last release*: May 08, 2023, *status*: 5 - Production/Stable, *requires*: pytest ; extra == 'test' Easily test your HTTP library against a local copy of httpbin + :pypi:`pytest-httpdbg` + *last release*: May 09, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=7.0.0) + + A pytest plugin to record HTTP(S) requests with stack trace + :pypi:`pytest-http-mocker` *last release*: Oct 20, 2019, *status*: N/A, @@ -4425,23 +4961,30 @@ This list contains 1118 plugins. A thin wrapper of HTTPretty for pytest :pypi:`pytest-httpserver` - *last release*: Jul 29, 2022, + *last release*: May 22, 2023, *status*: 3 - Alpha, *requires*: N/A pytest-httpserver is a httpserver for pytest + :pypi:`pytest-httptesting` + *last release*: Jun 03, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + http_testing framework on top of pytest + :pypi:`pytest-httpx` - *last release*: May 24, 2022, + *last release*: Apr 12, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (<8.*,>=6.*) + *requires*: pytest (<8.0,>=6.0) Send responses to httpx. :pypi:`pytest-httpx-blockage` - *last release*: Nov 16, 2021, + *last release*: Feb 16, 2023, *status*: N/A, - *requires*: pytest (>=6.2.5) + *requires*: pytest (>=7.2.1) Disable httpx requests during a test run @@ -4469,7 +5012,7 @@ This list contains 1118 plugins. :pypi:`pytest-ibutsu` *last release*: Aug 05, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest>=7.1 A plugin to sent pytest results to an Ibutsu server @@ -4509,7 +5052,7 @@ This list contains 1118 plugins. ignore failures from flaky tests (pytest plugin) :pypi:`pytest-image-diff` - *last release*: Jun 08, 2022, + *last release*: Mar 09, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -4557,22 +5100,29 @@ This list contains 1118 plugins. Reuse pytest.ini to store env variables + :pypi:`pytest-inline` + *last release*: Feb 08, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0.0) + + A pytest plugin for writing inline tests. + :pypi:`pytest-inmanta` - *last release*: Sep 07, 2022, + *last release*: Feb 23, 2023, *status*: 5 - Production/Stable, *requires*: N/A A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: Aug 10, 2022, + *last release*: Apr 12, 2023, *status*: 5 - Production/Stable, *requires*: N/A Inmanta tests package :pypi:`pytest-inmanta-lsm` - *last release*: Sep 07, 2022, + *last release*: May 17, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -4593,16 +5143,16 @@ This list contains 1118 plugins. A simple image diff plugin for pytest :pypi:`pytest-insta` - *last release*: Feb 28, 2022, + *last release*: Nov 02, 2022, *status*: N/A, - *requires*: pytest (>=6.0.2) + *requires*: pytest (>=7.2.0,<8.0.0) A practical snapshot testing plugin for pytest :pypi:`pytest-instafail` - *last release*: Jun 14, 2020, + *last release*: Mar 31, 2023, *status*: 4 - Beta, - *requires*: pytest (>=2.9) + *requires*: pytest (>=5) pytest plugin to show failures instantly @@ -4614,16 +5164,16 @@ This list contains 1118 plugins. pytest plugin to instrument tests :pypi:`pytest-integration` - *last release*: Apr 16, 2020, + *last release*: Nov 17, 2022, *status*: N/A, *requires*: N/A Organizing pytests by integration or not :pypi:`pytest-integration-mark` - *last release*: Jul 19, 2021, + *last release*: May 22, 2023, *status*: N/A, - *requires*: pytest (>=5.2,<7.0) + *requires*: pytest (>=5.2) Automatic integration test marking and excluding plugin for pytest @@ -4641,12 +5191,19 @@ This list contains 1118 plugins. Pytest plugin for intercepting outgoing connection requests during pytest run. - :pypi:`pytest-invenio` - *last release*: Aug 09, 2022, - *status*: 5 - Production/Stable, - *requires*: pytest (<7,>=6) + :pypi:`pytest-interface-tester` + *last release*: May 09, 2023, + *status*: 4 - Beta, + *requires*: pytest - "Pytest fixtures for Invenio." + Pytest plugin for checking charm relation interface protocol compliance. + + :pypi:`pytest-invenio` + *last release*: Jun 02, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (<7.2.0,>=6) + + Pytest fixtures for Invenio. :pypi:`pytest-involve` *last release*: Feb 02, 2020, @@ -4656,7 +5213,7 @@ This list contains 1118 plugins. Run tests covering a specific file or changeset :pypi:`pytest-ipdb` - *last release*: Sep 02, 2014, + *last release*: Mar 20, 2013, *status*: 2 - Pre-Alpha, *requires*: N/A @@ -4669,15 +5226,22 @@ This list contains 1118 plugins. THIS PROJECT IS ABANDONED + :pypi:`pytest-isolate` + *last release*: Feb 20, 2023, + *status*: 4 - Beta, + *requires*: pytest + + + :pypi:`pytest-isort` - *last release*: Feb 08, 2022, + *last release*: Oct 31, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) py.test plugin to check import ordering using isort :pypi:`pytest-is-running` - *last release*: Aug 19, 2022, + *last release*: Jun 16, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -4711,6 +5275,13 @@ This list contains 1118 plugins. Run jasmine tests from your pytest test suite + :pypi:`pytest-jelastic` + *last release*: Nov 16, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + Pytest plugin defining the necessary command-line options to pass to pytests testing a Jelastic environment. + :pypi:`pytest-jest` *last release*: May 22, 2018, *status*: 4 - Beta, @@ -4718,20 +5289,41 @@ This list contains 1118 plugins. A custom jest-pytest oriented Pytest reporter + :pypi:`pytest-jinja` + *last release*: Oct 04, 2022, + *status*: 3 - Alpha, + *requires*: pytest (>=6.2.5,<7.0.0) + + A plugin to generate customizable jinja-based HTML reports in pytest + :pypi:`pytest-jira` - *last release*: Apr 07, 2022, + *last release*: Jun 12, 2023, *status*: 3 - Alpha, *requires*: N/A py.test JIRA integration plugin, using markers + :pypi:`pytest-jira-xfail` + *last release*: Jun 14, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + Plugin skips (xfail) tests if unresolved Jira issue(s) linked + :pypi:`pytest-jira-xray` - *last release*: Aug 25, 2022, + *last release*: Jun 06, 2023, *status*: 4 - Beta, *requires*: pytest pytest plugin to integrate tests with JIRA XRAY + :pypi:`pytest-job-selection` + *last release*: Jan 30, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin for load balancing test suites + :pypi:`pytest-jobserver` *last release*: May 15, 2019, *status*: 5 - Production/Stable, @@ -4754,9 +5346,9 @@ This list contains 1118 plugins. Generate JSON test reports :pypi:`pytest-json-fixtures` - *last release*: Aug 09, 2022, + *last release*: Mar 14, 2023, *status*: 4 - Beta, - *requires*: pytest (>=7.1.0) + *requires*: N/A JSON output for the --fixtures flag @@ -4774,8 +5366,29 @@ This list contains 1118 plugins. A pytest plugin to report test results as JSON files + :pypi:`pytest-jtr` + *last release*: Nov 29, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + pytest plugin supporting json test report output + + :pypi:`pytest-jupyter` + *last release*: Mar 30, 2023, + *status*: 4 - Beta, + *requires*: pytest + + A pytest plugin for testing Jupyter libraries and extensions. + + :pypi:`pytest-jupyterhub` + *last release*: Apr 25, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + A reusable JupyterHub pytest plugin + :pypi:`pytest-kafka` - *last release*: Aug 24, 2021, + *last release*: Jun 14, 2023, *status*: N/A, *requires*: pytest @@ -4788,6 +5401,20 @@ This list contains 1118 plugins. A plugin to send pytest events to Kafka + :pypi:`pytest-kasima` + *last release*: Jan 26, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2.1,<8.0.0) + + Display horizontal lines above and below the captured standard output for easy viewing. + + :pypi:`pytest-keep-together` + *last release*: Dec 07, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest + + Pytest plugin to customize test ordering by running all 'related' tests together + :pypi:`pytest-kexi` *last release*: Apr 29, 2022, *status*: N/A, @@ -4796,7 +5423,7 @@ This list contains 1118 plugins. :pypi:`pytest-kind` - *last release*: Sep 08, 2022, + *last release*: Nov 30, 2022, *status*: 5 - Production/Stable, *requires*: N/A @@ -4823,6 +5450,13 @@ This list contains 1118 plugins. Run Konira DSL tests with py.test + :pypi:`pytest-koopmans` + *last release*: Nov 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A plugin for testing the koopmans package + :pypi:`pytest-krtech-common` *last release*: Nov 28, 2016, *status*: 4 - Beta, @@ -4830,6 +5464,13 @@ This list contains 1118 plugins. pytest krtech common library + :pypi:`pytest-kubernetes` + *last release*: May 17, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + + :pypi:`pytest-kwparametrize` *last release*: Jan 22, 2021, *status*: N/A, @@ -4851,8 +5492,22 @@ This list contains 1118 plugins. + :pypi:`pytest-langchain` + *last release*: Feb 26, 2023, + *status*: N/A, + *requires*: pytest + + Pytest-style test runner for langchain agents + + :pypi:`pytest-lark` + *last release*: Nov 20, 2022, + *status*: N/A, + *requires*: N/A + + A package for enhancing pytest + :pypi:`pytest-launchable` - *last release*: Jun 14, 2022, + *last release*: Apr 05, 2023, *status*: N/A, *requires*: pytest (>=4.2.0) @@ -4872,6 +5527,13 @@ This list contains 1118 plugins. It helps to use fixtures in pytest.mark.parametrize + :pypi:`pytest-lazy-fixtures` + *last release*: May 28, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.1,<8.0.0) + + Allows you to use fixtures in @pytest.mark.parametrize. + :pypi:`pytest-ldap` *last release*: Aug 18, 2020, *status*: N/A, @@ -4880,11 +5542,11 @@ This list contains 1118 plugins. python-ldap fixtures for pytest :pypi:`pytest-leak-finder` - *last release*: Apr 18, 2022, + *last release*: Feb 15, 2023, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) - Find the previous test that makes another to fail + Find the test that's leaking before the one that fails :pypi:`pytest-leaks` *last release*: Nov 27, 2019, @@ -4943,9 +5605,9 @@ This list contains 1118 plugins. Profile code executed by pytest :pypi:`pytest-line-profiler-apn` - *last release*: Aug 14, 2022, - *status*: 4 - Beta, - *requires*: N/A + *last release*: Dec 05, 2022, + *status*: N/A, + *requires*: pytest (>=3.5.0) Profile code executed by pytest @@ -4977,24 +5639,31 @@ This list contains 1118 plugins. Live results for pytest + :pypi:`pytest-local-badge` + *last release*: Jan 15, 2023, + *status*: N/A, + *requires*: pytest (>=6.1.0) + + Generate local badges (shields) reporting your test suite status. + :pypi:`pytest-localftpserver` - *last release*: Aug 25, 2021, + *last release*: Oct 04, 2022, *status*: 5 - Production/Stable, *requires*: pytest A PyTest plugin which provides an FTP fixture for your tests :pypi:`pytest-localserver` - *last release*: Aug 30, 2022, + *last release*: Jan 30, 2023, *status*: 4 - Beta, *requires*: N/A pytest plugin to test server connections locally. :pypi:`pytest-localstack` - *last release*: Aug 22, 2019, + *last release*: Jun 07, 2023, *status*: 4 - Beta, - *requires*: pytest (>=3.3.0) + *requires*: pytest (>=6.0.0,<7.0.0) Pytest plugin for AWS integration tests @@ -5054,6 +5723,20 @@ This list contains 1118 plugins. Configures logging and allows tweaking the log level with a py.test flag + :pypi:`pytest-logging-end-to-end-test-tool` + *last release*: Sep 23, 2022, + *status*: N/A, + *requires*: pytest (>=7.1.2,<8.0.0) + + + + :pypi:`pytest-logikal` + *last release*: Jun 04, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (==7.3.1) + + Common testing environment + :pypi:`pytest-log-report` *last release*: Dec 26, 2019, *status*: N/A, @@ -5076,16 +5759,16 @@ This list contains 1118 plugins. pytest plugin for looping tests :pypi:`pytest-lsp` - *last release*: Jul 18, 2022, + *last release*: May 19, 2023, *status*: 3 - Alpha, *requires*: pytest - Pytest plugin for end-to-end testing of language servers + pytest plugin for end-to-end testing of language servers :pypi:`pytest-manual-marker` *last release*: Aug 04, 2022, *status*: 3 - Alpha, - *requires*: N/A + *requires*: pytest>=7 pytest marker for marking manual tests @@ -5104,7 +5787,7 @@ This list contains 1118 plugins. Test your markdown docs with pytest :pypi:`pytest-markdown-docs` - *last release*: Sep 02, 2022, + *last release*: Mar 09, 2023, *status*: N/A, *requires*: N/A @@ -5173,6 +5856,13 @@ This list contains 1118 plugins. Provide tools for generating tests from combinations of fixtures. + :pypi:`pytest-maybe-context` + *last release*: Apr 16, 2023, + *status*: N/A, + *requires*: pytest (>=7,<8) + + Simplify tests with warning and exception cases. + :pypi:`pytest-maybe-raises` *last release*: May 27, 2022, *status*: N/A, @@ -5195,12 +5885,19 @@ This list contains 1118 plugins. Plugin for generating Markdown reports for pytest results :pypi:`pytest-md-report` - *last release*: Aug 06, 2022, + *last release*: May 28, 2023, *status*: 4 - Beta, *requires*: pytest (!=6.0.0,<8,>=3.3.2) A pytest plugin to make a test results report with Markdown table format. + :pypi:`pytest-memlog` + *last release*: May 03, 2023, + *status*: N/A, + *requires*: pytest (>=7.3.0,<8.0.0) + + Log memory usage during tests + :pypi:`pytest-memprof` *last release*: Mar 29, 2019, *status*: 4 - Beta, @@ -5209,9 +5906,9 @@ This list contains 1118 plugins. Estimates memory consumption of test functions :pypi:`pytest-memray` - *last release*: Aug 21, 2022, + *last release*: Jun 06, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest>=7.2 A simple plugin to use with pytest @@ -5244,16 +5941,16 @@ This list contains 1118 plugins. Pytest plugin for sending report message of marked tests execution :pypi:`pytest-messenger` - *last release*: Feb 07, 2022, + *last release*: Nov 24, 2022, *status*: 5 - Production/Stable, *requires*: N/A Pytest to Slack reporting plugin :pypi:`pytest-metadata` - *last release*: Jul 15, 2022, + *last release*: May 27, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0,<8.0.0) + *requires*: pytest>=7.0.0 pytest plugin for test session metadata @@ -5264,6 +5961,13 @@ This list contains 1118 plugins. Custom metrics report for pytest + :pypi:`pytest-mh` + *last release*: Jun 08, 2023, + *status*: N/A, + *requires*: pytest + + Pytest multihost plugin + :pypi:`pytest-mimesis` *last release*: Mar 21, 2020, *status*: 5 - Production/Stable, @@ -5278,6 +5982,13 @@ This list contains 1118 plugins. A pytest plugin for running tests against Minecraft releases + :pypi:`pytest-mini` + *last release*: Feb 06, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + A plugin to test mp + :pypi:`pytest-missing-fixtures` *last release*: Oct 14, 2020, *status*: 4 - Beta, @@ -5300,7 +6011,7 @@ This list contains 1118 plugins. pytest plugin to display test execution output like a mochajs :pypi:`pytest-mock` - *last release*: Jul 05, 2022, + *last release*: Jun 15, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=5.0) @@ -5342,7 +6053,7 @@ This list contains 1118 plugins. An in-memory mock of a Redis server that runs in a separate thread. This is to be used for unit-tests that require a Redis database. :pypi:`pytest-mock-resources` - *last release*: Aug 24, 2022, + *last release*: Jun 09, 2023, *status*: N/A, *requires*: pytest (>=1.0) @@ -5362,6 +6073,13 @@ This list contains 1118 plugins. A set of fixtures to test your requests to HTTP/UDP servers + :pypi:`pytest-mocktcp` + *last release*: Oct 11, 2022, + *status*: N/A, + *requires*: pytest + + A pytest plugin for testing TCP clients + :pypi:`pytest-modified-env` *last release*: Jan 29, 2022, *status*: 4 - Beta, @@ -5398,14 +6116,14 @@ This list contains 1118 plugins. MongoDB process and client fixtures plugin for Pytest. :pypi:`pytest-mongodb` - *last release*: Dec 07, 2019, + *last release*: May 16, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=2.5.2) + *requires*: N/A pytest plugin for MongoDB fixtures :pypi:`pytest-monitor` - *last release*: May 18, 2022, + *last release*: Oct 22, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -5453,6 +6171,13 @@ This list contains 1118 plugins. pytest plugin to collect information from tests + :pypi:`pytest-mpiexec` + *last release*: Apr 13, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + pytest plugin for running individual tests with mpiexec + :pypi:`pytest-mpl` *last release*: Jul 23, 2022, *status*: 4 - Beta, @@ -5461,18 +6186,18 @@ This list contains 1118 plugins. pytest plugin to help with testing figures output from Matplotlib :pypi:`pytest-mproc` - *last release*: Sep 04, 2022, + *last release*: Nov 15, 2022, *status*: 4 - Beta, *requires*: pytest (>=6) low-startup-overhead, scalable, distributed-testing pytest plugin - :pypi:`pytest-multi-check` - *last release*: Jul 12, 2022, - *status*: N/A, - *requires*: pytest + :pypi:`pytest-mqtt` + *last release*: Mar 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8) ; extra == 'test' - Pytest-плагин, реализует возможность мульти проверок и мягких проверок + pytest-mqtt supports testing systems based on MQTT :pypi:`pytest-multihost` *last release*: Apr 07, 2020, @@ -5482,21 +6207,21 @@ This list contains 1118 plugins. Utility for writing multi-host tests for pytest :pypi:`pytest-multilog` - *last release*: Jun 10, 2021, + *last release*: Jan 17, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest Multi-process logs handling and other helpers for pytest :pypi:`pytest-multithreading` - *last release*: Aug 12, 2021, + *last release*: Dec 07, 2022, *status*: N/A, - *requires*: pytest (>=3.6) + *requires*: N/A a pytest plugin for th and concurrent testing :pypi:`pytest-multithreading-allure` - *last release*: Mar 22, 2022, + *last release*: Nov 25, 2022, *status*: N/A, *requires*: N/A @@ -5510,7 +6235,7 @@ This list contains 1118 plugins. Add the mutation testing feature to pytest :pypi:`pytest-mypy` - *last release*: Feb 07, 2022, + *last release*: Dec 18, 2022, *status*: 4 - Beta, *requires*: pytest (>=6.2) ; python_version >= "3.10" @@ -5524,28 +6249,28 @@ This list contains 1118 plugins. Mypy static type checker plugin for Pytest :pypi:`pytest-mypy-plugins` - *last release*: Aug 15, 2022, + *last release*: May 05, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.0.0) + *requires*: pytest (>=6.2.0) pytest plugin for writing tests for mypy plugins :pypi:`pytest-mypy-plugins-shim` *last release*: Apr 12, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.0.0 Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. :pypi:`pytest-mypy-testing` - *last release*: May 30, 2022, + *last release*: Feb 25, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest>=7,<8 Pytest plugin to check mypy output. :pypi:`pytest-mysql` - *last release*: Feb 15, 2022, + *last release*: Mar 27, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6.2) @@ -5565,6 +6290,13 @@ This list contains 1118 plugins. pytest-neo is a plugin for pytest that shows tests like screen of Matrix. + :pypi:`pytest-netdut` + *last release*: Jun 05, 2023, + *status*: N/A, + *requires*: pytest (>=3.5.0) + + "Automated software testing for switches using pytest" + :pypi:`pytest-network` *last release*: May 07, 2020, *status*: N/A, @@ -5615,7 +6347,7 @@ This list contains 1118 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: Aug 23, 2022, + *last release*: Jun 07, 2023, *status*: N/A, *requires*: pytest (==6.2.5) @@ -5699,12 +6431,19 @@ This list contains 1118 plugins. A PyTest Reporter to send test runs to Notion.so :pypi:`pytest-nunit` - *last release*: Jul 26, 2022, - *status*: 4 - Beta, - *requires*: N/A + *last release*: Oct 20, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest (>=4.6.0) A pytest plugin for generating NUnit3 test result XML output + :pypi:`pytest-oar` + *last release*: May 02, 2023, + *status*: N/A, + *requires*: pytest>=6.0.1 + + PyTest plugin for the OAR testing framework + :pypi:`pytest-object-getter` *last release*: Jul 31, 2022, *status*: 5 - Production/Stable, @@ -5720,9 +6459,9 @@ This list contains 1118 plugins. pytest results data-base and HTML reporter :pypi:`pytest-odoo` - *last release*: Feb 08, 2022, + *last release*: Nov 17, 2022, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (>=7.2.0) py.test plugin to run Odoo tests @@ -5740,6 +6479,20 @@ This list contains 1118 plugins. pytest plugin to test OpenERP modules + :pypi:`pytest-offline` + *last release*: Mar 09, 2023, + *status*: 1 - Planning, + *requires*: pytest (>=7.0.0,<8.0.0) + + + + :pypi:`pytest-ogsm-plugin` + *last release*: May 16, 2023, + *status*: N/A, + *requires*: N/A + + 针对特定项目定制化插件,优化了pytest报告展示方式,并添加了项目所需特定参数 + :pypi:`pytest-ok` *last release*: Apr 01, 2019, *status*: 4 - Beta, @@ -5769,7 +6522,7 @@ This list contains 1118 plugins. Pytest plugin for detecting inadvertent open file handles :pypi:`pytest-opentelemetry` - *last release*: Sep 07, 2022, + *last release*: Mar 15, 2023, *status*: N/A, *requires*: pytest @@ -5783,7 +6536,7 @@ This list contains 1118 plugins. pytest plugin for publish results to opentmi :pypi:`pytest-operator` - *last release*: Aug 17, 2022, + *last release*: Sep 28, 2022, *status*: N/A, *requires*: pytest @@ -5811,7 +6564,7 @@ This list contains 1118 plugins. A pytest plugin for orchestrating tests :pypi:`pytest-order` - *last release*: Jan 09, 2022, + *last release*: Mar 10, 2023, *status*: 4 - Beta, *requires*: pytest (>=5.0) ; python_version < "3.10" @@ -5824,6 +6577,13 @@ This list contains 1118 plugins. pytest plugin to run your tests in a specific order + :pypi:`pytest-order-modify` + *last release*: Nov 04, 2022, + *status*: N/A, + *requires*: N/A + + 新增run_marker 来自定义用例的执行顺序 + :pypi:`pytest-osxnotify` *last release*: May 15, 2015, *status*: N/A, @@ -5832,12 +6592,19 @@ This list contains 1118 plugins. OS X notifications for py.test results. :pypi:`pytest-otel` - *last release*: May 26, 2022, + *last release*: Jan 18, 2023, *status*: N/A, *requires*: N/A pytest-otel report OpenTelemetry traces about test executed + :pypi:`pytest-override-env-var` + *last release*: Feb 25, 2023, + *status*: N/A, + *requires*: N/A + + Pytest mark to override a value of an environment variable. + :pypi:`pytest-owner` *last release*: Apr 25, 2022, *status*: N/A, @@ -5873,6 +6640,13 @@ This list contains 1118 plugins. a pytest plugin for parallel and concurrent testing + :pypi:`pytest-parallelize-tests` + *last release*: Jan 27, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin that parallelizes test execution across multiple hosts + :pypi:`pytest-param` *last release*: Sep 11, 2016, *status*: 4 - Beta, @@ -5902,11 +6676,18 @@ This list contains 1118 plugins. A more user-friendly way to write parametrized tests. :pypi:`pytest-parametrized` - *last release*: Oct 19, 2020, + *last release*: Sep 13, 2022, *status*: 5 - Production/Stable, *requires*: pytest - Pytest plugin for parametrizing tests with default iterables. + Pytest decorator for parametrizing tests with default iterables. + + :pypi:`pytest-parametrize-suite` + *last release*: Jan 19, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest + + A simple pytest extension for creating a named test suite. :pypi:`pytest-parawtf` *last release*: Dec 03, 2018, @@ -5936,6 +6717,13 @@ This list contains 1118 plugins. Allow setting the path to a paste config file + :pypi:`pytest-patch` + *last release*: Apr 29, 2023, + *status*: 3 - Alpha, + *requires*: pytest (>=7.0.0) + + An automagic \`patch\` fixture that can patch objects directly or by name. + :pypi:`pytest-patches` *last release*: Aug 30, 2021, *status*: 4 - Beta, @@ -5979,11 +6767,11 @@ This list contains 1118 plugins. Change the exit code of pytest test sessions when a required percent of tests pass. :pypi:`pytest-perf` - *last release*: Jun 23, 2022, + *last release*: Jun 02, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6) ; extra == 'testing' - pytest-perf + Run performance tests against the mainline code. :pypi:`pytest-performance` *last release*: Sep 11, 2020, @@ -5993,14 +6781,14 @@ This list contains 1118 plugins. A simple plugin to ensure the execution of critical sections of code has not been impacted :pypi:`pytest-persistence` - *last release*: Jun 27, 2022, + *last release*: Jun 14, 2023, *status*: N/A, *requires*: N/A Pytest tool for persistent objects :pypi:`pytest-pg` - *last release*: Jun 07, 2022, + *last release*: May 04, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6.0.0) @@ -6048,6 +6836,13 @@ This list contains 1118 plugins. Slice in your test base thanks to powerful markers. + :pypi:`pytest-pingguo-pytest-plugin` + *last release*: Oct 26, 2022, + *status*: 4 - Beta, + *requires*: N/A + + pingguo test + :pypi:`pytest-pings` *last release*: Jun 29, 2019, *status*: 3 - Alpha, @@ -6098,9 +6893,9 @@ This list contains 1118 plugins. Pytest plugin for reading playbooks. :pypi:`pytest-playwright` - *last release*: Mar 16, 2022, + *last release*: Apr 24, 2023, *status*: N/A, - *requires*: pytest + *requires*: pytest (<8.0.0,>=6.2.4) A pytest wrapper with fixtures for Playwright to automate web browsers @@ -6125,6 +6920,13 @@ This list contains 1118 plugins. A pytest fixture for visual testing with Playwright + :pypi:`pytest-plone` + *last release*: Jan 05, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + Pytest plugin to test Plone addons + :pypi:`pytest-plt` *last release*: Aug 17, 2020, *status*: 5 - Production/Stable, @@ -6140,9 +6942,9 @@ This list contains 1118 plugins. A plugin to help developing and testing other plugins :pypi:`pytest-plus` - *last release*: Mar 19, 2020, + *last release*: Dec 24, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.50) + *requires*: pytest (>=6.0.1) PyTest Plus Plugin :: extends pytest functionality @@ -6154,12 +6956,19 @@ This list contains 1118 plugins. :pypi:`pytest-pointers` - *last release*: Oct 14, 2021, + *last release*: Dec 26, 2022, *status*: N/A, *requires*: N/A Pytest plugin to define functions you test with special marks for better navigation and reports + :pypi:`pytest-pokie` + *last release*: May 22, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pokie plugin for pytest + :pypi:`pytest-polarion-cfme` *last release*: Nov 13, 2017, *status*: 3 - Alpha, @@ -6203,7 +7012,7 @@ This list contains 1118 plugins. Visualize your failed tests with poo :pypi:`pytest-pop` - *last release*: Aug 18, 2022, + *last release*: May 09, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -6224,12 +7033,19 @@ This list contains 1118 plugins. Run PostgreSQL in Docker container in Pytest. :pypi:`pytest-postgresql` - *last release*: Mar 11, 2022, + *last release*: May 20, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2.0) + *requires*: pytest (>=6.2) Postgresql fixtures and fixture factories for Pytest. + :pypi:`pytest-pot` + *last release*: Nov 20, 2022, + *status*: N/A, + *requires*: N/A + + A package for enhancing pytest + :pypi:`pytest-power` *last release*: Dec 31, 2020, *status*: N/A, @@ -6244,6 +7060,13 @@ This list contains 1118 plugins. A Pytest plugin to drop duplicated tests during collection, but will prefer keeping nested packages. + :pypi:`pytest-pretty` + *last release*: Apr 05, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest>=7 + + pytest plugin for printing summary data as I want it + :pypi:`pytest-pretty-terminal` *last release*: Jan 31, 2022, *status*: N/A, @@ -6259,9 +7082,9 @@ This list contains 1118 plugins. Minitest-style test colors :pypi:`pytest-print` - *last release*: Dec 28, 2021, + *last release*: Jun 16, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6) + *requires*: pytest>=7.3.2 pytest-print adds the printer fixture you can use to print messages to the user (directly to the pytest runner, not stdout) @@ -6294,7 +7117,7 @@ This list contains 1118 plugins. Report test pass / failures to a Prometheus PushGateway :pypi:`pytest-prometheus-pushgateway` - *last release*: Sep 01, 2022, + *last release*: Sep 27, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -6335,6 +7158,13 @@ This list contains 1118 plugins. Pytest PuDB debugger integration + :pypi:`pytest-pumpkin-spice` + *last release*: Sep 18, 2022, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin that makes your test reporting pumpkin-spiced + :pypi:`pytest-purkinje` *last release*: Oct 28, 2017, *status*: 2 - Pre-Alpha, @@ -6342,6 +7172,20 @@ This list contains 1118 plugins. py.test plugin for purkinje test runner + :pypi:`pytest-pusher` + *last release*: Jan 06, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=3.6) + + pytest plugin for push report to minio + + :pypi:`pytest-py125` + *last release*: Dec 03, 2022, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-pycharm` *last release*: Aug 13, 2020, *status*: 5 - Production/Stable, @@ -6350,7 +7194,7 @@ This list contains 1118 plugins. Plugin for py.test to enter PyCharm debugger on uncaught exceptions :pypi:`pytest-pycodestyle` - *last release*: Mar 13, 2022, + *last release*: Oct 28, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -6364,7 +7208,7 @@ This list contains 1118 plugins. py.test plugin to connect to a remote debug server with PyDev or PyCharm. :pypi:`pytest-pydocstyle` - *last release*: Mar 13, 2022, + *last release*: Jan 05, 2023, *status*: 3 - Alpha, *requires*: N/A @@ -6385,7 +7229,7 @@ This list contains 1118 plugins. Record PyMySQL queries and mock with the stored data. :pypi:`pytest-pyodide` - *last release*: Sep 08, 2022, + *last release*: Jun 11, 2023, *status*: N/A, *requires*: pytest @@ -6420,7 +7264,7 @@ This list contains 1118 plugins. Pytest fixture "q" for pyq :pypi:`pytest-pyramid` - *last release*: Oct 15, 2021, + *last release*: Dec 13, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -6433,13 +7277,34 @@ This list contains 1118 plugins. Pyramid server fixture for py.test + :pypi:`pytest-pyreport` + *last release*: May 08, 2023, + *status*: N/A, + *requires*: pytest (>=7.3.1) + + PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report + :pypi:`pytest-pyright` - *last release*: Aug 16, 2021, + *last release*: Nov 20, 2022, *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=7.0.0) Pytest plugin for type checking code with Pyright + :pypi:`pytest-pyspec` + *last release*: Mar 12, 2023, + *status*: 5 - Production/Stable, + *requires*: pytest (>=7.2.1,<8.0.0) + + A plugin that transforms the pytest output into a result similar to the RSpec. It enables the use of docstrings to display results and also enables the use of the prefixes "describe", "with" and "it". + + :pypi:`pytest-pystack` + *last release*: May 07, 2023, + *status*: N/A, + *requires*: pytest (>=3.5.0) + + Plugin to run pystack after a timeout for a test suite. + :pypi:`pytest-pytestrail` *last release*: Aug 27, 2020, *status*: 4 - Beta, @@ -6461,6 +7326,20 @@ This list contains 1118 plugins. pytest plugin for a better developer experience when working with the PyTorch test suite + :pypi:`pytest-pyvista` + *last release*: Mar 19, 2023, + *status*: 4 - Beta, + *requires*: pytest>=3.5.0 + + Pytest-pyvista package + + :pypi:`pytest-qaseio` + *last release*: May 11, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.2.2,<8.0.0) + + Pytest plugin for Qase.io integration + :pypi:`pytest-qasync` *last release*: Jul 12, 2021, *status*: 4 - Beta, @@ -6469,16 +7348,16 @@ This list contains 1118 plugins. Pytest support for qasync. :pypi:`pytest-qatouch` - *last release*: Jun 26, 2021, + *last release*: Feb 14, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.0) Pytest plugin for uploading test results to your QA Touch Testrun. :pypi:`pytest-qgis` - *last release*: Jun 26, 2022, + *last release*: Jun 09, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2.3) + *requires*: pytest (>=6.2.5) A pytest plugin for testing QGIS python plugins @@ -6497,7 +7376,7 @@ This list contains 1118 plugins. pytest plugin to generate test result QR codes :pypi:`pytest-qt` - *last release*: Jun 23, 2022, + *last release*: Oct 25, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0.0) @@ -6518,16 +7397,16 @@ This list contains 1118 plugins. A plugin for pytest to manage expected test failures :pypi:`pytest-quickcheck` - *last release*: Nov 15, 2020, + *last release*: Nov 05, 2022, *status*: 4 - Beta, - *requires*: pytest (<6.0.0,>=4.0) + *requires*: pytest (>=4.0) pytest plugin to generate random data inspired by QuickCheck :pypi:`pytest-rabbitmq` - *last release*: Feb 11, 2022, + *last release*: Jun 16, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0) + *requires*: pytest (>=6.2) RabbitMQ process and client fixtures for pytest @@ -6609,7 +7488,7 @@ This list contains 1118 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-random-order` - *last release*: Nov 30, 2018, + *last release*: Dec 03, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.0.0) @@ -6623,14 +7502,21 @@ This list contains 1118 plugins. Test your README.md file :pypi:`pytest-reana` - *last release*: May 16, 2022, + *last release*: Dec 13, 2022, *status*: 3 - Alpha, *requires*: N/A Pytest fixtures for REANA. + :pypi:`pytest-recorder` + *last release*: Mar 30, 2023, + *status*: N/A, + *requires*: N/A + + Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. + :pypi:`pytest-recording` - *last release*: Jun 20, 2022, + *last release*: Feb 16, 2023, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -6644,9 +7530,9 @@ This list contains 1118 plugins. Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal :pypi:`pytest-redis` - *last release*: Feb 10, 2022, + *last release*: Apr 19, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2.0) + *requires*: pytest (>=6.2) Redis fixtures and fixture factories for Pytest. @@ -6678,6 +7564,13 @@ This list contains 1118 plugins. Conveniently run pytest with a dot-formatted test reference. + :pypi:`pytest-regex` + *last release*: May 29, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Select pytest tests with regular expressions + :pypi:`pytest-regex-dependency` *last release*: Jun 12, 2022, *status*: N/A, @@ -6686,9 +7579,9 @@ This list contains 1118 plugins. Management of Pytest dependencies via regex patterns :pypi:`pytest-regressions` - *last release*: Jan 19, 2022, + *last release*: Jan 13, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.5.0) + *requires*: pytest (>=6.2.0) Easy to use fixtures to write regression tests. @@ -6707,9 +7600,9 @@ This list contains 1118 plugins. a pytest plugin that sorts tests using "before" and "after" markers :pypi:`pytest-relaxed` - *last release*: Jun 14, 2019, + *last release*: May 23, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (<5,>=3) + *requires*: pytest (>=7) Relaxed test discovery/organization for pytest @@ -6721,15 +7614,15 @@ This list contains 1118 plugins. Pytest plugin to create a temporary directory with remote files :pypi:`pytest-remotedata` - *last release*: Dec 21, 2021, + *last release*: Dec 12, 2022, *status*: 3 - Alpha, *requires*: pytest (>=4.6) Pytest plugin for controlling remote data access. :pypi:`pytest-remote-response` - *last release*: Jun 05, 2022, - *status*: 4 - Beta, + *last release*: Apr 26, 2023, + *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) Pytest plugin for capturing and mocking connection requests. @@ -6763,7 +7656,7 @@ This list contains 1118 plugins. Saves previous test runs and allow re-execute previous pytest runs to reproduce crashes or flaky tests :pypi:`pytest-repo-health` - *last release*: Dec 16, 2021, + *last release*: Apr 17, 2023, *status*: 3 - Alpha, *requires*: pytest @@ -6784,12 +7677,19 @@ This list contains 1118 plugins. Generate Pytest reports with templates :pypi:`pytest-reporter-html1` - *last release*: Jun 08, 2021, + *last release*: Jun 05, 2023, *status*: 4 - Beta, *requires*: N/A A basic HTML report template for Pytest + :pypi:`pytest-reporter-html-dots` + *last release*: Jan 22, 2023, + *status*: N/A, + *requires*: N/A + + A basic HTML report for pytest using Jinja2 template engine. + :pypi:`pytest-reportinfra` *last release*: Aug 11, 2019, *status*: 3 - Alpha, @@ -6805,9 +7705,9 @@ This list contains 1118 plugins. A plugin to report summarized results in a table format :pypi:`pytest-reportlog` - *last release*: Dec 11, 2020, + *last release*: May 22, 2023, *status*: 3 - Alpha, - *requires*: pytest (>=5.2) + *requires*: pytest Replacement for the --resultlog option, focused in simplicity and extensibility @@ -6826,12 +7726,19 @@ This list contains 1118 plugins. pytest plugin for adding tests' parameters to junit report :pypi:`pytest-reportportal` - *last release*: Jun 24, 2022, + *last release*: Jun 08, 2023, *status*: N/A, *requires*: pytest (>=3.8.0) Agent for Reporting results of tests to the Report Portal + :pypi:`pytest-reports` + *last release*: Jun 07, 2023, + *status*: N/A, + *requires*: N/A + + An interesting python package + :pypi:`pytest-reqs` *last release*: May 12, 2019, *status*: N/A, @@ -6868,7 +7775,7 @@ This list contains 1118 plugins. A pytest plugin to elegantly skip tests with optional requirements :pypi:`pytest-reraise` - *last release*: Jun 17, 2021, + *last release*: Sep 20, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.6) @@ -6882,7 +7789,7 @@ This list contains 1118 plugins. Re-run only changed files in specified branch :pypi:`pytest-rerunfailures` - *last release*: Sep 17, 2021, + *last release*: Mar 09, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=5.3) @@ -6895,12 +7802,19 @@ This list contains 1118 plugins. pytest plugin to re-run tests to eliminate flaky failures - :pypi:`pytest-resilient-circuits` - *last release*: Aug 12, 2022, - *status*: N/A, - *requires*: N/A + :pypi:`pytest-reserial` + *last release*: Apr 26, 2023, + *status*: 4 - Beta, + *requires*: pytest - Resilient Circuits fixtures for PyTest. + Pytest fixture for recording and replaying serial port traffic. + + :pypi:`pytest-resilient-circuits` + *last release*: Jun 01, 2023, + *status*: N/A, + *requires*: pytest (~=4.6) ; python_version == "2.7" + + Resilient Circuits fixtures for PyTest :pypi:`pytest-resource` *last release*: Nov 14, 2018, @@ -6916,6 +7830,13 @@ This list contains 1118 plugins. Provides path for uniform access to test resources in isolated directory + :pypi:`pytest-resource-usage` + *last release*: Nov 06, 2022, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.0.0 + + Pytest plugin for reporting running time and peak memory usage + :pypi:`pytest-responsemock` *last release*: Mar 10, 2022, *status*: 5 - Production/Stable, @@ -6924,7 +7845,7 @@ This list contains 1118 plugins. Simplified requests calls mocking for pytest :pypi:`pytest-responses` - *last release*: Apr 26, 2021, + *last release*: Oct 11, 2022, *status*: N/A, *requires*: pytest (>=2.5) @@ -6938,12 +7859,33 @@ This list contains 1118 plugins. :pypi:`pytest-restrict` - *last release*: May 11, 2022, + *last release*: Jun 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest Pytest plugin to restrict the test types allowed + :pypi:`pytest-result-log` + *last release*: Apr 17, 2023, + *status*: N/A, + *requires*: pytest>=7.2.0 + + Write the execution result of the case to the log + + :pypi:`pytest-result-sender` + *last release*: Apr 20, 2023, + *status*: N/A, + *requires*: pytest>=7.3.1 + + + + :pypi:`pytest-resume` + *last release*: Apr 22, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0) + + A Pytest plugin to resuming from the last run test + :pypi:`pytest-rethinkdb` *last release*: Jul 24, 2016, *status*: 4 - Beta, @@ -6958,8 +7900,22 @@ This list contains 1118 plugins. Adds the ability to retry flaky tests in CI environments + :pypi:`pytest-retry-class` + *last release*: Mar 25, 2023, + *status*: N/A, + *requires*: pytest (>=5.3) + + A pytest plugin to rerun entire class on failure + + :pypi:`pytest-reusable-testcases` + *last release*: Apr 28, 2023, + *status*: N/A, + *requires*: N/A + + + :pypi:`pytest-reverse` - *last release*: May 11, 2022, + *last release*: Jun 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -6979,6 +7935,13 @@ This list contains 1118 plugins. A pytest plugin using Rich for beautiful test result formatting. + :pypi:`pytest-richtrace` + *last release*: Nov 05, 2022, + *status*: N/A, + *requires*: pytest (>=7.2.0,<8.0.0) + + + :pypi:`pytest-ringo` *last release*: Sep 27, 2017, *status*: 3 - Alpha, @@ -7001,7 +7964,7 @@ This list contains 1118 plugins. Fixtures for seeding tests and making randomness reproducible :pypi:`pytest-roast` - *last release*: Jul 29, 2021, + *last release*: Nov 09, 2022, *status*: 5 - Production/Stable, *requires*: pytest @@ -7029,7 +7992,7 @@ This list contains 1118 plugins. Extend py.test for RPC OpenStack testing. :pypi:`pytest-rst` - *last release*: Aug 02, 2022, + *last release*: Jan 26, 2023, *status*: N/A, *requires*: N/A @@ -7049,6 +8012,13 @@ This list contains 1118 plugins. Coverage-based regression test selection (RTS) plugin for pytest + :pypi:`pytest-ruff` + *last release*: Jun 08, 2023, + *status*: 4 - Beta, + *requires*: N/A + + pytest plugin to check ruff requirements. + :pypi:`pytest-run-changed` *last release*: Apr 02, 2021, *status*: 3 - Alpha, @@ -7071,21 +8041,42 @@ This list contains 1118 plugins. Invoke py.test as distutils command with dependency resolution :pypi:`pytest-run-subprocess` - *last release*: Jul 03, 2022, + *last release*: Nov 12, 2022, *status*: 5 - Production/Stable, *requires*: pytest Pytest Plugin for running and testing subprocesses. + :pypi:`pytest-runtime-types` + *last release*: Feb 09, 2023, + *status*: N/A, + *requires*: pytest + + Checks type annotations on runtime while running tests. + :pypi:`pytest-runtime-xfail` *last release*: Aug 26, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=5.0.0 Call runtime_xfail() to mark running test as xfail. + :pypi:`pytest-runtime-yoyo` + *last release*: Jun 12, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + run case mark timeout + + :pypi:`pytest-ry-demo1` + *last release*: Mar 26, 2023, + *status*: N/A, + *requires*: N/A + + 测试 + :pypi:`pytest-saccharin` - *last release*: May 10, 2022, + *last release*: Oct 31, 2022, *status*: 3 - Alpha, *requires*: N/A @@ -7106,7 +8097,7 @@ This list contains 1118 plugins. A Pytest plugin that builds and creates docker containers :pypi:`pytest-salt-factories` - *last release*: Aug 25, 2022, + *last release*: Dec 15, 2022, *status*: 4 - Beta, *requires*: pytest (>=6.0.0) @@ -7148,7 +8139,7 @@ This list contains 1118 plugins. :pypi:`pytest-sbase` - *last release*: Sep 09, 2022, + *last release*: Jun 13, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -7161,6 +8152,13 @@ This list contains 1118 plugins. pytest plugin for test scenarios + :pypi:`pytest-schedule` + *last release*: Jan 07, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + The job of test scheduling for humans. + :pypi:`pytest-schema` *last release*: Mar 14, 2022, *status*: 5 - Production/Stable, @@ -7183,14 +8181,14 @@ This list contains 1118 plugins. A pytest plugin which allows to (de-)select tests from a file. :pypi:`pytest-selenium` - *last release*: Mar 28, 2022, + *last release*: May 28, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.0.0,<7.0.0) + *requires*: pytest>=6.0.0 pytest plugin for Selenium :pypi:`pytest-seleniumbase` - *last release*: Sep 09, 2022, + *last release*: Jun 13, 2023, *status*: 5 - Production/Stable, *requires*: N/A @@ -7218,12 +8216,19 @@ This list contains 1118 plugins. Send pytest execution result email :pypi:`pytest-sentry` - *last release*: Apr 21, 2021, + *last release*: Jan 05, 2023, *status*: N/A, - *requires*: pytest + *requires*: N/A A pytest plugin to send testrun information to Sentry.io + :pypi:`pytest-sequence-markers` + *last release*: May 23, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Pytest plugin for sequencing markers for execution of tests + :pypi:`pytest-server-fixtures` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -7239,9 +8244,9 @@ This list contains 1118 plugins. Automatically mocks resources from serverless.yml in pytest using moto. :pypi:`pytest-servers` - *last release*: Aug 22, 2022, + *last release*: Apr 15, 2023, *status*: 3 - Alpha, - *requires*: pytest (==7.1.2) + *requires*: pytest (>=6.2) pytest servers @@ -7273,6 +8278,13 @@ This list contains 1118 plugins. pytest-session_to_file is a py.test plugin for capturing and saving to file the stdout of py.test. + :pypi:`pytest-setupinfo` + *last release*: Jan 23, 2023, + *status*: N/A, + *requires*: N/A + + Displaying setup info during pytest command run + :pypi:`pytest-sftpserver` *last release*: Sep 16, 2019, *status*: 4 - Beta, @@ -7287,6 +8299,13 @@ This list contains 1118 plugins. + :pypi:`pytest-share-hdf` + *last release*: Sep 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Plugin to save test data in HDF files and retrieve them for comparison + :pypi:`pytest-sharkreport` *last release*: Jul 11, 2022, *status*: N/A, @@ -7302,7 +8321,7 @@ This list contains 1118 plugins. A pytest plugin to help with testing shell scripts / black box commands :pypi:`pytest-shell-utilities` - *last release*: Jul 28, 2022, + *last release*: Sep 23, 2022, *status*: 4 - Beta, *requires*: pytest (>=6.0.0) @@ -7316,7 +8335,7 @@ This list contains 1118 plugins. Versatile ZODB abstraction layer - pytest fixtures :pypi:`pytest-sherlock` - *last release*: Nov 18, 2021, + *last release*: Jan 16, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.1) @@ -7365,9 +8384,9 @@ This list contains 1118 plugins. Allow for multiple processes to log to a single file :pypi:`pytest-skip-markers` - *last release*: May 09, 2022, + *last release*: Dec 20, 2022, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.0.0) + *requires*: pytest (>=7.1.0) Pytest Salt Plugin @@ -7386,9 +8405,9 @@ This list contains 1118 plugins. Automatically skip tests that don't need to run! :pypi:`pytest-skip-slow` - *last release*: Apr 26, 2022, + *last release*: Feb 09, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.2.0 A pytest plugin to skip \`@pytest.mark.slow\` tests by default. @@ -7406,6 +8425,20 @@ This list contains 1118 plugins. A pytest plugin to skip \`@pytest.mark.slow\` tests by default. + :pypi:`pytest-slowest-first` + *last release*: Dec 11, 2022, + *status*: 4 - Beta, + *requires*: N/A + + Sort tests by their last duration, slowest first + + :pypi:`pytest-slow-last` + *last release*: Dec 10, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + Run tests in order of execution time (faster tests first) + :pypi:`pytest-smartcollect` *last release*: Oct 04, 2018, *status*: N/A, @@ -7434,6 +8467,13 @@ This list contains 1118 plugins. Send email with pytest execution result + :pypi:`pytest-smtpd` + *last release*: May 15, 2023, + *status*: N/A, + *requires*: pytest + + An SMTP server for testing built on aiosmtpd + :pypi:`pytest-snail` *last release*: Nov 04, 2019, *status*: 3 - Alpha, @@ -7470,7 +8510,7 @@ This list contains 1118 plugins. Setup test data and run tests on snowflake in BDD style! :pypi:`pytest-socket` - *last release*: Jan 23, 2022, + *last release*: Feb 03, 2023, *status*: 4 - Beta, *requires*: pytest (>=3.6.3) @@ -7512,7 +8552,7 @@ This list contains 1118 plugins. A simple plugin to first execute tests that historically failed more :pypi:`pytest-sosu` - *last release*: Apr 11, 2022, + *last release*: Feb 14, 2023, *status*: 2 - Pre-Alpha, *requires*: pytest @@ -7554,9 +8594,9 @@ This list contains 1118 plugins. Library pytest-spec2md is a pytest plugin to create a markdown specification while running pytest. :pypi:`pytest-speed` - *last release*: Jul 06, 2022, + *last release*: Jan 22, 2023, *status*: 3 - Alpha, - *requires*: pytest (>=7) + *requires*: pytest>=7 Modern benchmarking library for python with pytest integration. @@ -7589,7 +8629,7 @@ This list contains 1118 plugins. Pytest plugin for the splinter automation library :pypi:`pytest-split` - *last release*: Apr 22, 2022, + *last release*: Apr 12, 2023, *status*: 4 - Beta, *requires*: pytest (>=5,<8) @@ -7617,14 +8657,14 @@ This list contains 1118 plugins. :pypi:`pytest-splunk-addon` - *last release*: Sep 08, 2022, + *last release*: Feb 22, 2023, *status*: N/A, - *requires*: pytest (>5.4.0,<6.3) + *requires*: pytest (>5.4.0,<7.3) A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Mar 16, 2022, + *last release*: Mar 07, 2023, *status*: N/A, *requires*: N/A @@ -7652,12 +8692,19 @@ This list contains 1118 plugins. pytest plugin with sqlalchemy related fixtures :pypi:`pytest-sqlalchemy-mock` - *last release*: Aug 10, 2022, + *last release*: Mar 15, 2023, *status*: 3 - Alpha, *requires*: pytest (>=2.0) pytest sqlalchemy plugin for mock + :pypi:`pytest-sqlalchemy-session` + *last release*: May 19, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.0) + + A pytest plugin for preserving test isolation that use SQLAlchemy. + :pypi:`pytest-sql-bigquery` *last release*: Dec 19, 2019, *status*: N/A, @@ -7665,6 +8712,13 @@ This list contains 1118 plugins. Yet another SQL-testing framework for BigQuery provided by pytest plugin + :pypi:`pytest-sqlfluff` + *last release*: Dec 21, 2022, + *status*: 4 - Beta, + *requires*: pytest (>=3.5.0) + + A pytest plugin to use sqlfluff to enable format checking of sql files. + :pypi:`pytest-squadcast` *last release*: Feb 22, 2022, *status*: 5 - Production/Stable, @@ -7675,7 +8729,7 @@ This list contains 1118 plugins. :pypi:`pytest-srcpaths` *last release*: Oct 15, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=6.2.0 Add paths to sys.path @@ -7693,6 +8747,20 @@ This list contains 1118 plugins. Start pytest run from a given point + :pypi:`pytest-star-track-issue` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + + A package to prevent Dependency Confusion attacks against Yandex. + + :pypi:`pytest-static` + *last release*: May 07, 2023, + *status*: 1 - Planning, + *requires*: N/A + + pytest-static + :pypi:`pytest-statsd` *last release*: Nov 30, 2018, *status*: 5 - Production/Stable, @@ -7722,7 +8790,7 @@ This list contains 1118 plugins. Run a test suite one failing test at a time. :pypi:`pytest-stf` - *last release*: Sep 09, 2022, + *last release*: Dec 04, 2022, *status*: N/A, *requires*: pytest (>=5.0) @@ -7743,7 +8811,7 @@ This list contains 1118 plugins. A Pytest plugin that allows you to loop tests for a user defined amount of time. :pypi:`pytest-structlog` - *last release*: Sep 21, 2021, + *last release*: Dec 18, 2022, *status*: N/A, *requires*: pytest @@ -7778,7 +8846,7 @@ This list contains 1118 plugins. A pytest plugin to organize long run tests (named studies) without interfering the regular tests :pypi:`pytest-subprocess` - *last release*: Feb 09, 2022, + *last release*: Jan 28, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=4.0.0) @@ -7792,7 +8860,7 @@ This list contains 1118 plugins. A hack to explicitly set up and tear down fixtures. :pypi:`pytest-subtests` - *last release*: May 26, 2022, + *last release*: May 15, 2023, *status*: 4 - Beta, *requires*: pytest (>=7.0) @@ -7806,25 +8874,18 @@ This list contains 1118 plugins. pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. :pypi:`pytest-sugar` - *last release*: Jul 10, 2022, - *status*: 3 - Alpha, - *requires*: pytest (>=2.9) + *last release*: Apr 10, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.0) pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly). - :pypi:`pytest-sugar-bugfix159` - *last release*: Nov 07, 2018, - *status*: 5 - Production/Stable, - *requires*: pytest (!=3.7.3,>=3.5); extra == 'testing' + :pypi:`pytest-suitemanager` + *last release*: Apr 28, 2023, + *status*: 4 - Beta, + *requires*: N/A - Workaround for https://github.com/Frozenball/pytest-sugar/issues/159 - - :pypi:`pytest-super-check` - *last release*: May 11, 2022, - *status*: 5 - Production/Stable, - *requires*: pytest - - Pytest plugin to check your TestCase classes call super in setUp, tearDown, etc. + A simple plugin to use with pytest :pypi:`pytest-svn` *last release*: May 28, 2019, @@ -7854,8 +8915,15 @@ This list contains 1118 plugins. Pyst - Pytest System-Test Plugin + :pypi:`pytest-tagging` + *last release*: Apr 01, 2023, + *status*: N/A, + *requires*: pytest (>=7.1.3,<8.0.0) + + a pytest plugin to tag tests + :pypi:`pytest-takeltest` - *last release*: Jan 04, 2022, + *last release*: Feb 15, 2023, *status*: N/A, *requires*: N/A @@ -7868,6 +8936,13 @@ This list contains 1118 plugins. + :pypi:`pytest-tally` + *last release*: May 22, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=6.2.5) + + A Pytest plugin to generate realtime summary stats, and display them in-console using a text-based dashboard. + :pypi:`pytest-tap` *last release*: Oct 27, 2021, *status*: 5 - Production/Stable, @@ -7896,6 +8971,13 @@ This list contains 1118 plugins. tblineinfo is a py.test plugin that insert the node id in the final py.test report when --tb=line option is used + :pypi:`pytest-tcpclient` + *last release*: Nov 16, 2022, + *status*: N/A, + *requires*: pytest (<8,>=7.1.3) + + A pytest plugin for testing TCP clients + :pypi:`pytest-teamcity-logblock` *last release*: May 15, 2018, *status*: 4 - Beta, @@ -7910,6 +8992,13 @@ This list contains 1118 plugins. Pytest to Telegram reporting plugin + :pypi:`pytest-telegram-notifier` + *last release*: Mar 17, 2023, + *status*: 5 - Production/Stable, + *requires*: N/A + + Telegram notification plugin for Pytest + :pypi:`pytest-tempdir` *last release*: Oct 11, 2019, *status*: 4 - Beta, @@ -7918,7 +9007,7 @@ This list contains 1118 plugins. Predictable and repeatable tempdir support. :pypi:`pytest-terra-fixt` - *last release*: Sep 09, 2022, + *last release*: Sep 15, 2022, *status*: N/A, *requires*: pytest (==6.2.5) @@ -7953,7 +9042,7 @@ This list contains 1118 plugins. Test configuration plugin for pytest. :pypi:`pytest-testdirectory` - *last release*: Feb 21, 2022, + *last release*: May 02, 2023, *status*: 5 - Production/Stable, *requires*: pytest @@ -7967,7 +9056,7 @@ This list contains 1118 plugins. A testdox format reporter for pytest :pypi:`pytest-test-grouping` - *last release*: Jun 17, 2022, + *last release*: Feb 01, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=2.5) @@ -7981,7 +9070,7 @@ This list contains 1118 plugins. A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. :pypi:`pytest-testinfra` - *last release*: Jun 19, 2022, + *last release*: May 21, 2023, *status*: 5 - Production/Stable, *requires*: pytest (!=3.0.2) @@ -7995,12 +9084,19 @@ This list contains 1118 plugins. pytest reporting plugin for testlink :pypi:`pytest-testmon` - *last release*: Sep 09, 2022, + *last release*: Jun 16, 2023, *status*: 4 - Beta, *requires*: N/A selects tests affected by changed files and methods + :pypi:`pytest-testmon-dev` + *last release*: Mar 30, 2023, + *status*: 4 - Beta, + *requires*: pytest (<8,>=5) + + selects tests affected by changed files and methods + :pypi:`pytest-testmon-oc` *last release*: Jun 01, 2022, *status*: 4 - Beta, @@ -8009,9 +9105,9 @@ This list contains 1118 plugins. nOly selects tests affected by changed files and methods :pypi:`pytest-testmon-skip-libraries` - *last release*: Jun 13, 2022, + *last release*: Mar 03, 2023, *status*: 4 - Beta, - *requires*: N/A + *requires*: pytest (<8,>=5) selects tests affected by changed files and methods @@ -8037,18 +9133,11 @@ This list contains 1118 plugins. pytest plugin for creating TestRail runs and adding results :pypi:`pytest-testrail2` - *last release*: Nov 17, 2020, + *last release*: Feb 10, 2023, *status*: N/A, - *requires*: pytest (>=5) + *requires*: pytest (<8.0,>=7.2.0) - A small example package - - :pypi:`pytest-testrail-api` - *last release*: Aug 29, 2022, - *status*: N/A, - *requires*: pytest (>=5.5) - - Плагин Pytest, для интеграции с TestRail + A pytest plugin to upload results to TestRail. :pypi:`pytest-testrail-api-client` *last release*: Dec 14, 2021, @@ -8107,7 +9196,7 @@ This list contains 1118 plugins. :pypi:`pytest-testreport` - *last release*: May 23, 2022, + *last release*: Dec 01, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -8142,7 +9231,7 @@ This list contains 1118 plugins. :pypi:`pytest-tesults` - *last release*: Dec 31, 2021, + *last release*: Dec 23, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=3.5.0) @@ -8183,6 +9272,13 @@ This list contains 1118 plugins. Ticking on tests + :pypi:`pytest-time` + *last release*: Jun 16, 2023, + *status*: 3 - Alpha, + *requires*: pytest + + + :pypi:`pytest-timeit` *last release*: Oct 13, 2016, *status*: 4 - Beta, @@ -8219,7 +9315,7 @@ This list contains 1118 plugins. Pytest plugin to add a timestamp prefix to the pytest output :pypi:`pytest-timestamps` - *last release*: Jan 16, 2022, + *last release*: Apr 01, 2023, *status*: N/A, *requires*: pytest (>=5.2) @@ -8240,7 +9336,7 @@ This list contains 1118 plugins. Better fixtures management. Various helpers :pypi:`pytest-tldr` - *last release*: Mar 12, 2021, + *last release*: Oct 26, 2022, *status*: 4 - Beta, *requires*: pytest (>=3.5.0) @@ -8281,6 +9377,13 @@ This list contains 1118 plugins. this is a vue-element ui report for pytest + :pypi:`pytest-tmux` + *last release*: Apr 22, 2023, + *status*: 4 - Beta, + *requires*: N/A + + A pytest plugin that enables tmux driven tests + :pypi:`pytest-todo` *last release*: May 23, 2019, *status*: 4 - Beta, @@ -8310,7 +9413,7 @@ This list contains 1118 plugins. Numerous useful plugins for pytest. :pypi:`pytest-tools` - *last release*: Jul 04, 2022, + *last release*: Oct 21, 2022, *status*: 4 - Beta, *requires*: N/A @@ -8394,15 +9497,15 @@ This list contains 1118 plugins. py.test plugin for using the same _trial_temp working directory as trial :pypi:`pytest-trio` - *last release*: Oct 16, 2020, + *last release*: Nov 01, 2022, *status*: N/A, - *requires*: N/A + *requires*: pytest (>=7.2.0) Pytest plugin for trio :pypi:`pytest-trytond` - *last release*: Feb 02, 2022, - *status*: 3 - Alpha, + *last release*: Nov 04, 2022, + *status*: 4 - Beta, *requires*: pytest (>=5) Pytest plugin for the Tryton server framework @@ -8429,11 +9532,18 @@ This list contains 1118 plugins. Test Class Base :pypi:`pytest-tui` - *last release*: Sep 07, 2022, + *last release*: Jun 12, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.2.5) + *requires*: N/A + + Text User Interface (TUI) and HTML report for Pytest test runs + + :pypi:`pytest-tutorials` + *last release*: Mar 11, 2023, + *status*: N/A, + *requires*: N/A + - Text User Interface (TUI) for Pytest, with optional auto-launch and HTML export :pypi:`pytest-twilio-conversations-client-mock` *last release*: Aug 02, 2022, @@ -8443,7 +9553,7 @@ This list contains 1118 plugins. :pypi:`pytest-twisted` - *last release*: Aug 30, 2021, + *last release*: Oct 16, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=2.3) @@ -8464,7 +9574,7 @@ This list contains 1118 plugins. A Typhoon HIL plugin that facilitates test parameter configuration at runtime :pypi:`pytest-typhoon-xray` - *last release*: Mar 07, 2022, + *last release*: Jun 10, 2023, *status*: 4 - Beta, *requires*: N/A @@ -8491,8 +9601,22 @@ This list contains 1118 plugins. Text User Interface for running python tests + :pypi:`pytest-ui-failed-screenshot` + *last release*: Dec 06, 2022, + *status*: N/A, + *requires*: N/A + + UI自动测试失败时自动截图,并将截图加入到测试报告中 + + :pypi:`pytest-ui-failed-screenshot-allure` + *last release*: Dec 06, 2022, + *status*: N/A, + *requires*: N/A + + UI自动测试失败时自动截图,并将截图加入到Allure测试报告中 + :pypi:`pytest-unflakable` - *last release*: Jun 14, 2022, + *last release*: Mar 24, 2023, *status*: 4 - Beta, *requires*: pytest (>=6.2.0) @@ -8520,19 +9644,26 @@ This list contains 1118 plugins. Run only unmarked tests :pypi:`pytest-unordered` - *last release*: Jul 08, 2022, + *last release*: Nov 28, 2022, *status*: 4 - Beta, *requires*: pytest (>=6.0.0) Test equality of unordered collections in pytest :pypi:`pytest-unstable` - *last release*: Jun 10, 2022, + *last release*: Sep 27, 2022, *status*: 4 - Beta, *requires*: N/A Set a test as unstable to return 0 even if it failed + :pypi:`pytest-unused-fixtures` + *last release*: Jun 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.3.2,<8.0.0) + + A pytest plugin to list unused fixtures after a test run. + :pypi:`pytest-upload-report` *last release*: Jun 18, 2021, *status*: 5 - Production/Stable, @@ -8541,9 +9672,9 @@ This list contains 1118 plugins. pytest-upload-report is a plugin for pytest that upload your test report for test results. :pypi:`pytest-utils` - *last release*: Dec 04, 2021, + *last release*: Feb 02, 2023, *status*: 4 - Beta, - *requires*: pytest (>=6.2.5,<7.0.0) + *requires*: pytest (>=7.0.0,<8.0.0) Some helpers for pytest. @@ -8562,9 +9693,9 @@ This list contains 1118 plugins. :pypi:`pytest-variables` - *last release*: Mar 27, 2022, + *last release*: May 27, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=3.0.0,<8.0.0) + *requires*: pytest>=7.0.0 pytest plugin for providing variables to tests/fixtures @@ -8596,6 +9727,13 @@ This list contains 1118 plugins. Test from HTTP interactions to dataframe processed. + :pypi:`pytest-vcs` + *last release*: Sep 22, 2022, + *status*: 4 - Beta, + *requires*: N/A + + + :pypi:`pytest-venv` *last release*: Aug 04, 2020, *status*: 4 - Beta, @@ -8604,11 +9742,11 @@ This list contains 1118 plugins. py.test fixture for creating a virtual environment :pypi:`pytest-ver` - *last release*: Aug 21, 2022, - *status*: 2 - Pre-Alpha, + *last release*: Mar 22, 2023, + *status*: 4 - Beta, *requires*: N/A - Pytest module with Verification Report + Pytest module with Verification Protocol, Verification Report and Trace Matrix :pypi:`pytest-verbose-parametrize` *last release*: May 28, 2019, @@ -8632,7 +9770,7 @@ This list contains 1118 plugins. Virtualenv fixture for py.test :pypi:`pytest-vnc` - *last release*: Jun 03, 2022, + *last release*: Feb 25, 2023, *status*: N/A, *requires*: pytest @@ -8652,6 +9790,13 @@ This list contains 1118 plugins. A pytest plugin to easily enable debugging tests within Visual Studio Code + :pypi:`pytest-vscode-pycharm-cls` + *last release*: Feb 01, 2023, + *status*: N/A, + *requires*: pytest + + A PyTest helper to enable start remote debugger on test start or failure or when pytest.set_trace is used. + :pypi:`pytest-vts` *last release*: Jun 05, 2019, *status*: N/A, @@ -8659,6 +9804,13 @@ This list contains 1118 plugins. pytest plugin for automatic recording of http stubbed tests + :pypi:`pytest-vulture` + *last release*: Jun 01, 2023, + *status*: N/A, + *requires*: pytest (>=7.0.0) + + A pytest plugin to checks dead code with vulture + :pypi:`pytest-vw` *last release*: Oct 07, 2015, *status*: 4 - Beta, @@ -8681,7 +9833,7 @@ This list contains 1118 plugins. Pytest plugin for testing whatsapp bots with end to end tests :pypi:`pytest-wake` - *last release*: Aug 30, 2022, + *last release*: May 11, 2023, *status*: N/A, *requires*: pytest @@ -8695,11 +9847,11 @@ This list contains 1118 plugins. Local continuous test runner with pytest and watchdog. :pypi:`pytest-watcher` - *last release*: Dec 30, 2021, - *status*: 3 - Alpha, + *last release*: Jun 11, 2023, + *status*: 4 - Beta, *requires*: N/A - Continiously runs pytest on changes in \*.py files + Automatically rerun your tests on file modifications :pypi:`pytest-wdl` *last release*: Nov 17, 2020, @@ -8708,6 +9860,13 @@ This list contains 1118 plugins. Pytest plugin for testing WDL workflows. + :pypi:`pytest-web3-data` + *last release*: Sep 15, 2022, + *status*: 4 - Beta, + *requires*: pytest + + + :pypi:`pytest-webdriver` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -8722,6 +9881,13 @@ This list contains 1118 plugins. Welian API Automation test framework pytest plugin + :pypi:`pytest-when` + *last release*: Jun 05, 2023, + *status*: N/A, + *requires*: pytest>=7.3.1 + + Utility which makes mocking more readable and controllable + :pypi:`pytest-whirlwind` *last release*: Jun 12, 2020, *status*: N/A, @@ -8765,18 +9931,18 @@ This list contains 1118 plugins. pytest with docker helpers. :pypi:`pytest-workflow` - *last release*: Dec 03, 2021, + *last release*: Jan 13, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4.0) + *requires*: pytest (>=7.0.0) A pytest plugin for configuring workflow/pipeline tests using YAML files :pypi:`pytest-xdist` - *last release*: Dec 10, 2021, + *last release*: May 19, 2023, *status*: 5 - Production/Stable, *requires*: pytest (>=6.2.0) - pytest xdist plugin for distributed testing and loop-on-failing modes + pytest xdist plugin for distributed testing, most importantly across multiple CPUs :pypi:`pytest-xdist-debug-for-graingert` *last release*: Jul 24, 2019, @@ -8799,6 +9965,13 @@ This list contains 1118 plugins. pytest plugin helps to reproduce failures for particular xdist node + :pypi:`pytest-xdist-worker-stats` + *last release*: Jun 15, 2023, + *status*: 4 - Beta, + *requires*: pytest (>=7.3.2,<8.0.0) + + A pytest plugin to list worker statistics after a xdist run. + :pypi:`pytest-xfaillist` *last release*: Sep 17, 2021, *status*: N/A, @@ -8821,9 +9994,9 @@ This list contains 1118 plugins. Extended logging for test and decorators :pypi:`pytest-xlsx` - *last release*: Aug 04, 2022, + *last release*: Mar 01, 2023, *status*: N/A, - *requires*: N/A + *requires*: pytest>=7.2.0 pytest plugin for generating test cases by xlsx(excel) @@ -8835,7 +10008,7 @@ This list contains 1118 plugins. An extended parametrizing plugin of pytest. :pypi:`pytest-xprocess` - *last release*: Aug 29, 2022, + *last release*: Jan 05, 2023, *status*: 4 - Beta, *requires*: pytest (>=2.8) @@ -8862,19 +10035,19 @@ This list contains 1118 plugins. - :pypi:`pytest-xreport` - *last release*: May 17, 2022, - *status*: 4 - Beta, - *requires*: pytest (>=3.5.0) - + :pypi:`pytest-xskynet` + *last release*: Feb 10, 2023, + *status*: N/A, + *requires*: N/A + A package to prevent Dependency Confusion attacks against Yandex. :pypi:`pytest-xvfb` - *last release*: Jun 09, 2020, + *last release*: May 29, 2023, *status*: 4 - Beta, *requires*: pytest (>=2.8.1) - A pytest plugin to run Xvfb for tests. + A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. :pypi:`pytest-yaml` *last release*: Oct 05, 2018, @@ -8883,6 +10056,13 @@ This list contains 1118 plugins. This plugin is used to load yaml output to your test using pytest framework. + :pypi:`pytest-yaml-sanmu` + *last release*: May 28, 2023, + *status*: N/A, + *requires*: pytest>=7.2.0 + + pytest plugin for generating test cases by yaml + :pypi:`pytest-yamltree` *last release*: Mar 02, 2020, *status*: 4 - Beta, @@ -8897,6 +10077,13 @@ This list contains 1118 plugins. Run tests against wsgi apps defined in yaml + :pypi:`pytest-yaml-yoyo` + *last release*: Jun 08, 2023, + *status*: N/A, + *requires*: pytest (>=7.2.0) + + http/https API run by yaml + :pypi:`pytest-yapf` *last release*: Jul 06, 2017, *status*: 4 - Beta, @@ -8905,9 +10092,9 @@ This list contains 1118 plugins. Run yapf :pypi:`pytest-yapf3` - *last release*: Aug 03, 2020, + *last release*: Mar 29, 2023, *status*: 5 - Production/Stable, - *requires*: pytest (>=5.4) + *requires*: pytest (>=7) Validate your Python file format with yapf @@ -8919,16 +10106,16 @@ This list contains 1118 plugins. PyTest plugin to run tests concurrently, each \`yield\` switch context to other one :pypi:`pytest-yls` - *last release*: Aug 08, 2022, + *last release*: Mar 29, 2023, *status*: N/A, - *requires*: pytest (>=7.1.2,<8.0.0) + *requires*: pytest (>=7.2.2,<8.0.0) Pytest plugin to test the YLS as a whole. :pypi:`pytest-yuk` *last release*: Mar 26, 2021, *status*: N/A, - *requires*: N/A + *requires*: pytest>=5.0.0 Display tests you are uneasy with, using 🤢/🤮 for pass/fail of tests marked with yuk. @@ -8947,12 +10134,19 @@ This list contains 1118 plugins. OWASP ZAP plugin for py.test. :pypi:`pytest-zebrunner` - *last release*: Jun 02, 2022, + *last release*: Dec 12, 2022, *status*: 5 - Production/Stable, *requires*: pytest (>=4.5.0) Pytest connector for Zebrunner reporting + :pypi:`pytest-zest` + *last release*: Nov 17, 2022, + *status*: N/A, + *requires*: N/A + + Zesty additions to pytest. + :pypi:`pytest-zigzag` *last release*: Feb 27, 2019, *status*: 4 - Beta, diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 6edb88b92..d5ab94bc3 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -956,6 +956,12 @@ TestReport :show-inheritance: :inherited-members: +TestShortLogReport +~~~~~~~~~~~~~~~~~~ + +.. autoclass:: pytest.TestShortLogReport() + :members: + _Result ~~~~~~~ @@ -1047,6 +1053,14 @@ 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. Alternative 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. + .. envvar:: PYTEST_ADDOPTS This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given @@ -1139,6 +1153,9 @@ Custom warnings generated in some situations such as improper usage or deprecate .. autoclass:: pytest.PytestRemovedIn8Warning :show-inheritance: +.. autoclass:: pytest.PytestRemovedIn9Warning + :show-inheritance: + .. autoclass:: pytest.PytestUnhandledCoroutineWarning :show-inheritance: @@ -1212,6 +1229,7 @@ passed multiple times. The expected format is ``name=value``. For example:: * ``classic``: classic pytest output. * ``progress``: like classic pytest output, but with a progress indicator. + * ``progress-even-when-capture-no``: allows the use of the progress indicator even when ``capture=no``. * ``count``: like progress, but shows progress as the number of tests completed instead of a percent. The default is ``progress``, but you can fallback to ``classic`` if you prefer or @@ -1688,6 +1706,11 @@ 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 @@ -1704,13 +1727,12 @@ 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 ` 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. @@ -1719,8 +1741,51 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] testpaths = testing doc - This tells pytest to only look for tests in ``testing`` and ``doc`` - directories when executing from the root directory. + This configuration means that executing: + + .. code-block:: console + + pytest + + has the same practical effects as executing: + + .. code-block:: console + + pytest testing doc + + +.. 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: ``all`` .. confval:: usefixtures @@ -1759,12 +1824,12 @@ All the command-line flags can be obtained by running ``pytest --help``:: $ pytest --help usage: pytest [options] [file_or_dir] [file_or_dir] [...] - Positional arguments: + positional arguments: file_or_dir - General: + general: -k EXPRESSION Only run tests which match the given substring - expression. An expression is a python evaluatable + expression. An expression is a Python evaluatable expression where all names are substring-matched against test names and their parent classes. Example: -k 'test_method or test_other' matches all @@ -1778,9 +1843,9 @@ All the command-line flags can be obtained by running ``pytest --help``:: 'extra_keyword_matches' set, as well as functions which have names assigned directly to them. The matching is case-insensitive. - -m MARKEXPR Only run tests matching given mark expression. - For example: -m 'mark1 and not mark2'. - --markers Show markers (builtin, plugin and per-project ones) + -m MARKEXPR Only run tests matching given mark expression. For + example: -m 'mark1 and not mark2'. + --markers show markers (builtin, plugin and per-project ones). -x, --exitfirst Exit instantly on first error or failed test --fixtures, --funcargs Show available fixtures, sorted by plugin appearance @@ -1790,18 +1855,18 @@ All the command-line flags can be obtained by running ``pytest --help``:: KeyboardInterrupt --pdbcls=modulename:classname Specify a custom interactive Python debugger for use - with --pdb. For example: + with --pdb.For example: --pdbcls=IPython.terminal.debugger:TerminalPdb --trace Immediately break when running each test - --capture=method Per-test capturing method: one of fd|sys|no|tee-sys. - -s Shortcut for --capture=no. + --capture=method Per-test capturing method: one of fd|sys|no|tee-sys + -s Shortcut for --capture=no --runxfail Report the results of xfail tests as if they were not marked --lf, --last-failed Rerun only the tests that failed at the last run (or all if none failed) - --ff, --failed-first Run all tests, but run the last failures first - This may re-order tests and thus lead to repeated - fixture setup/teardown + --ff, --failed-first Run all tests, but run the last failures first. This + may re-order tests and thus lead to repeated fixture + setup/teardown. --nf, --new-first Run tests from new files first, then the rest of the tests sorted by file mtime --cache-show=[CACHESHOW] @@ -1815,11 +1880,10 @@ All the command-line flags can be obtained by running ``pytest --help``:: test next time --sw-skip, --stepwise-skip Ignore the first failing test but stop on the next - failing test. - implicitly enables --stepwise. + failing test. Implicitly enables --stepwise. Reporting: - --durations=N show N slowest setup/test durations (N=0 for all) + --durations=N Show N slowest setup/test durations (N=0 for all) --durations-min=N Minimal duration in seconds for inclusion in slowest list. Default: 0.005. -v, --verbose Increase verbosity @@ -1836,8 +1900,10 @@ All the command-line flags can be obtained by running ``pytest --help``:: --disable-warnings, --disable-pytest-warnings Disable warnings summary -l, --showlocals Show locals in tracebacks (disabled by default) + --no-showlocals Hide locals in tracebacks (negate --showlocals + passed through addopts) --tb=style Traceback print mode - (auto/long/short/line/native/no). + (auto/long/short/line/native/no) --show-capture={no,stdout,stderr,log,all} Controls how captured stdout/stderr/log is shown on failed tests. Default: all. @@ -1860,18 +1926,18 @@ 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 Load configuration from `file` instead of trying to - locate one of the implicit configuration files + -c FILE, --config-file=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 + Force test execution even if collection errors occur --rootdir=ROOTDIR Define root directory for tests. Can be relative path: 'root_dir', './root_dir', 'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: '$HOME/root_dir'. - Collection: + collection: --collect-only, --co Only collect tests, don't execute them --pyargs Try to interpret all arguments as Python packages --ignore=path Ignore path during collection (multi-allowed) @@ -1899,27 +1965,24 @@ All the command-line flags can be obtained by running ``pytest --help``:: For a given doctest, continue to run after the first failure - Test session debugging and configuration: - --basetemp=dir Base temporary directory for this test run. (Warning: - this directory is removed if it exists.) + test session debugging and configuration: + --basetemp=dir Base temporary directory for this test run. + (Warning: this directory is removed if it exists.) -V, --version Display pytest version and information about plugins. When given twice, also display information about plugins. -h, --help Show help message and configuration info -p name Early-load given plugin module name or entry point - (multi-allowed) - To avoid loading of plugins, use the `no:` prefix, - e.g. `no:doctest` + (multi-allowed). To avoid loading of plugins, use + the `no:` prefix, e.g. `no:doctest`. --trace-config Trace considerations of conftest.py files --debug=[DEBUG_FILE_NAME] Store internal tracing debug information in this log - file. - This file is opened with 'w' and truncated as a - result, care advised. - Default: pytestdebug.log. + file. This file is opened with 'w' and truncated as + a result, care advised. Default: pytestdebug.log. -o OVERRIDE_INI, --override-ini=OVERRIDE_INI Override ini option with "option=value" style, e.g. - `-o xfail_strict=True -o cache_dir=cache` + `-o xfail_strict=True -o cache_dir=cache`. --assert=MODE Control assertion debugging tools. 'plain' performs no assertion debugging. 'rewrite' (the default) rewrites assert statements @@ -1930,11 +1993,11 @@ All the command-line flags can be obtained by running ``pytest --help``:: --setup-plan Show what fixtures and tests would be executed but don't execute anything - Logging: - --log-level=LEVEL Level of messages to catch/display. - Not set by default, so it depends on the root/parent - log handler's effective level, where it is "WARNING" - by default. + logging: + --log-level=LEVEL Level of messages to catch/display. Not set by + default, so it depends on the root/parent log + handler's effective level, where it is "WARNING" by + default. --log-format=LOG_FORMAT Log format used by the logging module --log-date-format=LOG_DATE_FORMAT @@ -1955,15 +2018,18 @@ All the command-line flags can be obtained by running ``pytest --help``:: --log-auto-indent=LOG_AUTO_INDENT Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer. + --log-disable=LOGGER_DISABLE + Disable a logger by name. Can be passed multiple + times. - [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found: + [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found: markers (linelist): Markers for test functions empty_parameter_set_mark (string): Default marker for empty parametersets norecursedirs (args): Directory patterns to avoid for recursion testpaths (args): Directories to search for tests when no files or - directories are given in the command line + directories are given on the command line filterwarnings (linelist): Each line specifies a pattern for warnings.filterwarnings. Processed after @@ -1984,9 +2050,18 @@ All the command-line flags can be obtained by running ``pytest --help``:: console_output_style (string): Console output: "classic", or with additional progress information ("progress" (percentage) | - "count") + "count" | "progress-even-when-capture-no" (forces + progress even when capture=no) xfail_strict (bool): Default for the strict parameter of xfail markers when not given explicitly (default: False) + tmp_path_retention_count (string): + How many sessions should we keep the `tmp_path` + directories, according to + `tmp_path_retention_policy`. + tmp_path_retention_policy (string): + Controls which directories created by the `tmp_path` + fixture are kept around, based on test outcome. + (all/failed/none) enable_assertion_pass_hook (bool): Enables the pytest_assertion_pass hook. Make sure to delete any previously generated pyc cache files. diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index a0d54cd4c..b6059723c 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,7 +1,11 @@ pallets-sphinx-themes pluggy>=1.0 -pygments-pytest>=2.2.0 +pygments-pytest>=2.3.0 sphinx-removed-in>=0.2.0 sphinx>=5,<6 sphinxcontrib-trio sphinxcontrib-svg2pdfconverter +# Pin packaging because it no longer handles 'latest' version, which +# is the version that is assigned to the docs. +# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045. +packaging <22 diff --git a/pyproject.toml b/pyproject.toml index fc9a119f6..a4139a5c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,3 +114,8 @@ 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" diff --git a/scripts/towncrier-draft-to-file.py b/scripts/towncrier-draft-to-file.py index 81507b40b..1f1068689 100644 --- a/scripts/towncrier-draft-to-file.py +++ b/scripts/towncrier-draft-to-file.py @@ -7,7 +7,9 @@ def main(): Platform agnostic wrapper script for towncrier. Fixes the issue (#7251) where windows users are unable to natively run tox -e docs to build pytest docs. """ - with open("doc/en/_changelog_towncrier_draft.rst", "w") as draft_file: + with open( + "doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8" + ) as draft_file: return call(("towncrier", "--draft"), stdout=draft_file) diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py index c034c7242..ea7e7986e 100644 --- a/scripts/update-plugin-list.py +++ b/scripts/update-plugin-list.py @@ -17,7 +17,9 @@ Plugin List =========== PyPI projects that match "pytest-\*" are considered plugins and are listed -automatically. Packages classified as inactive are excluded. +automatically together with a manually-maintained list in `the source +code `_. +Packages classified as inactive are excluded. .. The following conditional uses a different format for this list when creating a PDF, because otherwise the table gets far too wide for the @@ -33,6 +35,9 @@ DEVELOPMENT_STATUS_CLASSIFIERS = ( "Development Status :: 6 - Mature", "Development Status :: 7 - Inactive", ) +ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins + "logassert", +} def escape_rst(text: str) -> str: @@ -52,18 +57,18 @@ def iter_plugins(): regex = r">([\d\w-]*)" response = requests.get("https://pypi.org/simple") - matches = list( - match - for match in re.finditer(regex, response.text) - if match.groups()[0].startswith("pytest-") - ) + match_names = (match.groups()[0] for match in re.finditer(regex, response.text)) + plugin_names = [ + name + for name in match_names + if name.startswith("pytest-") or name in ADDITIONAL_PROJECTS + ] - for match in tqdm(matches, smoothing=0): - name = match.groups()[0] + for name in tqdm(plugin_names, smoothing=0): response = requests.get(f"https://pypi.org/pypi/{name}/json") if response.status_code == 404: - # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple but - # return 404 on the JSON API. Skip. + # Some packages, like pytest-azurepipelines42, are included in https://pypi.org/simple + # but return 404 on the JSON API. Skip. continue response.raise_for_status() info = response.json()["info"] @@ -78,11 +83,23 @@ 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 + + def version_sort_key(version_string): + """ + Return the sort key for the given version string + returned by the API. + """ + try: + return packaging.version.parse(version_string) + except packaging.version.InvalidVersion: + # Use a hard-coded pre-release version. + return packaging.version.Version("0.0.0alpha") + releases = response.json()["releases"] - for release in sorted(releases, key=packaging.version.parse, reverse=True): + for release in sorted(releases, key=version_sort_key, reverse=True): if releases[release]: release_date = datetime.date.fromisoformat( releases[release][-1]["upload_time_iso_8601"].split("T")[0] @@ -90,7 +107,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(), @@ -122,7 +141,7 @@ def main(): reference_dir = pathlib.Path("doc", "en", "reference") plugin_list = reference_dir / "plugin_list.rst" - with plugin_list.open("w") as f: + with plugin_list.open("w", encoding="UTF-8") as f: f.write(FILE_HEAD) f.write(f"This list contains {len(plugins)} plugins.\n\n") f.write(".. only:: not latex\n\n") diff --git a/setup.cfg b/setup.cfg index 38f50556c..bc93b1c06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -6,7 +6,7 @@ long_description_content_type = text/x-rst url = https://docs.pytest.org/en/latest/ author = Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others license = MIT -license_file = LICENSE +license_files = LICENSE platforms = unix, linux, osx, cygwin, win32 classifiers = Development Status :: 6 - Mature @@ -21,6 +21,8 @@ classifiers = 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 @@ -36,16 +38,16 @@ packages = _pytest _pytest._code _pytest._io + _pytest._py _pytest.assertion _pytest.config _pytest.mark pytest +py_modules = py install_requires = - attrs>=19.2.0 iniconfig packaging pluggy>=0.12,<2.0 - py>=1.8.2 colorama;sys_platform=="win32" exceptiongroup>=1.0.0rc8;python_version<"3.11" importlib-metadata>=0.12;python_version<"3.8" @@ -66,11 +68,13 @@ console_scripts = [options.extras_require] testing = argcomplete + attrs>=19.2.0 hypothesis>=3.56 mock nose pygments>=2.7.2 requests + setuptools xmlschema [options.package_data] @@ -94,7 +98,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 diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index 120f09ff6..6a8083770 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -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)) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 97985def1..9b051332b 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,4 +1,5 @@ import ast +import dataclasses import inspect import os import re @@ -30,9 +31,7 @@ from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -from weakref import ref -import attr import pluggy import _pytest @@ -50,9 +49,9 @@ from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath if TYPE_CHECKING: + from typing_extensions import Final from typing_extensions import Literal from typing_extensions import SupportsIndex - from weakref import ReferenceType _TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] @@ -194,25 +193,25 @@ class Frame: class TracebackEntry: """A single entry in a Traceback.""" - __slots__ = ("_rawentry", "_excinfo", "_repr_style") + __slots__ = ("_rawentry", "_repr_style") def __init__( self, rawentry: TracebackType, - excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, + repr_style: Optional['Literal["short", "long"]'] = None, ) -> None: - self._rawentry = rawentry - self._excinfo = excinfo - self._repr_style: Optional['Literal["short", "long"]'] = None + self._rawentry: "Final" = rawentry + self._repr_style: "Final" = repr_style + + def with_repr_style( + self, repr_style: Optional['Literal["short", "long"]'] + ) -> "TracebackEntry": + return TracebackEntry(self._rawentry, repr_style) @property def lineno(self) -> int: return self._rawentry.tb_lineno - 1 - def set_repr_style(self, mode: "Literal['short', 'long']") -> None: - assert mode in ("short", "long") - self._repr_style = mode - @property def frame(self) -> Frame: return Frame(self._rawentry.tb_frame) @@ -272,7 +271,7 @@ class TracebackEntry: source = property(getsource) - def ishidden(self) -> bool: + def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -296,7 +295,7 @@ class TracebackEntry: else: break if tbh and callable(tbh): - return tbh(None if self._excinfo is None else self._excinfo()) + return tbh(excinfo) return tbh def __str__(self) -> str: @@ -329,16 +328,14 @@ class Traceback(List[TracebackEntry]): def __init__( self, tb: Union[TracebackType, Iterable[TracebackEntry]], - excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None, ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" - self._excinfo = excinfo if isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: cur_: Optional[TracebackType] = cur while cur_ is not None: - yield TracebackEntry(cur_, excinfo=excinfo) + yield TracebackEntry(cur_) cur_ = cur_.tb_next super().__init__(f(tb)) @@ -378,7 +375,7 @@ class Traceback(List[TracebackEntry]): continue if firstlineno is not None and x.frame.code.firstlineno != firstlineno: continue - return Traceback(x._rawentry, self._excinfo) + return Traceback(x._rawentry) return self @overload @@ -398,26 +395,27 @@ class Traceback(List[TracebackEntry]): return super().__getitem__(key) def filter( - self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden() + self, + # TODO(py38): change to positional only. + _excinfo_or_fn: Union[ + "ExceptionInfo[BaseException]", + Callable[[TracebackEntry], bool], + ], ) -> "Traceback": - """Return a Traceback instance with certain items removed + """Return a Traceback instance with certain items removed. - fn is a function that gets a single argument, a TracebackEntry - instance, and should return True when the item should be added - to the Traceback, False when not. + If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s + which are hidden (see ishidden() above). - By default this removes all the TracebackEntries which are hidden - (see ishidden() above). + Otherwise, the filter is a function that gets a single argument, a + ``TracebackEntry`` instance, and should return True when the item should + be added to the ``Traceback``, False when not. """ - return Traceback(filter(fn, self), self._excinfo) - - def getcrashentry(self) -> TracebackEntry: - """Return last non-hidden traceback entry that lead to the exception of a traceback.""" - for i in range(-1, -len(self) - 1, -1): - entry = self[i] - if not entry.ishidden(): - return entry - return self[-1] + if isinstance(_excinfo_or_fn, ExceptionInfo): + fn = lambda x: not x.ishidden(_excinfo_or_fn) # noqa: E731 + else: + fn = _excinfo_or_fn + return Traceback(filter(fn, self)) def recursionindex(self) -> Optional[int]: """Return the index of the frame/TracebackEntry where recursion originates if @@ -445,7 +443,7 @@ E = TypeVar("E", bound=BaseException, covariant=True) @final -@attr.s(repr=False, init=False, auto_attribs=True) +@dataclasses.dataclass class ExceptionInfo(Generic[E]): """Wraps sys.exc_info() objects and offers help for navigating the traceback.""" @@ -469,22 +467,41 @@ class ExceptionInfo(Generic[E]): self._traceback = traceback @classmethod - def from_exc_info( + def from_exception( cls, - exc_info: Tuple[Type[E], E, TracebackType], + # Ignoring error: "Cannot use a covariant type variable as a parameter". + # This is OK to ignore because this class is (conceptually) readonly. + # See https://github.com/python/mypy/issues/7049. + exception: E, # type: ignore[misc] exprinfo: Optional[str] = None, ) -> "ExceptionInfo[E]": - """Return an ExceptionInfo for an existing exc_info tuple. + """Return an ExceptionInfo for an existing exception. - .. warning:: - - Experimental API + The exception must have a non-``None`` ``__traceback__`` attribute, + otherwise this function fails with an assertion error. This means that + the exception must have been raised, or added a traceback with the + :py:meth:`~BaseException.with_traceback()` method. :param exprinfo: A text string helping to determine if we should strip ``AssertionError`` from the output. Defaults to the exception message/``__str__()``. + + .. versionadded:: 7.4 """ + assert ( + exception.__traceback__ + ), "Exceptions passed to ExcInfo.from_exception(...) must have a non-None __traceback__." + exc_info = (type(exception), exception, exception.__traceback__) + return cls.from_exc_info(exc_info, exprinfo) + + @classmethod + def from_exc_info( + cls, + exc_info: Tuple[Type[E], E, TracebackType], + exprinfo: Optional[str] = None, + ) -> "ExceptionInfo[E]": + """Like :func:`from_exception`, but using old-style exc_info tuple.""" _striptext = "" if exprinfo is None and isinstance(exc_info[1], AssertionError): exprinfo = getattr(exc_info[1], "msg", None) @@ -563,7 +580,7 @@ class ExceptionInfo(Generic[E]): def traceback(self) -> Traceback: """The traceback.""" if self._traceback is None: - self._traceback = Traceback(self.tb, excinfo=ref(self)) + self._traceback = Traceback(self.tb) return self._traceback @traceback.setter @@ -602,18 +619,25 @@ class ExceptionInfo(Generic[E]): """ return isinstance(self.value, exc) - def _getreprcrash(self) -> "ReprFileLocation": - exconly = self.exconly(tryshort=True) - entry = self.traceback.getcrashentry() - path, lineno = entry.frame.code.raw.co_filename, entry.lineno - return ReprFileLocation(path, lineno + 1, exconly) + def _getreprcrash(self) -> Optional["ReprFileLocation"]: + # Find last non-hidden traceback entry that led to the exception of the + # traceback, or None if all hidden. + for i in range(-1, -len(self.traceback) - 1, -1): + entry = self.traceback[i] + if not entry.ishidden(self): + path, lineno = entry.frame.code.raw.co_filename, entry.lineno + exconly = self.exconly(tryshort=True) + return ReprFileLocation(path, lineno + 1, exconly) + return None def getrepr( self, showlocals: bool = False, style: "_TracebackStyle" = "long", abspath: bool = False, - tbfilter: bool = True, + tbfilter: Union[ + bool, Callable[["ExceptionInfo[BaseException]"], Traceback] + ] = True, funcargs: bool = False, truncate_locals: bool = True, chain: bool = True, @@ -625,14 +649,20 @@ class ExceptionInfo(Generic[E]): Ignored if ``style=="native"``. :param str style: - long|short|no|native|value traceback style. + long|short|line|no|native|value traceback style. :param bool abspath: If paths should be changed to absolute or left unchanged. - :param bool tbfilter: - Hide entries that contain a local variable ``__tracebackhide__==True``. - Ignored if ``style=="native"``. + :param tbfilter: + A filter for traceback entries. + + * If false, don't hide any entries. + * If true, hide internal entries and entries that contain a local + variable ``__tracebackhide__ = True``. + * If a callable, delegates the filtering to the callable. + + Ignored if ``style`` is ``"native"``. :param bool funcargs: Show fixtures ("funcargs" for legacy purposes) per traceback entry. @@ -649,12 +679,14 @@ class ExceptionInfo(Generic[E]): """ if style == "native": return ReprExceptionInfo( - ReprTracebackNative( + reprtraceback=ReprTracebackNative( traceback.format_exception( - self.type, self.value, self.traceback[0]._rawentry + self.type, + self.value, + self.traceback[0]._rawentry if self.traceback else None, ) ), - self._getreprcrash(), + reprcrash=self._getreprcrash(), ) fmt = FormattedExcinfo( @@ -684,7 +716,7 @@ class ExceptionInfo(Generic[E]): return True -@attr.s(auto_attribs=True) +@dataclasses.dataclass class FormattedExcinfo: """Presenting information about failing Functions and Generators.""" @@ -695,12 +727,12 @@ class FormattedExcinfo: showlocals: bool = False style: "_TracebackStyle" = "long" abspath: bool = True - tbfilter: bool = True + tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True funcargs: bool = False truncate_locals: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = attr.ib( - factory=dict, init=False, repr=False + astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + default_factory=dict, init=False, repr=False ) def _getindent(self, source: "Source") -> int: @@ -741,11 +773,13 @@ class FormattedExcinfo: ) -> List[str]: """Return formatted and marked up source lines.""" lines = [] - if source is None or line_index >= len(source.lines): + if source is not None and line_index < 0: + line_index += len(source) + if source is None or line_index >= len(source.lines) or line_index < 0: + # `line_index` could still be outside `range(len(source.lines))` if + # we're processing AST with pathological position attributes. source = Source("???") line_index = 0 - if line_index < 0: - line_index += len(source) space_prefix = " " if short: lines.append(space_prefix + source.lines[line_index].strip()) @@ -805,12 +839,16 @@ class FormattedExcinfo: def repr_traceback_entry( self, - entry: TracebackEntry, + entry: Optional[TracebackEntry], excinfo: Optional[ExceptionInfo[BaseException]] = None, ) -> "ReprEntry": lines: List[str] = [] - style = entry._repr_style if entry._repr_style is not None else self.style - if style in ("short", "long"): + style = ( + entry._repr_style + if entry is not None and entry._repr_style is not None + else self.style + ) + if style in ("short", "long") and entry is not None: source = self._getentrysource(entry) if source is None: source = Source("???") @@ -851,25 +889,31 @@ class FormattedExcinfo: def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": traceback = excinfo.traceback - if self.tbfilter: - traceback = traceback.filter() + if callable(self.tbfilter): + traceback = self.tbfilter(excinfo) + elif self.tbfilter: + traceback = traceback.filter(excinfo) if isinstance(excinfo.value, RecursionError): traceback, extraline = self._truncate_recursive_traceback(traceback) else: extraline = None + if not traceback: + if extraline is None: + extraline = "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames." + entries = [self.repr_traceback_entry(None, excinfo)] + return ReprTraceback(entries, extraline, style=self.style) + last = traceback[-1] - entries = [] if self.style == "value": - reprentry = self.repr_traceback_entry(last, excinfo) - entries.append(reprentry) + entries = [self.repr_traceback_entry(last, excinfo)] return ReprTraceback(entries, None, style=self.style) - for index, entry in enumerate(traceback): - einfo = (last == entry) and excinfo or None - reprentry = self.repr_traceback_entry(entry, einfo) - entries.append(reprentry) + entries = [ + self.repr_traceback_entry(entry, excinfo if last == entry else None) + for entry in traceback + ] return ReprTraceback(entries, extraline, style=self.style) def _truncate_recursive_traceback( @@ -926,6 +970,7 @@ class FormattedExcinfo: seen: Set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) + if excinfo_: # Fall back to native traceback as a temporary workaround until # full support for exception groups added to ExceptionInfo. @@ -942,9 +987,7 @@ class FormattedExcinfo: ) else: reprtraceback = self.repr_traceback(excinfo_) - reprcrash: Optional[ReprFileLocation] = ( - excinfo_._getreprcrash() if self.style != "value" else None - ) + reprcrash = excinfo_._getreprcrash() else: # Fallback to native repr if the exception doesn't have a traceback: # ExceptionInfo objects require a full traceback to work. @@ -952,25 +995,17 @@ class FormattedExcinfo: traceback.format_exception(type(e), e, None) ) reprcrash = None - repr_chain += [(reprtraceback, reprcrash, descr)] + if e.__cause__ is not None and self.chain: e = e.__cause__ - excinfo_ = ( - ExceptionInfo.from_exc_info((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None descr = "The above exception was the direct cause of the following exception:" elif ( e.__context__ is not None and not e.__suppress_context__ and self.chain ): e = e.__context__ - excinfo_ = ( - ExceptionInfo.from_exc_info((type(e), e, e.__traceback__)) - if e.__traceback__ - else None - ) + excinfo_ = ExceptionInfo.from_exception(e) if e.__traceback__ else None descr = "During handling of the above exception, another exception occurred:" else: e = None @@ -978,7 +1013,7 @@ class FormattedExcinfo: return ExceptionChainRepr(repr_chain) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class TerminalRepr: def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception @@ -996,14 +1031,14 @@ class TerminalRepr: # This class is abstract -- only subclasses are instantiated. -@attr.s(eq=False) +@dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprcrash: Optional["ReprFileLocation"] reprtraceback: "ReprTraceback" - - def __attrs_post_init__(self) -> None: - self.sections: List[Tuple[str, str, str]] = [] + reprcrash: Optional["ReprFileLocation"] + sections: List[Tuple[str, str, str]] = dataclasses.field( + init=False, default_factory=list + ) def addsection(self, name: str, content: str, sep: str = "-") -> None: self.sections.append((name, content, sep)) @@ -1014,16 +1049,23 @@ class ExceptionRepr(TerminalRepr): tw.line(content) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] - def __attrs_post_init__(self) -> None: - super().__attrs_post_init__() + def __init__( + self, + chain: Sequence[ + Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] + ], + ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. - self.reprtraceback = self.chain[-1][0] - self.reprcrash = self.chain[-1][1] + super().__init__( + reprtraceback=chain[-1][0], + reprcrash=chain[-1][1], + ) + self.chain = chain def toterminal(self, tw: TerminalWriter) -> None: for element in self.chain: @@ -1034,17 +1076,17 @@ class ExceptionChainRepr(ExceptionRepr): super().toterminal(tw) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): reprtraceback: "ReprTraceback" - reprcrash: "ReprFileLocation" + reprcrash: Optional["ReprFileLocation"] def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) super().toterminal(tw) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] extraline: Optional[str] @@ -1073,12 +1115,12 @@ class ReprTraceback(TerminalRepr): class ReprTracebackNative(ReprTraceback): def __init__(self, tblines: Sequence[str]) -> None: - self.style = "native" self.reprentries = [ReprEntryNative(tblines)] self.extraline = None + self.style = "native" -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprEntryNative(TerminalRepr): lines: Sequence[str] @@ -1088,7 +1130,7 @@ class ReprEntryNative(TerminalRepr): tw.write("".join(self.lines)) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] reprfuncargs: Optional["ReprFuncArgs"] @@ -1142,8 +1184,8 @@ class ReprEntry(TerminalRepr): def toterminal(self, tw: TerminalWriter) -> None: if self.style == "short": - assert self.reprfileloc is not None - self.reprfileloc.toterminal(tw) + if self.reprfileloc: + self.reprfileloc.toterminal(tw) self._write_entry_lines(tw) if self.reprlocals: self.reprlocals.toterminal(tw, indent=" " * 8) @@ -1168,12 +1210,15 @@ class ReprEntry(TerminalRepr): ) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprFileLocation(TerminalRepr): - path: str = attr.ib(converter=str) + path: str lineno: int message: str + def __post_init__(self) -> None: + self.path = str(self.path) + def toterminal(self, tw: TerminalWriter) -> None: # Filename and lineno output for each entry, using an output format # that most editors understand. @@ -1185,7 +1230,7 @@ class ReprFileLocation(TerminalRepr): tw.line(f":{self.lineno}: {msg}") -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprLocals(TerminalRepr): lines: Sequence[str] @@ -1194,7 +1239,7 @@ class ReprLocals(TerminalRepr): tw.line(indent + line) -@attr.s(eq=False, auto_attribs=True) +@dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): args: Sequence[Tuple[str, object]] diff --git a/src/_pytest/_py/__init__.py b/src/_pytest/_py/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/_pytest/_py/error.py b/src/_pytest/_py/error.py new file mode 100644 index 000000000..0b8f2d535 --- /dev/null +++ b/src/_pytest/_py/error.py @@ -0,0 +1,109 @@ +"""create errno-specific classes for IO or os calls.""" +from __future__ import annotations + +import errno +import os +import sys +from typing import Callable +from typing import TYPE_CHECKING +from typing import TypeVar + +if TYPE_CHECKING: + from typing_extensions import ParamSpec + + P = ParamSpec("P") + +R = TypeVar("R") + + +class Error(EnvironmentError): + def __repr__(self) -> str: + return "{}.{} {!r}: {} ".format( + self.__class__.__module__, + self.__class__.__name__, + self.__class__.__doc__, + " ".join(map(str, self.args)), + # repr(self.args) + ) + + def __str__(self) -> str: + s = "[{}]: {}".format( + self.__class__.__doc__, + " ".join(map(str, self.args)), + ) + return s + + +_winerrnomap = { + 2: errno.ENOENT, + 3: errno.ENOENT, + 17: errno.EEXIST, + 18: errno.EXDEV, + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable + 22: errno.ENOTDIR, + 20: errno.ENOTDIR, + 267: errno.ENOTDIR, + 5: errno.EACCES, # anything better? +} + + +class ErrorMaker: + """lazily provides Exception classes for each possible POSIX errno + (as defined per the 'errno' module). All such instances + subclass EnvironmentError. + """ + + _errno2class: dict[int, type[Error]] = {} + + def __getattr__(self, name: str) -> type[Error]: + if name[0] == "_": + raise AttributeError(name) + eno = getattr(errno, name) + cls = self._geterrnoclass(eno) + setattr(self, name, cls) + return cls + + def _geterrnoclass(self, eno: int) -> type[Error]: + try: + return self._errno2class[eno] + except KeyError: + clsname = errno.errorcode.get(eno, "UnknownErrno%d" % (eno,)) + errorcls = type( + clsname, + (Error,), + {"__module__": "py.error", "__doc__": os.strerror(eno)}, + ) + self._errno2class[eno] = errorcls + return errorcls + + def checked_call( + self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs + ) -> R: + """Call a function and raise an errno-exception if applicable.""" + __tracebackhide__ = True + try: + return func(*args, **kwargs) + except Error: + raise + except OSError as value: + if not hasattr(value, "errno"): + raise + errno = value.errno + if sys.platform == "win32": + try: + cls = self._geterrnoclass(_winerrnomap[errno]) + except KeyError: + raise value + else: + # we are not on Windows, or we got a proper OSError + cls = self._geterrnoclass(errno) + + raise cls(f"{func.__name__}{args!r}") + + +_error_maker = ErrorMaker() +checked_call = _error_maker.checked_call + + +def __getattr__(attr: str) -> type[Error]: + return getattr(_error_maker, attr) # type: ignore[no-any-return] diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py new file mode 100644 index 000000000..73a070d19 --- /dev/null +++ b/src/_pytest/_py/path.py @@ -0,0 +1,1475 @@ +"""local path implementation.""" +from __future__ import annotations + +import atexit +import fnmatch +import importlib.util +import io +import os +import posixpath +import sys +import uuid +import warnings +from contextlib import contextmanager +from os.path import abspath +from os.path import dirname +from os.path import exists +from os.path import isabs +from os.path import isdir +from os.path import isfile +from os.path import islink +from os.path import normpath +from stat import S_ISDIR +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 + +from . import error + +if TYPE_CHECKING: + from typing import Literal + +# Moved from local.py. +iswin32 = sys.platform == "win32" or (getattr(os, "_name", False) == "nt") + + +class Checkers: + _depend_on_existence = "exists", "link", "dir", "file" + + def __init__(self, path): + self.path = path + + def dotfile(self): + return self.path.basename.startswith(".") + + def ext(self, arg): + if not arg.startswith("."): + arg = "." + arg + return self.path.ext == arg + + def basename(self, arg): + return self.path.basename == arg + + def basestarts(self, arg): + return self.path.basename.startswith(arg) + + def relto(self, arg): + return self.path.relto(arg) + + def fnmatch(self, arg): + return self.path.fnmatch(arg) + + def endswith(self, arg): + return str(self.path).endswith(arg) + + def _evaluate(self, kw): + from .._code.source import getrawcode + + for name, value in kw.items(): + invert = False + meth = None + try: + meth = getattr(self, name) + except AttributeError: + if name[:3] == "not": + invert = True + try: + meth = getattr(self, name[3:]) + except AttributeError: + pass + if meth is None: + raise TypeError(f"no {name!r} checker available for {self.path!r}") + try: + if getrawcode(meth).co_argcount > 1: + if (not meth(value)) ^ invert: + return False + else: + if bool(value) ^ bool(meth()) ^ invert: + return False + except (error.ENOENT, error.ENOTDIR, error.EBUSY): + # EBUSY feels not entirely correct, + # but its kind of necessary since ENOMEDIUM + # is not accessible in python + for name in self._depend_on_existence: + if name in kw: + if kw.get(name): + return False + name = "not" + name + if name in kw: + if not kw.get(name): + return False + return True + + _statcache: Stat + + def _stat(self) -> Stat: + try: + return self._statcache + except AttributeError: + try: + self._statcache = self.path.stat() + except error.ELOOP: + self._statcache = self.path.lstat() + return self._statcache + + def dir(self): + return S_ISDIR(self._stat().mode) + + def file(self): + return S_ISREG(self._stat().mode) + + def exists(self): + return self._stat() + + def link(self): + st = self.path.lstat() + return S_ISLNK(st.mode) + + +class NeverRaised(Exception): + pass + + +class Visitor: + def __init__(self, fil, rec, ignore, bf, sort): + if isinstance(fil, str): + fil = FNMatcher(fil) + if isinstance(rec, str): + self.rec: Callable[[LocalPath], bool] = FNMatcher(rec) + elif not hasattr(rec, "__call__") and rec: + self.rec = lambda path: True + else: + self.rec = rec + self.fil = fil + self.ignore = ignore + self.breadthfirst = bf + self.optsort = cast(Callable[[Any], Any], sorted) if sort else (lambda x: x) + + def gen(self, path): + try: + entries = path.listdir() + except self.ignore: + return + rec = self.rec + dirs = self.optsort( + [p for p in entries if p.check(dir=1) and (rec is None or rec(p))] + ) + if not self.breadthfirst: + for subdir in dirs: + for p in self.gen(subdir): + yield p + for p in self.optsort(entries): + if self.fil is None or self.fil(p): + yield p + if self.breadthfirst: + for subdir in dirs: + for p in self.gen(subdir): + yield p + + +class FNMatcher: + def __init__(self, pattern): + self.pattern = pattern + + def __call__(self, path): + pattern = self.pattern + + if ( + pattern.find(path.sep) == -1 + and iswin32 + and pattern.find(posixpath.sep) != -1 + ): + # Running on Windows, the pattern has no Windows path separators, + # and the pattern has one or more Posix path separators. Replace + # the Posix path separators with the Windows path separator. + pattern = pattern.replace(posixpath.sep, path.sep) + + if pattern.find(path.sep) == -1: + name = path.basename + else: + name = str(path) # path.strpath # XXX svn? + if not os.path.isabs(pattern): + pattern = "*" + path.sep + pattern + return fnmatch.fnmatch(name, pattern) + + +def map_as_list(func, iter): + return list(map(func, iter)) + + +class Stat: + if TYPE_CHECKING: + + @property + def size(self) -> int: + ... + + @property + def mtime(self) -> float: + ... + + def __getattr__(self, name: str) -> Any: + return getattr(self._osstatresult, "st_" + name) + + def __init__(self, path, osstatresult): + self.path = path + self._osstatresult = osstatresult + + @property + def owner(self): + if iswin32: + raise NotImplementedError("XXX win32") + import pwd + + entry = error.checked_call(pwd.getpwuid, self.uid) # type:ignore[attr-defined] + return entry[0] + + @property + def group(self): + """Return group name of file.""" + if iswin32: + raise NotImplementedError("XXX win32") + import grp + + entry = error.checked_call(grp.getgrgid, self.gid) # type:ignore[attr-defined] + return entry[0] + + def isdir(self): + return S_ISDIR(self._osstatresult.st_mode) + + def isfile(self): + return S_ISREG(self._osstatresult.st_mode) + + def islink(self): + self.path.lstat() + return S_ISLNK(self._osstatresult.st_mode) + + +def getuserid(user): + import pwd + + if not isinstance(user, int): + user = pwd.getpwnam(user)[2] # type:ignore[attr-defined] + return user + + +def getgroupid(group): + import grp + + if not isinstance(group, int): + group = grp.getgrnam(group)[2] # type:ignore[attr-defined] + return group + + +class LocalPath: + """Object oriented interface to os.path and other local filesystem + related information. + """ + + class ImportMismatchError(ImportError): + """raised on pyimport() if there is a mismatch of __file__'s""" + + sep = os.sep + + def __init__(self, path=None, expanduser=False): + """Initialize and return a local Path instance. + + Path can be relative to the current directory. + If path is None it defaults to the current working directory. + If expanduser is True, tilde-expansion is performed. + Note that Path instances always carry an absolute path. + Note also that passing in a local path object will simply return + the exact same path object. Use new() to get a new copy. + """ + if path is None: + self.strpath = error.checked_call(os.getcwd) + else: + try: + path = os.fspath(path) + except TypeError: + raise ValueError( + "can only pass None, Path instances " + "or non-empty strings to LocalPath" + ) + if expanduser: + path = os.path.expanduser(path) + self.strpath = abspath(path) + + if sys.platform != "win32": + + def chown(self, user, group, rec=0): + """Change ownership to the given user and group. + user and group may be specified by a number or + by a name. if rec is True change ownership + recursively. + """ + uid = getuserid(user) + gid = getgroupid(group) + if rec: + for x in self.visit(rec=lambda x: x.check(link=0)): + if x.check(link=0): + error.checked_call(os.chown, str(x), uid, gid) + error.checked_call(os.chown, str(self), uid, gid) + + def readlink(self) -> str: + """Return value of a symbolic link.""" + # https://github.com/python/mypy/issues/12278 + return error.checked_call(os.readlink, self.strpath) # type: ignore[arg-type,return-value] + + def mklinkto(self, oldname): + """Posix style hard link to another name.""" + error.checked_call(os.link, str(oldname), str(self)) + + def mksymlinkto(self, value, absolute=1): + """Create a symbolic link with the given value (pointing to another name).""" + if absolute: + error.checked_call(os.symlink, str(value), self.strpath) + else: + base = self.common(value) + # with posix local paths '/' is always a common base + relsource = self.__class__(value).relto(base) + reldest = self.relto(base) + n = reldest.count(self.sep) + target = self.sep.join(("..",) * n + (relsource,)) + error.checked_call(os.symlink, target, self.strpath) + + def __div__(self, other): + return self.join(os.fspath(other)) + + __truediv__ = __div__ # py3k + + @property + def basename(self): + """Basename part of path.""" + return self._getbyspec("basename")[0] + + @property + def dirname(self): + """Dirname part of path.""" + return self._getbyspec("dirname")[0] + + @property + def purebasename(self): + """Pure base name of the path.""" + return self._getbyspec("purebasename")[0] + + @property + def ext(self): + """Extension of the path (including the '.').""" + return self._getbyspec("ext")[0] + + def read_binary(self): + """Read and return a bytestring from reading the path.""" + with self.open("rb") as f: + return f.read() + + def read_text(self, encoding): + """Read and return a Unicode string from reading the path.""" + with self.open("r", encoding=encoding) as f: + return f.read() + + def read(self, mode="r"): + """Read and return a bytestring from reading the path.""" + with self.open(mode) as f: + return f.read() + + def readlines(self, cr=1): + """Read and return a list of lines from the path. if cr is False, the + newline will be removed from the end of each line.""" + mode = "r" + + if not cr: + content = self.read(mode) + return content.split("\n") + else: + f = self.open(mode) + try: + return f.readlines() + finally: + f.close() + + def load(self): + """(deprecated) return object unpickled from self.read()""" + f = self.open("rb") + try: + import pickle + + return error.checked_call(pickle.load, f) + finally: + f.close() + + def move(self, target): + """Move this path to target.""" + if target.relto(self): + raise error.EINVAL(target, "cannot move path into a subdirectory of itself") + try: + self.rename(target) + except error.EXDEV: # invalid cross-device link + self.copy(target) + self.remove() + + def fnmatch(self, pattern): + """Return true if the basename/fullname matches the glob-'pattern'. + + valid pattern characters:: + + * matches everything + ? matches any single character + [seq] matches any character in seq + [!seq] matches any char not in seq + + If the pattern contains a path-separator then the full path + is used for pattern matching and a '*' is prepended to the + pattern. + + if the pattern doesn't contain a path-separator the pattern + is only matched against the basename. + """ + return FNMatcher(pattern)(self) + + def relto(self, relpath): + """Return a string which is the relative part of the path + to the given 'relpath'. + """ + if not isinstance(relpath, (str, LocalPath)): + raise TypeError(f"{relpath!r}: not a string or path object") + strrelpath = str(relpath) + if strrelpath and strrelpath[-1] != self.sep: + strrelpath += self.sep + # assert strrelpath[-1] == self.sep + # assert strrelpath[-2] != self.sep + strself = self.strpath + if sys.platform == "win32" or getattr(os, "_name", None) == "nt": + if os.path.normcase(strself).startswith(os.path.normcase(strrelpath)): + return strself[len(strrelpath) :] + elif strself.startswith(strrelpath): + return strself[len(strrelpath) :] + return "" + + def ensure_dir(self, *args): + """Ensure the path joined with args is a directory.""" + return self.ensure(*args, **{"dir": True}) + + def bestrelpath(self, dest): + """Return a string which is a relative path from self + (assumed to be a directory) to dest such that + self.join(bestrelpath) == dest and if not such + path can be determined return dest. + """ + try: + if self == dest: + return os.curdir + base = self.common(dest) + if not base: # can be the case on windows + return str(dest) + self2base = self.relto(base) + reldest = dest.relto(base) + if self2base: + n = self2base.count(self.sep) + 1 + else: + n = 0 + lst = [os.pardir] * n + if reldest: + lst.append(reldest) + target = dest.sep.join(lst) + return target + except AttributeError: + return str(dest) + + def exists(self): + return self.check() + + def isdir(self): + return self.check(dir=1) + + def isfile(self): + return self.check(file=1) + + def parts(self, reverse=False): + """Return a root-first list of all ancestor directories + plus the path itself. + """ + current = self + lst = [self] + while 1: + last = current + current = current.dirpath() + if last == current: + break + lst.append(current) + if not reverse: + lst.reverse() + return lst + + def common(self, other): + """Return the common part shared with the other path + or None if there is no common part. + """ + last = None + for x, y in zip(self.parts(), other.parts()): + if x != y: + return last + last = x + return last + + def __add__(self, other): + """Return new path object with 'other' added to the basename""" + return self.new(basename=self.basename + str(other)) + + def visit(self, fil=None, rec=None, ignore=NeverRaised, bf=False, sort=False): + """Yields all paths below the current one + + fil is a filter (glob pattern or callable), if not matching the + path will not be yielded, defaulting to None (everything is + returned) + + rec is a filter (glob pattern or callable) that controls whether + a node is descended, defaulting to None + + ignore is an Exception class that is ignoredwhen calling dirlist() + on any of the paths (by default, all exceptions are reported) + + bf if True will cause a breadthfirst search instead of the + default depthfirst. Default: False + + sort if True will sort entries within each directory level. + """ + yield from Visitor(fil, rec, ignore, bf, sort).gen(self) + + def _sortlist(self, res, sort): + if sort: + if hasattr(sort, "__call__"): + warnings.warn( + DeprecationWarning( + "listdir(sort=callable) is deprecated and breaks on python3" + ), + stacklevel=3, + ) + res.sort(sort) + else: + res.sort() + + def __fspath__(self): + return self.strpath + + def __hash__(self): + s = self.strpath + if iswin32: + s = s.lower() + return hash(s) + + def __eq__(self, other): + s1 = os.fspath(self) + try: + s2 = os.fspath(other) + except TypeError: + return False + if iswin32: + s1 = s1.lower() + try: + s2 = s2.lower() + except AttributeError: + return False + return s1 == s2 + + def __ne__(self, other): + return not (self == other) + + def __lt__(self, other): + return os.fspath(self) < os.fspath(other) + + def __gt__(self, other): + return os.fspath(self) > os.fspath(other) + + def samefile(self, other): + """Return True if 'other' references the same file as 'self'.""" + other = os.fspath(other) + if not isabs(other): + other = abspath(other) + if self == other: + return True + if not hasattr(os.path, "samefile"): + return False + return error.checked_call(os.path.samefile, self.strpath, other) + + def remove(self, rec=1, ignore_errors=False): + """Remove a file or directory (or a directory tree if rec=1). + if ignore_errors is True, errors while removing directories will + be ignored. + """ + if self.check(dir=1, link=0): + if rec: + # force remove of readonly files on windows + if iswin32: + self.chmod(0o700, rec=1) + import shutil + + error.checked_call( + shutil.rmtree, self.strpath, ignore_errors=ignore_errors + ) + else: + error.checked_call(os.rmdir, self.strpath) + else: + if iswin32: + self.chmod(0o700) + error.checked_call(os.remove, self.strpath) + + def computehash(self, hashtype="md5", chunksize=524288): + """Return hexdigest of hashvalue for this file.""" + try: + try: + import hashlib as mod + except ImportError: + if hashtype == "sha1": + hashtype = "sha" + mod = __import__(hashtype) + hash = getattr(mod, hashtype)() + except (AttributeError, ImportError): + raise ValueError(f"Don't know how to compute {hashtype!r} hash") + f = self.open("rb") + try: + while 1: + buf = f.read(chunksize) + if not buf: + return hash.hexdigest() + hash.update(buf) + finally: + f.close() + + def new(self, **kw): + """Create a modified version of this path. + the following keyword arguments modify various path parts:: + + a:/some/path/to/a/file.ext + xx drive + xxxxxxxxxxxxxxxxx dirname + xxxxxxxx basename + xxxx purebasename + xxx ext + """ + obj = object.__new__(self.__class__) + if not kw: + obj.strpath = self.strpath + return obj + drive, dirname, basename, purebasename, ext = self._getbyspec( + "drive,dirname,basename,purebasename,ext" + ) + if "basename" in kw: + if "purebasename" in kw or "ext" in kw: + raise ValueError("invalid specification %r" % kw) + else: + pb = kw.setdefault("purebasename", purebasename) + try: + ext = kw["ext"] + except KeyError: + pass + else: + if ext and not ext.startswith("."): + ext = "." + ext + kw["basename"] = pb + ext + + if "dirname" in kw and not kw["dirname"]: + kw["dirname"] = drive + else: + kw.setdefault("dirname", dirname) + kw.setdefault("sep", self.sep) + obj.strpath = normpath("%(dirname)s%(sep)s%(basename)s" % kw) + return obj + + def _getbyspec(self, spec: str) -> list[str]: + """See new for what 'spec' can be.""" + res = [] + parts = self.strpath.split(self.sep) + + args = filter(None, spec.split(",")) + for name in args: + if name == "drive": + res.append(parts[0]) + elif name == "dirname": + res.append(self.sep.join(parts[:-1])) + else: + basename = parts[-1] + if name == "basename": + res.append(basename) + else: + i = basename.rfind(".") + if i == -1: + purebasename, ext = basename, "" + else: + purebasename, ext = basename[:i], basename[i:] + if name == "purebasename": + res.append(purebasename) + elif name == "ext": + res.append(ext) + else: + raise ValueError("invalid part specification %r" % name) + return res + + def dirpath(self, *args, **kwargs): + """Return the directory path joined with any given path arguments.""" + if not kwargs: + path = object.__new__(self.__class__) + path.strpath = dirname(self.strpath) + if args: + path = path.join(*args) + return path + return self.new(basename="").join(*args, **kwargs) + + def join(self, *args: os.PathLike[str], abs: bool = False) -> LocalPath: + """Return a new path by appending all 'args' as path + components. if abs=1 is used restart from root if any + of the args is an absolute path. + """ + sep = self.sep + strargs = [os.fspath(arg) for arg in args] + strpath = self.strpath + if abs: + newargs: list[str] = [] + for arg in reversed(strargs): + if isabs(arg): + strpath = arg + strargs = newargs + break + newargs.insert(0, arg) + # special case for when we have e.g. strpath == "/" + actual_sep = "" if strpath.endswith(sep) else sep + for arg in strargs: + arg = arg.strip(sep) + if iswin32: + # allow unix style paths even on windows. + arg = arg.strip("/") + arg = arg.replace("/", sep) + strpath = strpath + actual_sep + arg + actual_sep = sep + obj = object.__new__(self.__class__) + obj.strpath = normpath(strpath) + return obj + + def open(self, mode="r", ensure=False, encoding=None): + """Return an opened file with the given mode. + + If ensure is True, create parent directories if needed. + """ + if ensure: + self.dirpath().ensure(dir=1) + if encoding: + return error.checked_call(io.open, self.strpath, mode, encoding=encoding) + return error.checked_call(open, self.strpath, mode) + + def _fastjoin(self, name): + child = object.__new__(self.__class__) + child.strpath = self.strpath + self.sep + name + return child + + def islink(self): + return islink(self.strpath) + + def check(self, **kw): + """Check a path for existence and properties. + + Without arguments, return True if the path exists, otherwise False. + + valid checkers:: + + file=1 # is a file + file=0 # is not a file (may not even exist) + dir=1 # is a dir + link=1 # is a link + exists=1 # exists + + You can specify multiple checker definitions, for example:: + + path.check(file=1, link=1) # a link pointing to a file + """ + if not kw: + return exists(self.strpath) + if len(kw) == 1: + if "dir" in kw: + return not kw["dir"] ^ isdir(self.strpath) + if "file" in kw: + return not kw["file"] ^ isfile(self.strpath) + if not kw: + kw = {"exists": 1} + return Checkers(self)._evaluate(kw) + + _patternchars = set("*?[" + os.sep) + + def listdir(self, fil=None, sort=None): + """List directory contents, possibly filter by the given fil func + and possibly sorted. + """ + if fil is None and sort is None: + names = error.checked_call(os.listdir, self.strpath) + return map_as_list(self._fastjoin, names) + if isinstance(fil, str): + if not self._patternchars.intersection(fil): + child = self._fastjoin(fil) + if exists(child.strpath): + return [child] + return [] + fil = FNMatcher(fil) + names = error.checked_call(os.listdir, self.strpath) + res = [] + for name in names: + child = self._fastjoin(name) + if fil is None or fil(child): + res.append(child) + self._sortlist(res, sort) + return res + + def size(self) -> int: + """Return size of the underlying file object""" + return self.stat().size + + def mtime(self) -> float: + """Return last modification time of the path.""" + return self.stat().mtime + + def copy(self, target, mode=False, stat=False): + """Copy path to target. + + If mode is True, will copy copy permission from path to target. + If stat is True, copy permission, last modification + time, last access time, and flags from path to target. + """ + if self.check(file=1): + if target.check(dir=1): + target = target.join(self.basename) + assert self != target + copychunked(self, target) + if mode: + copymode(self.strpath, target.strpath) + if stat: + copystat(self, target) + else: + + def rec(p): + return p.check(link=0) + + for x in self.visit(rec=rec): + relpath = x.relto(self) + newx = target.join(relpath) + newx.dirpath().ensure(dir=1) + if x.check(link=1): + newx.mksymlinkto(x.readlink()) + continue + elif x.check(file=1): + copychunked(x, newx) + elif x.check(dir=1): + newx.ensure(dir=1) + if mode: + copymode(x.strpath, newx.strpath) + if stat: + copystat(x, newx) + + def rename(self, target): + """Rename this path to target.""" + target = os.fspath(target) + return error.checked_call(os.rename, self.strpath, target) + + def dump(self, obj, bin=1): + """Pickle object into path location""" + f = self.open("wb") + import pickle + + try: + error.checked_call(pickle.dump, obj, f, bin) + finally: + f.close() + + def mkdir(self, *args): + """Create & return the directory joined with args.""" + p = self.join(*args) + error.checked_call(os.mkdir, os.fspath(p)) + return p + + def write_binary(self, data, ensure=False): + """Write binary data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("wb") as f: + f.write(data) + + def write_text(self, data, encoding, ensure=False): + """Write text data into path using the specified encoding. + If ensure is True create missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + with self.open("w", encoding=encoding) as f: + f.write(data) + + def write(self, data, mode="w", ensure=False): + """Write data into path. If ensure is True create + missing parent directories. + """ + if ensure: + self.dirpath().ensure(dir=1) + if "b" in mode: + if not isinstance(data, bytes): + raise ValueError("can only process bytes") + else: + if not isinstance(data, str): + if not isinstance(data, bytes): + data = str(data) + else: + data = data.decode(sys.getdefaultencoding()) + f = self.open(mode) + try: + f.write(data) + finally: + f.close() + + def _ensuredirs(self): + parent = self.dirpath() + if parent == self: + return self + if parent.check(dir=0): + parent._ensuredirs() + if self.check(dir=0): + try: + self.mkdir() + except error.EEXIST: + # race condition: file/dir created by another thread/process. + # complain if it is not a dir + if self.check(dir=0): + raise + return self + + def ensure(self, *args, **kwargs): + """Ensure that an args-joined path exists (by default as + a file). if you specify a keyword argument 'dir=True' + then the path is forced to be a directory path. + """ + p = self.join(*args) + if kwargs.get("dir", 0): + return p._ensuredirs() + else: + p.dirpath()._ensuredirs() + if not p.check(file=1): + p.open("wb").close() + return p + + @overload + def stat(self, raising: Literal[True] = ...) -> Stat: + ... + + @overload + def stat(self, raising: Literal[False]) -> Stat | None: + ... + + def stat(self, raising: bool = True) -> Stat | None: + """Return an os.stat() tuple.""" + if raising: + return Stat(self, error.checked_call(os.stat, self.strpath)) + try: + return Stat(self, os.stat(self.strpath)) + except KeyboardInterrupt: + raise + except Exception: + return None + + def lstat(self) -> Stat: + """Return an os.lstat() tuple.""" + return Stat(self, error.checked_call(os.lstat, self.strpath)) + + def setmtime(self, mtime=None): + """Set modification time for the given path. if 'mtime' is None + (the default) then the file's mtime is set to current time. + + Note that the resolution for 'mtime' is platform dependent. + """ + if mtime is None: + return error.checked_call(os.utime, self.strpath, mtime) + try: + return error.checked_call(os.utime, self.strpath, (-1, mtime)) + except error.EINVAL: + return error.checked_call(os.utime, self.strpath, (self.atime(), mtime)) + + def chdir(self): + """Change directory to self and return old current directory""" + try: + old = self.__class__() + except error.ENOENT: + old = None + error.checked_call(os.chdir, self.strpath) + return old + + @contextmanager + def as_cwd(self): + """ + Return a context manager, which changes to the path's dir during the + managed "with" context. + On __enter__ it returns the old dir, which might be ``None``. + """ + old = self.chdir() + try: + yield old + finally: + if old is not None: + old.chdir() + + def realpath(self): + """Return a new path which contains no symbolic links.""" + return self.__class__(os.path.realpath(self.strpath)) + + def atime(self): + """Return last access time of the path.""" + return self.stat().atime + + def __repr__(self): + return "local(%r)" % self.strpath + + def __str__(self): + """Return string representation of the Path.""" + return self.strpath + + def chmod(self, mode, rec=0): + """Change permissions to the given mode. If mode is an + integer it directly encodes the os-specific modes. + if rec is True perform recursively. + """ + if not isinstance(mode, int): + raise TypeError(f"mode {mode!r} must be an integer") + if rec: + for x in self.visit(rec=rec): + error.checked_call(os.chmod, str(x), mode) + error.checked_call(os.chmod, self.strpath, mode) + + def pypkgpath(self): + """Return the Python package path by looking for the last + directory upwards which still contains an __init__.py. + Return None if a pkgpath can not be determined. + """ + pkgpath = None + for parent in self.parts(reverse=True): + if parent.isdir(): + if not parent.join("__init__.py").exists(): + break + if not isimportable(parent.basename): + break + pkgpath = parent + return pkgpath + + def _ensuresyspath(self, ensuremode, path): + if ensuremode: + s = str(path) + if ensuremode == "append": + if s not in sys.path: + sys.path.append(s) + else: + if s != sys.path[0]: + sys.path.insert(0, s) + + def pyimport(self, modname=None, ensuresyspath=True): + """Return path as an imported python module. + + If modname is None, look for the containing package + and construct an according module name. + The module will be put/looked up in sys.modules. + if ensuresyspath is True then the root dir for importing + the file (taking __init__.py files into account) will + be prepended to sys.path if it isn't there already. + If ensuresyspath=="append" the root dir will be appended + if it isn't already contained in sys.path. + if ensuresyspath is False no modification of syspath happens. + + Special value of ensuresyspath=="importlib" is intended + purely for using in pytest, it is capable only of importing + separate .py files outside packages, e.g. for test suite + without any __init__.py file. It effectively allows having + same-named test modules in different places and offers + mild opt-in via this option. Note that it works only in + recent versions of python. + """ + if not self.check(): + raise error.ENOENT(self) + + if ensuresyspath == "importlib": + if modname is None: + modname = self.purebasename + spec = importlib.util.spec_from_file_location(modname, str(self)) + if spec is None or spec.loader is None: + raise ImportError( + f"Can't find module {modname} at location {str(self)}" + ) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + return mod + + pkgpath = None + if modname is None: + pkgpath = self.pypkgpath() + if pkgpath is not None: + pkgroot = pkgpath.dirpath() + names = self.new(ext="").relto(pkgroot).split(self.sep) + if names[-1] == "__init__": + names.pop() + modname = ".".join(names) + else: + pkgroot = self.dirpath() + modname = self.purebasename + + self._ensuresyspath(ensuresyspath, pkgroot) + __import__(modname) + mod = sys.modules[modname] + if self.basename == "__init__.py": + return mod # we don't check anything as we might + # be in a namespace package ... too icky to check + modfile = mod.__file__ + assert modfile is not None + if modfile[-4:] in (".pyc", ".pyo"): + modfile = modfile[:-1] + elif modfile.endswith("$py.class"): + modfile = modfile[:-9] + ".py" + if modfile.endswith(os.sep + "__init__.py"): + if self.basename != "__init__.py": + modfile = modfile[:-12] + try: + issame = self.samefile(modfile) + except error.ENOENT: + issame = False + if not issame: + ignore = os.getenv("PY_IGNORE_IMPORTMISMATCH") + if ignore != "1": + raise self.ImportMismatchError(modname, modfile, self) + return mod + else: + try: + return sys.modules[modname] + except KeyError: + # we have a custom modname, do a pseudo-import + import types + + mod = types.ModuleType(modname) + mod.__file__ = str(self) + sys.modules[modname] = mod + try: + with open(str(self), "rb") as f: + exec(f.read(), mod.__dict__) + except BaseException: + del sys.modules[modname] + raise + return mod + + def sysexec(self, *argv: os.PathLike[str], **popen_opts: Any) -> str: + """Return stdout text from executing a system child process, + where the 'self' path points to executable. + The process is directly invoked and not through a system shell. + """ + from subprocess import Popen, PIPE + + popen_opts.pop("stdout", None) + popen_opts.pop("stderr", None) + proc = Popen( + [str(self)] + [str(arg) for arg in argv], + **popen_opts, + stdout=PIPE, + stderr=PIPE, + ) + stdout: str | bytes + stdout, stderr = proc.communicate() + ret = proc.wait() + if isinstance(stdout, bytes): + stdout = stdout.decode(sys.getdefaultencoding()) + if ret != 0: + if isinstance(stderr, bytes): + stderr = stderr.decode(sys.getdefaultencoding()) + raise RuntimeError( + ret, + ret, + str(self), + stdout, + stderr, + ) + return stdout + + @classmethod + def sysfind(cls, name, checker=None, paths=None): + """Return a path object found by looking at the systems + underlying PATH specification. If the checker is not None + it will be invoked to filter matching paths. If a binary + cannot be found, None is returned + Note: This is probably not working on plain win32 systems + but may work on cygwin. + """ + if isabs(name): + p = local(name) + if p.check(file=1): + return p + else: + if paths is None: + if iswin32: + paths = os.environ["Path"].split(";") + if "" not in paths and "." not in paths: + paths.append(".") + try: + systemroot = os.environ["SYSTEMROOT"] + except KeyError: + pass + else: + paths = [ + path.replace("%SystemRoot%", systemroot) for path in paths + ] + else: + paths = os.environ["PATH"].split(":") + tryadd = [] + if iswin32: + tryadd += os.environ["PATHEXT"].split(os.pathsep) + tryadd.append("") + + for x in paths: + for addext in tryadd: + p = local(x).join(name, abs=True) + addext + try: + if p.check(file=1): + if checker: + if not checker(p): + continue + return p + except error.EACCES: + pass + return None + + @classmethod + def _gethomedir(cls): + try: + x = os.environ["HOME"] + except KeyError: + try: + x = os.environ["HOMEDRIVE"] + os.environ["HOMEPATH"] + except KeyError: + return None + return cls(x) + + # """ + # special class constructors for local filesystem paths + # """ + @classmethod + def get_temproot(cls): + """Return the system's temporary directory + (where tempfiles are usually created in) + """ + import tempfile + + return local(tempfile.gettempdir()) + + @classmethod + def mkdtemp(cls, rootdir=None): + """Return a Path object pointing to a fresh new temporary directory + (which we created ourself). + """ + import tempfile + + if rootdir is None: + rootdir = cls.get_temproot() + return cls(error.checked_call(tempfile.mkdtemp, dir=str(rootdir))) + + @classmethod + def make_numbered_dir( + cls, prefix="session-", rootdir=None, keep=3, lock_timeout=172800 + ): # two days + """Return unique directory with a number greater than the current + maximum one. The number is assumed to start directly after prefix. + if keep is true directories with a number less than (maxnum-keep) + will be removed. If .lock files are used (lock_timeout non-zero), + algorithm is multi-process safe. + """ + if rootdir is None: + rootdir = cls.get_temproot() + + nprefix = prefix.lower() + + def parse_num(path): + """Parse the number out of a path (if it matches the prefix)""" + nbasename = path.basename.lower() + if nbasename.startswith(nprefix): + try: + return int(nbasename[len(nprefix) :]) + except ValueError: + pass + + def create_lockfile(path): + """Exclusively create lockfile. Throws when failed""" + mypid = os.getpid() + lockfile = path.join(".lock") + if hasattr(lockfile, "mksymlinkto"): + lockfile.mksymlinkto(str(mypid)) + else: + fd = error.checked_call( + os.open, str(lockfile), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o644 + ) + with os.fdopen(fd, "w") as f: + f.write(str(mypid)) + return lockfile + + def atexit_remove_lockfile(lockfile): + """Ensure lockfile is removed at process exit""" + mypid = os.getpid() + + def try_remove_lockfile(): + # in a fork() situation, only the last process should + # remove the .lock, otherwise the other processes run the + # risk of seeing their temporary dir disappear. For now + # we remove the .lock in the parent only (i.e. we assume + # that the children finish before the parent). + if os.getpid() != mypid: + return + try: + lockfile.remove() + except error.Error: + pass + + atexit.register(try_remove_lockfile) + + # compute the maximum number currently in use with the prefix + lastmax = None + while True: + maxnum = -1 + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None: + maxnum = max(maxnum, num) + + # make the new directory + try: + udir = rootdir.mkdir(prefix + str(maxnum + 1)) + if lock_timeout: + lockfile = create_lockfile(udir) + atexit_remove_lockfile(lockfile) + except (error.EEXIST, error.ENOENT, error.EBUSY): + # race condition (1): another thread/process created the dir + # in the meantime - try again + # race condition (2): another thread/process spuriously acquired + # lock treating empty directory as candidate + # for removal - try again + # race condition (3): another thread/process tried to create the lock at + # the same time (happened in Python 3.3 on Windows) + # https://ci.appveyor.com/project/pytestbot/py/build/1.0.21/job/ffi85j4c0lqwsfwa + if lastmax == maxnum: + raise + lastmax = maxnum + continue + break + + def get_mtime(path): + """Read file modification time""" + try: + return path.lstat().mtime + except error.Error: + pass + + garbage_prefix = prefix + "garbage-" + + def is_garbage(path): + """Check if path denotes directory scheduled for removal""" + bn = path.basename + return bn.startswith(garbage_prefix) + + # prune old directories + udir_time = get_mtime(udir) + if keep and udir_time: + for path in rootdir.listdir(): + num = parse_num(path) + if num is not None and num <= (maxnum - keep): + try: + # try acquiring lock to remove directory as exclusive user + if lock_timeout: + create_lockfile(path) + except (error.EEXIST, error.ENOENT, error.EBUSY): + path_time = get_mtime(path) + if not path_time: + # assume directory doesn't exist now + continue + if abs(udir_time - path_time) < lock_timeout: + # assume directory with lockfile exists + # and lock timeout hasn't expired yet + continue + + # path dir locked for exclusive use + # and scheduled for removal to avoid another thread/process + # treating it as a new directory or removal candidate + garbage_path = rootdir.join(garbage_prefix + str(uuid.uuid4())) + try: + path.rename(garbage_path) + garbage_path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + if is_garbage(path): + try: + path.remove(rec=1) + except KeyboardInterrupt: + raise + except Exception: # this might be error.Error, WindowsError ... + pass + + # make link... + try: + username = os.environ["USER"] # linux, et al + except KeyError: + try: + username = os.environ["USERNAME"] # windows + except KeyError: + username = "current" + + src = str(udir) + dest = src[: src.rfind("-")] + "-" + username + try: + os.unlink(dest) + except OSError: + pass + try: + os.symlink(src, dest) + except (OSError, AttributeError, NotImplementedError): + pass + + return udir + + +def copymode(src, dest): + """Copy permission from src to dst.""" + import shutil + + shutil.copymode(src, dest) + + +def copystat(src, dest): + """Copy permission, last modification time, + last access time, and flags from src to dst.""" + import shutil + + shutil.copystat(str(src), str(dest)) + + +def copychunked(src, dest): + chunksize = 524288 # half a meg of bytes + fsrc = src.open("rb") + try: + fdest = dest.open("wb") + try: + while 1: + buf = fsrc.read(chunksize) + if not buf: + break + fdest.write(buf) + finally: + fdest.close() + finally: + fsrc.close() + + +def isimportable(name): + if name and (name[0].isalpha() or name[0] == "_"): + name = name.replace("_", "") + return not name or name.isalnum() + + +local = LocalPath diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 63f9dd8f2..ab83fee32 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -44,10 +44,20 @@ from _pytest.stash import StashKey if TYPE_CHECKING: from _pytest.assertion import AssertionState +if sys.version_info >= (3, 8): + namedExpr = ast.NamedExpr + astNameConstant = ast.Constant + astStr = ast.Constant + astNum = ast.Constant +else: + namedExpr = ast.Expr + astNameConstant = ast.NameConstant + astStr = ast.Str + astNum = ast.Num + assertstate_key = StashKey["AssertionState"]() - # pytest caches rewritten pycs in pycache dirs PYTEST_TAG = f"{sys.implementation.cache_tag}-pytest-{version}" PYC_EXT = ".py" + (__debug__ and "c" or "o") @@ -180,7 +190,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]) @@ -274,8 +284,12 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader) return f.read() if sys.version_info >= (3, 10): + if sys.version_info >= (3, 12): + from importlib.resources.abc import TraversableResources + else: + from importlib.abc import TraversableResources - def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore + def get_resource_reader(self, name: str) -> TraversableResources: # type: ignore if sys.version_info < (3, 11): from importlib.readers import FileReader else: @@ -631,8 +645,12 @@ class AssertionRewriter(ast.NodeVisitor): .push_format_context() and .pop_format_context() which allows to build another %-formatted string while already building one. - This state is reset on every new assert statement visited and used - by the other visitors. + :variables_overwrite: A dict filled with references to variables + that change value within an assert. This happens when a variable is + reassigned with the walrus operator + + This state, except the variables_overwrite, is reset on every new assert + statement visited and used by the other visitors. """ def __init__( @@ -648,6 +666,7 @@ class AssertionRewriter(ast.NodeVisitor): else: self.enable_assertion_pass_hook = False self.source = source + self.variables_overwrite: Dict[str, str] = {} def run(self, mod: ast.Module) -> None: """Find all assert statements in *mod* and rewrite them.""" @@ -662,14 +681,17 @@ class AssertionRewriter(ast.NodeVisitor): if doc is not None and self.is_rewrite_disabled(doc): return pos = 0 - lineno = 1 + item = None for item in mod.body: if ( expect_docstring and isinstance(item, ast.Expr) - and isinstance(item.value, ast.Str) + and isinstance(item.value, astStr) ): - doc = item.value.s + if sys.version_info >= (3, 8): + doc = item.value.value + else: + doc = item.value.s if self.is_rewrite_disabled(doc): return expect_docstring = False @@ -801,7 +823,7 @@ class AssertionRewriter(ast.NodeVisitor): current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys = [ast.Str(key) for key in current.keys()] + keys = [astStr(key) for key in current.keys()] format_dict = ast.Dict(keys, list(current.values())) form = ast.BinOp(expl_expr, ast.Mod(), format_dict) name = "@py_format" + str(next(self.variable_counter)) @@ -855,16 +877,16 @@ class AssertionRewriter(ast.NodeVisitor): negation = ast.UnaryOp(ast.Not(), top_condition) if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook - msg = self.pop_format_context(ast.Str(explanation)) + msg = self.pop_format_context(astStr(explanation)) # Failed if assert_.msg: assertmsg = self.helper("_format_assertmsg", assert_.msg) gluestr = "\n>assert " else: - assertmsg = ast.Str("") + assertmsg = astStr("") gluestr = "assert " - err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg) + err_explanation = ast.BinOp(astStr(gluestr), ast.Add(), msg) err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) err_name = ast.Name("AssertionError", ast.Load()) fmt = self.helper("_format_explanation", err_msg) @@ -880,8 +902,8 @@ class AssertionRewriter(ast.NodeVisitor): hook_call_pass = ast.Expr( self.helper( "_call_assertion_pass", - ast.Num(assert_.lineno), - ast.Str(orig), + astNum(assert_.lineno), + astStr(orig), fmt_pass, ) ) @@ -900,7 +922,7 @@ class AssertionRewriter(ast.NodeVisitor): variables = [ ast.Name(name, ast.Store()) for name in self.format_variables ] - clear_format = ast.Assign(variables, ast.NameConstant(None)) + clear_format = ast.Assign(variables, astNameConstant(None)) self.statements.append(clear_format) else: # Original assertion rewriting @@ -911,9 +933,9 @@ class AssertionRewriter(ast.NodeVisitor): assertmsg = self.helper("_format_assertmsg", assert_.msg) explanation = "\n>assert " + explanation else: - assertmsg = ast.Str("") + assertmsg = astStr("") explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) + template = ast.BinOp(assertmsg, ast.Add(), astStr(explanation)) msg = self.pop_format_context(template) fmt = self.helper("_format_explanation", msg) err_name = ast.Name("AssertionError", ast.Load()) @@ -925,7 +947,7 @@ class AssertionRewriter(ast.NodeVisitor): # Clear temporary variables by setting them to None. if self.variables: variables = [ast.Name(name, ast.Store()) for name in self.variables] - clear = ast.Assign(variables, ast.NameConstant(None)) + clear = ast.Assign(variables, astNameConstant(None)) self.statements.append(clear) # Fix locations (line numbers/column offsets). for stmt in self.statements: @@ -933,14 +955,26 @@ class AssertionRewriter(ast.NodeVisitor): ast.copy_location(node, assert_) return self.statements + def visit_NamedExpr(self, name: namedExpr) -> Tuple[namedExpr, str]: + # This method handles the 'walrus operator' repr of the target + # name if it's a local variable or _should_repr_global_name() + # thinks it's acceptable. + locs = ast.Call(self.builtin("locals"), [], []) + target_id = name.target.id # type: ignore[attr-defined] + inlocs = ast.Compare(astStr(target_id), [ast.In()], [locs]) + dorepr = self.helper("_should_repr_global_name", name) + test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) + expr = ast.IfExp(test, self.display(name), astStr(target_id)) + return name, self.explanation_param(expr) + def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) - inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) + inlocs = ast.Compare(astStr(name.id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) + expr = ast.IfExp(test, self.display(name), astStr(name.id)) return name, self.explanation_param(expr) def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: @@ -959,10 +993,26 @@ class AssertionRewriter(ast.NodeVisitor): # cond is set in a prior loop iteration below self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa self.expl_stmts = fail_inner + # Check if the left operand is a namedExpr and the value has already been visited + if ( + isinstance(v, ast.Compare) + and isinstance(v.left, namedExpr) + and v.left.target.id + in [ + ast_expr.id + for ast_expr in boolop.values[:i] + if hasattr(ast_expr, "id") + ] + ): + pytest_temp = self.variable() + self.variables_overwrite[ + v.left.target.id + ] = v.left # type:ignore[assignment] + v.left.target.id = pytest_temp self.push_format_context() res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) - expl_format = self.pop_format_context(ast.Str(expl)) + expl_format = self.pop_format_context(astStr(expl)) call = ast.Call(app, [expl_format], []) self.expl_stmts.append(ast.Expr(call)) if i < levels: @@ -974,7 +1024,7 @@ class AssertionRewriter(ast.NodeVisitor): self.statements = body = inner self.statements = save self.expl_stmts = fail_save - expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) + expl_template = self.helper("_format_boolop", expl_list, astNum(is_or)) expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) @@ -998,10 +1048,19 @@ class AssertionRewriter(ast.NodeVisitor): new_args = [] new_kwargs = [] for arg in call.args: + if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite: + arg = self.variables_overwrite[arg.id] # type:ignore[assignment] res, expl = self.visit(arg) arg_expls.append(expl) new_args.append(res) for keyword in call.keywords: + if ( + isinstance(keyword.value, ast.Name) + and keyword.value.id in self.variables_overwrite + ): + keyword.value = self.variables_overwrite[ + keyword.value.id + ] # type:ignore[assignment] res, expl = self.visit(keyword.value) new_kwargs.append(ast.keyword(keyword.arg, res)) if keyword.arg: @@ -1034,6 +1093,15 @@ class AssertionRewriter(ast.NodeVisitor): def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: self.push_format_context() + # We first check if we have overwritten a variable in the previous assert + if isinstance(comp.left, ast.Name) and comp.left.id in self.variables_overwrite: + comp.left = self.variables_overwrite[ + comp.left.id + ] # type:ignore[assignment] + if isinstance(comp.left, namedExpr): + self.variables_overwrite[ + comp.left.target.id + ] = comp.left # type:ignore[assignment] left_res, left_expl = self.visit(comp.left) if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" @@ -1045,14 +1113,23 @@ class AssertionRewriter(ast.NodeVisitor): syms = [] results = [left_res] for i, op, next_operand in it: + if ( + isinstance(next_operand, namedExpr) + and isinstance(left_res, ast.Name) + and next_operand.target.id == left_res.id + ): + next_operand.target.id = self.variable() + self.variables_overwrite[ + left_res.id + ] = next_operand # type:ignore[assignment] next_res, next_expl = self.visit(next_operand) if isinstance(next_operand, (ast.Compare, ast.BoolOp)): next_expl = f"({next_expl})" results.append(next_res) sym = BINOP_MAP[op.__class__] - syms.append(ast.Str(sym)) + syms.append(astStr(sym)) expl = f"{left_expl} {sym} {next_expl}" - expls.append(ast.Str(expl)) + expls.append(astStr(expl)) res_expr = ast.Compare(left_res, [op], [next_res]) self.statements.append(ast.Assign([store_names[i]], res_expr)) left_res, left_expl = next_res, next_expl @@ -1068,6 +1145,7 @@ class AssertionRewriter(ast.NodeVisitor): res: ast.expr = ast.BoolOp(ast.And(), load_names) else: res = load_names[0] + return res, self.explanation_param(self.pop_format_context(expl_call)) diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index ce148dca0..dfd6f65d2 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -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): diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 777c1b0b0..855716d81 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -1,6 +1,7 @@ """Implementation of the cache provider.""" # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. +import dataclasses import json import os from pathlib import Path @@ -12,8 +13,6 @@ from typing import Optional from typing import Set from typing import Union -import attr - from .pathlib import resolve_from_str from .pathlib import rm_rf from .reports import CollectReport @@ -28,11 +27,10 @@ from _pytest.deprecated import check_ispytest from _pytest.fixtures import fixture from _pytest.fixtures import FixtureRequest from _pytest.main import Session -from _pytest.python import Module +from _pytest.nodes import File from _pytest.python import Package from _pytest.reports import TestReport - README_CONTENT = """\ # pytest cache directory # @@ -53,10 +51,12 @@ Signature: 8a477f597d28d172789f06886806bc55 @final -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class Cache: - _cachedir: Path = attr.ib(repr=False) - _config: Config = attr.ib(repr=False) + """Instance of the `cache` fixture.""" + + _cachedir: Path = dataclasses.field(repr=False) + _config: Config = dataclasses.field(repr=False) # Sub-directory under cache-dir for directories created by `mkdir()`. _CACHE_PREFIX_DIRS = "d" @@ -179,16 +179,22 @@ class Cache: else: cache_dir_exists_already = self._cachedir.exists() path.parent.mkdir(exist_ok=True, parents=True) - except OSError: - self.warn("could not create cache path {path}", path=path, _ispytest=True) + except OSError as exc: + self.warn( + f"could not create cache path {path}: {exc}", + _ispytest=True, + ) return if not cache_dir_exists_already: self._ensure_supporting_files() data = json.dumps(value, ensure_ascii=False, indent=2) try: f = path.open("w", encoding="UTF-8") - except OSError: - self.warn("cache could not write path {path}", path=path, _ispytest=True) + except OSError as exc: + self.warn( + f"cache could not write path {path}: {exc}", + _ispytest=True, + ) else: with f: f.write(data) @@ -213,22 +219,30 @@ class LFPluginCollWrapper: @hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector: nodes.Collector): - if isinstance(collector, Session): + if isinstance(collector, (Session, Package)): out = yield res: CollectReport = out.get_result() # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths + # Use stable sort to priorize last failed. + def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: + # Package.path is the __init__.py file, we need the directory. + if isinstance(node, Package): + path = node.path.parent + else: + path = node.path + return path in lf_paths + res.result = sorted( res.result, - # use stable sort to priorize last failed - key=lambda x: x.path in lf_paths, + key=sort_key, reverse=True, ) return - elif isinstance(collector, Module): + elif isinstance(collector, File): if collector.path in self.lfplugin._last_failed_paths: out = yield res = out.get_result() @@ -266,10 +280,9 @@ class LFPluginCollSkipfiles: def pytest_make_collect_report( self, collector: nodes.Collector ) -> Optional[CollectReport]: - # Packages are Modules, but _last_failed_paths only contains - # test-bearing paths and doesn't try to include the paths of their - # packages, so don't filter them. - if isinstance(collector, Module) and not isinstance(collector, Package): + # Packages are Files, but we only want to skip test-bearing Files, + # so don't filter Packages. + if isinstance(collector, File) and not isinstance(collector, Package): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -299,9 +312,14 @@ class LFPlugin: ) def get_last_failed_paths(self) -> Set[Path]: - """Return a set with all Paths()s of the previously failed nodeids.""" + """Return a set with all Paths of the previously failed nodeids and + their parents.""" rootpath = self.config.rootpath - result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed} + result = set() + for nodeid in self.lastfailed: + path = rootpath / nodeid.split("::")[0] + result.add(path) + result.update(path.parents) return {x for x in result if x.exists()} def pytest_report_collectionfinish(self) -> Optional[str]: @@ -492,7 +510,7 @@ def pytest_addoption(parser: Parser) -> None: def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: - if config.option.cacheshow: + if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session return wrap_session(config, cacheshow) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 6131a46df..a8ca0869f 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -1,19 +1,26 @@ """Per-test stdout/stderr capturing mechanism.""" +import abc +import collections import contextlib -import functools import io import os import sys from io import UnsupportedOperation from tempfile import TemporaryFile +from types import TracebackType from typing import Any from typing import AnyStr +from typing import BinaryIO from typing import Generator from typing import Generic +from typing import Iterable from typing import Iterator +from typing import List +from typing import NamedTuple from typing import Optional from typing import TextIO from typing import Tuple +from typing import Type from typing import TYPE_CHECKING from typing import Union @@ -29,6 +36,7 @@ from _pytest.nodes import File from _pytest.nodes import Item if TYPE_CHECKING: + from typing_extensions import Final from typing_extensions import Literal _CaptureMethod = Literal["fd", "sys", "no", "tee-sys"] @@ -185,19 +193,27 @@ class TeeCaptureIO(CaptureIO): return self._other.write(s) -class DontReadFromInput: - encoding = None +class DontReadFromInput(TextIO): + @property + def encoding(self) -> str: + return sys.__stdin__.encoding - def read(self, *args): + def read(self, size: int = -1) -> str: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) readline = read - readlines = read - __next__ = read - def __iter__(self): + def __next__(self) -> str: + return self.readline() + + def readlines(self, hint: Optional[int] = -1) -> List[str]: + raise OSError( + "pytest: reading from stdin while output is captured! Consider using `-s`." + ) + + def __iter__(self) -> Iterator[str]: return self def fileno(self) -> int: @@ -215,7 +231,7 @@ class DontReadFromInput: def readable(self) -> bool: return False - def seek(self, offset: int) -> int: + def seek(self, offset: int, whence: int = 0) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no seek(int)") def seekable(self) -> bool: @@ -224,41 +240,104 @@ class DontReadFromInput: def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - def truncate(self, size: int) -> None: - raise UnsupportedOperation("cannont truncate stdin") + def truncate(self, size: Optional[int] = None) -> int: + raise UnsupportedOperation("cannot truncate stdin") - def write(self, *args) -> None: + def write(self, data: str) -> int: raise UnsupportedOperation("cannot write to stdin") - def writelines(self, *args) -> None: + def writelines(self, lines: Iterable[str]) -> None: raise UnsupportedOperation("Cannot write to stdin") def writable(self) -> bool: return False - @property - def buffer(self): + def __enter__(self) -> "DontReadFromInput": return self + def __exit__( + self, + type: Optional[Type[BaseException]], + value: Optional[BaseException], + traceback: Optional[TracebackType], + ) -> None: + pass + + @property + def buffer(self) -> BinaryIO: + # The str/bytes doesn't actually matter in this type, so OK to fake. + return self # type: ignore[return-value] + # Capture classes. +class CaptureBase(abc.ABC, Generic[AnyStr]): + EMPTY_BUFFER: AnyStr + + @abc.abstractmethod + def __init__(self, fd: int) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def start(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def done(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def suspend(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def resume(self) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def writeorg(self, data: AnyStr) -> None: + raise NotImplementedError() + + @abc.abstractmethod + def snap(self) -> AnyStr: + raise NotImplementedError() + + patchsysdict = {0: "stdin", 1: "stdout", 2: "stderr"} -class NoCapture: - EMPTY_BUFFER = None - __init__ = start = done = suspend = resume = lambda *args: None +class NoCapture(CaptureBase[str]): + EMPTY_BUFFER = "" + + def __init__(self, fd: int) -> None: + pass + + def start(self) -> None: + pass + + def done(self) -> None: + pass + + def suspend(self) -> None: + pass + + def resume(self) -> None: + pass + + def snap(self) -> str: + return "" + + def writeorg(self, data: str) -> None: + pass -class SysCaptureBinary: - - EMPTY_BUFFER = b"" - - def __init__(self, fd: int, tmpfile=None, *, tee: bool = False) -> None: +class SysCaptureBase(CaptureBase[AnyStr]): + def __init__( + self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + ) -> None: name = patchsysdict[fd] - self._old = getattr(sys, name) + self._old: TextIO = getattr(sys, name) self.name = name if tmpfile is None: if name == "stdin": @@ -298,14 +377,6 @@ class SysCaptureBinary: setattr(sys, self.name, self.tmpfile) self._state = "started" - def snap(self): - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.buffer.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - def done(self) -> None: self._assert_state("done", ("initialized", "started", "suspended", "done")) if self._state == "done": @@ -327,36 +398,43 @@ class SysCaptureBinary: setattr(sys, self.name, self.tmpfile) self._state = "started" - def writeorg(self, data) -> None: + +class SysCaptureBinary(SysCaptureBase[bytes]): + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: bytes) -> None: self._assert_state("writeorg", ("started", "suspended")) self._old.flush() self._old.buffer.write(data) self._old.buffer.flush() -class SysCapture(SysCaptureBinary): - EMPTY_BUFFER = "" # type: ignore[assignment] +class SysCapture(SysCaptureBase[str]): + EMPTY_BUFFER = "" - def snap(self): + def snap(self) -> str: + self._assert_state("snap", ("started", "suspended")) + assert isinstance(self.tmpfile, CaptureIO) res = self.tmpfile.getvalue() self.tmpfile.seek(0) self.tmpfile.truncate() return res - def writeorg(self, data): + def writeorg(self, data: str) -> None: self._assert_state("writeorg", ("started", "suspended")) self._old.write(data) self._old.flush() -class FDCaptureBinary: - """Capture IO to/from a given OS-level file descriptor. - - snap() produces `bytes`. - """ - - EMPTY_BUFFER = b"" - +class FDCaptureBase(CaptureBase[AnyStr]): def __init__(self, targetfd: int) -> None: self.targetfd = targetfd @@ -382,7 +460,7 @@ class FDCaptureBinary: if targetfd == 0: self.tmpfile = open(os.devnull, encoding="utf-8") - self.syscapture = SysCapture(targetfd) + self.syscapture: CaptureBase[str] = SysCapture(targetfd) else: self.tmpfile = EncodedFile( TemporaryFile(buffering=0), @@ -394,7 +472,7 @@ class FDCaptureBinary: if targetfd in patchsysdict: self.syscapture = SysCapture(targetfd, self.tmpfile) else: - self.syscapture = NoCapture() + self.syscapture = NoCapture(targetfd) self._state = "initialized" @@ -421,14 +499,6 @@ class FDCaptureBinary: self.syscapture.start() self._state = "started" - def snap(self): - self._assert_state("snap", ("started", "suspended")) - self.tmpfile.seek(0) - res = self.tmpfile.buffer.read() - self.tmpfile.seek(0) - self.tmpfile.truncate() - return res - def done(self) -> None: """Stop capturing, restore streams, return original capture file, seeked to position zero.""" @@ -461,22 +531,38 @@ class FDCaptureBinary: os.dup2(self.tmpfile.fileno(), self.targetfd) self._state = "started" - def writeorg(self, data): + +class FDCaptureBinary(FDCaptureBase[bytes]): + """Capture IO to/from a given OS-level file descriptor. + + snap() produces `bytes`. + """ + + EMPTY_BUFFER = b"" + + def snap(self) -> bytes: + self._assert_state("snap", ("started", "suspended")) + self.tmpfile.seek(0) + res = self.tmpfile.buffer.read() + self.tmpfile.seek(0) + self.tmpfile.truncate() + return res + + def writeorg(self, data: bytes) -> None: """Write to original file descriptor.""" self._assert_state("writeorg", ("started", "suspended")) os.write(self.targetfd_save, data) -class FDCapture(FDCaptureBinary): +class FDCapture(FDCaptureBase[str]): """Capture IO to/from a given OS-level file descriptor. snap() produces text. """ - # Ignore type because it doesn't match the type in the superclass (bytes). - EMPTY_BUFFER = "" # type: ignore + EMPTY_BUFFER = "" - def snap(self): + def snap(self) -> str: self._assert_state("snap", ("started", "suspended")) self.tmpfile.seek(0) res = self.tmpfile.read() @@ -484,77 +570,49 @@ class FDCapture(FDCaptureBinary): self.tmpfile.truncate() return res - def writeorg(self, data): + def writeorg(self, data: str) -> None: """Write to original file descriptor.""" - super().writeorg(data.encode("utf-8")) # XXX use encoding of original stream + self._assert_state("writeorg", ("started", "suspended")) + # XXX use encoding of original stream + os.write(self.targetfd_save, data.encode("utf-8")) # MultiCapture -# This class was a namedtuple, but due to mypy limitation[0] it could not be -# made generic, so was replaced by a regular class which tries to emulate the -# pertinent parts of a namedtuple. If the mypy limitation is ever lifted, can -# make it a namedtuple again. -# [0]: https://github.com/python/mypy/issues/685 -@final -@functools.total_ordering -class CaptureResult(Generic[AnyStr]): - """The result of :method:`CaptureFixture.readouterr`.""" +# Generic NamedTuple only supported since Python 3.11. +if sys.version_info >= (3, 11) or TYPE_CHECKING: - __slots__ = ("out", "err") + @final + class CaptureResult(NamedTuple, Generic[AnyStr]): + """The result of :method:`CaptureFixture.readouterr`.""" - def __init__(self, out: AnyStr, err: AnyStr) -> None: - self.out: AnyStr = out - self.err: AnyStr = err + out: AnyStr + err: AnyStr - def __len__(self) -> int: - return 2 +else: - def __iter__(self) -> Iterator[AnyStr]: - return iter((self.out, self.err)) + class CaptureResult( + collections.namedtuple("CaptureResult", ["out", "err"]), Generic[AnyStr] + ): + """The result of :method:`CaptureFixture.readouterr`.""" - def __getitem__(self, item: int) -> AnyStr: - return tuple(self)[item] - - def _replace( - self, *, out: Optional[AnyStr] = None, err: Optional[AnyStr] = None - ) -> "CaptureResult[AnyStr]": - return CaptureResult( - out=self.out if out is None else out, err=self.err if err is None else err - ) - - def count(self, value: AnyStr) -> int: - return tuple(self).count(value) - - def index(self, value) -> int: - return tuple(self).index(value) - - def __eq__(self, other: object) -> bool: - if not isinstance(other, (CaptureResult, tuple)): - return NotImplemented - return tuple(self) == tuple(other) - - def __hash__(self) -> int: - return hash(tuple(self)) - - def __lt__(self, other: object) -> bool: - if not isinstance(other, (CaptureResult, tuple)): - return NotImplemented - return tuple(self) < tuple(other) - - def __repr__(self) -> str: - return f"CaptureResult(out={self.out!r}, err={self.err!r})" + __slots__ = () class MultiCapture(Generic[AnyStr]): _state = None _in_suspended = False - def __init__(self, in_, out, err) -> None: - self.in_ = in_ - self.out = out - self.err = err + def __init__( + self, + in_: Optional[CaptureBase[AnyStr]], + out: Optional[CaptureBase[AnyStr]], + err: Optional[CaptureBase[AnyStr]], + ) -> None: + self.in_: Optional[CaptureBase[AnyStr]] = in_ + self.out: Optional[CaptureBase[AnyStr]] = out + self.err: Optional[CaptureBase[AnyStr]] = err def __repr__(self) -> str: return "".format( @@ -578,8 +636,10 @@ class MultiCapture(Generic[AnyStr]): """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: + assert self.out is not None self.out.writeorg(out) if err: + assert self.err is not None self.err.writeorg(err) return out, err @@ -600,6 +660,7 @@ class MultiCapture(Generic[AnyStr]): if self.err: self.err.resume() if self._in_suspended: + assert self.in_ is not None self.in_.resume() self._in_suspended = False @@ -622,7 +683,8 @@ class MultiCapture(Generic[AnyStr]): def readouterr(self) -> CaptureResult[AnyStr]: out = self.out.snap() if self.out else "" err = self.err.snap() if self.err else "" - return CaptureResult(out, err) + # TODO: This type error is real, need to fix. + return CaptureResult(out, err) # type: ignore[arg-type] def _get_multicapture(method: "_CaptureMethod") -> MultiCapture[str]: @@ -662,7 +724,7 @@ class CaptureManager: """ def __init__(self, method: "_CaptureMethod") -> None: - self._method = method + self._method: Final = method self._global_capturing: Optional[MultiCapture[str]] = None self._capture_fixture: Optional[CaptureFixture[Any]] = None @@ -831,14 +893,18 @@ class CaptureFixture(Generic[AnyStr]): :fixture:`capfd` and :fixture:`capfdbinary` fixtures.""" def __init__( - self, captureclass, request: SubRequest, *, _ispytest: bool = False + self, + captureclass: Type[CaptureBase[AnyStr]], + request: SubRequest, + *, + _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass = captureclass + self.captureclass: Type[CaptureBase[AnyStr]] = captureclass self.request = request self._capture: Optional[MultiCapture[AnyStr]] = None - self._captured_out = self.captureclass.EMPTY_BUFFER - self._captured_err = self.captureclass.EMPTY_BUFFER + self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER + self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER def _start(self) -> None: if self._capture is None: @@ -893,7 +959,9 @@ class CaptureFixture(Generic[AnyStr]): @contextlib.contextmanager def disabled(self) -> Generator[None, None, None]: """Temporarily disable capturing while inside the ``with`` block.""" - capmanager = self.request.config.pluginmanager.getplugin("capturemanager") + capmanager: CaptureManager = self.request.config.pluginmanager.getplugin( + "capturemanager" + ) with capmanager.global_and_fixture_disabled(): yield @@ -920,8 +988,8 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: captured = capsys.readouterr() assert captured.out == "hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[str](SysCapture, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCapture, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -948,8 +1016,8 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, captured = capsysbinary.readouterr() assert captured.out == b"hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[bytes](SysCaptureBinary, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(SysCaptureBinary, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -976,8 +1044,8 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: captured = capfd.readouterr() assert captured.out == "hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[str](FDCapture, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCapture, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture @@ -1005,8 +1073,8 @@ def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, N assert captured.out == b"hello\n" """ - capman = request.config.pluginmanager.getplugin("capturemanager") - capture_fixture = CaptureFixture[bytes](FDCaptureBinary, request, _ispytest=True) + capman: CaptureManager = request.config.pluginmanager.getplugin("capturemanager") + capture_fixture = CaptureFixture(FDCaptureBinary, request, _ispytest=True) capman.set_fixture(capture_fixture) capture_fixture._start() yield capture_fixture diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index fab4c3110..352211de8 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -1,4 +1,7 @@ """Python version compatibility code.""" +from __future__ import annotations + +import dataclasses import enum import functools import inspect @@ -11,13 +14,9 @@ from typing import Any from typing import Callable from typing import Generic from typing import NoReturn -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union -import attr import py # fmt: off @@ -46,7 +45,7 @@ LEGACY_PATH = py.path. local # fmt: on -def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: +def legacy_path(path: str | os.PathLike[str]) -> LEGACY_PATH: """Internal wrapper to prepare lazy proxies for legacy_path instances""" return LEGACY_PATH(path) @@ -56,7 +55,7 @@ def legacy_path(path: Union[str, "os.PathLike[str]"]) -> LEGACY_PATH: # https://www.python.org/dev/peps/pep-0484/#support-for-singleton-types-in-unions class NotSetType(enum.Enum): token = 0 -NOTSET: "Final" = NotSetType.token # noqa: E305 +NOTSET: Final = NotSetType.token # noqa: E305 # fmt: on if sys.version_info >= (3, 8): @@ -94,7 +93,7 @@ def is_async_function(func: object) -> bool: return iscoroutinefunction(func) or inspect.isasyncgenfunction(func) -def getlocation(function, curdir: Optional[str] = None) -> str: +def getlocation(function, curdir: str | None = None) -> str: function = get_real_func(function) fn = Path(inspect.getfile(function)) lineno = function.__code__.co_firstlineno @@ -132,8 +131,8 @@ def getfuncargnames( *, name: str = "", is_method: bool = False, - cls: Optional[type] = None, -) -> Tuple[str, ...]: + cls: type | None = None, +) -> tuple[str, ...]: """Return the names of a function's mandatory arguments. Should return the names of all function arguments that: @@ -197,7 +196,7 @@ def getfuncargnames( return arg_names -def get_default_arg_names(function: Callable[..., Any]) -> Tuple[str, ...]: +def get_default_arg_names(function: Callable[..., Any]) -> tuple[str, ...]: # Note: this code intentionally mirrors the code at the beginning of # getfuncargnames, to get the arguments which were excluded from its result # because they had default values. @@ -228,7 +227,7 @@ def _bytes_to_ascii(val: bytes) -> str: return val.decode("ascii", "backslashreplace") -def ascii_escaped(val: Union[bytes, str]) -> str: +def ascii_escaped(val: bytes | str) -> str: r"""If val is pure ASCII, return it as an str, otherwise, escape bytes objects into a sequence of escaped bytes: @@ -252,7 +251,7 @@ def ascii_escaped(val: Union[bytes, str]) -> str: return _translate_non_printable(ret) -@attr.s +@dataclasses.dataclass class _PytestWrapper: """Dummy wrapper around a function object for internal use only. @@ -261,7 +260,7 @@ class _PytestWrapper: decorator to issue warnings when the fixture function is called directly. """ - obj = attr.ib() + obj: Any def get_real_func(obj): @@ -355,7 +354,6 @@ else: if sys.version_info >= (3, 8): from functools import cached_property as cached_property else: - from typing import Type class cached_property(Generic[_S, _T]): __slots__ = ("func", "__doc__") @@ -366,12 +364,12 @@ else: @overload def __get__( - self, instance: None, owner: Optional[Type[_S]] = ... - ) -> "cached_property[_S, _T]": + self, instance: None, owner: type[_S] | None = ... + ) -> cached_property[_S, _T]: ... @overload - def __get__(self, instance: _S, owner: Optional[Type[_S]] = ...) -> _T: + def __get__(self, instance: _S, owner: type[_S] | None = ...) -> _T: ... def __get__(self, instance, owner=None): @@ -381,6 +379,18 @@ else: return value +def get_user_id() -> int | None: + """Return the current user id, or None if we cannot get it reliably on the current platform.""" + # win32 does not have a getuid() function. + # On Emscripten, getuid() is a stub that always returns 0. + if sys.platform in ("win32", "emscripten"): + return None + # getuid shouldn't fail, but cpython defines such a case. + # Let's hope for the best. + uid = os.getuid() + return uid if uid != -1 else None + + # Perform exhaustiveness checking. # # Consider this example: diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 25f156f8b..c9a4b7f63 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -2,6 +2,7 @@ import argparse import collections.abc import copy +import dataclasses import enum import glob import inspect @@ -34,7 +35,6 @@ from typing import Type from typing import TYPE_CHECKING from typing import Union -import attr from pluggy import HookimplMarker from pluggy import HookspecMarker from pluggy import PluginManager @@ -49,7 +49,7 @@ from _pytest._code import ExceptionInfo from _pytest._code import filter_traceback from _pytest._io import TerminalWriter from _pytest.compat import final -from _pytest.compat import importlib_metadata +from _pytest.compat import importlib_metadata # type: ignore[attr-defined] from _pytest.outcomes import fail from _pytest.outcomes import Skipped from _pytest.pathlib import absolutepath @@ -62,7 +62,6 @@ from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import warn_explicit_for if TYPE_CHECKING: - from _pytest._code.code import _TracebackStyle from _pytest.terminal import TerminalReporter from .argparsing import Argument @@ -527,7 +526,13 @@ class PytestPluginManager(PluginManager): # Internal API for local conftest plugin handling. # def _set_initial_conftests( - self, namespace: argparse.Namespace, rootpath: Path + self, + args: Sequence[Union[str, Path]], + pyargs: bool, + noconftest: bool, + rootpath: Path, + confcutdir: Optional[Path], + importmode: Union[ImportMode, str], ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -537,27 +542,29 @@ class PytestPluginManager(PluginManager): common options will not confuse our logic here. """ current = Path.cwd() - self._confcutdir = ( - absolutepath(current / namespace.confcutdir) - if namespace.confcutdir - else None - ) - self._noconftest = namespace.noconftest - self._using_pyargs = namespace.pyargs - testpaths = namespace.file_or_dir + self._confcutdir = absolutepath(current / confcutdir) if confcutdir else None + self._noconftest = noconftest + self._using_pyargs = pyargs foundanchor = False - for testpath in testpaths: - path = str(testpath) + for intitial_path in args: + path = str(intitial_path) # remove node-id syntax i = path.find("::") if i != -1: path = path[:i] anchor = absolutepath(current / path) - if anchor.exists(): # we found some file object - self._try_load_conftest(anchor, namespace.importmode, rootpath) + + # Ensure we do not break if what appears to be an anchor + # is in fact a very long option (#10169). + try: + anchor_exists = anchor.exists() + except OSError: # pragma: no cover + anchor_exists = False + if anchor_exists: + self._try_load_conftest(anchor, importmode, rootpath) foundanchor = True if not foundanchor: - self._try_load_conftest(current, namespace.importmode, rootpath) + self._try_load_conftest(current, importmode, rootpath) def _is_in_confcutdir(self, path: Path) -> bool: """Whether a path is within the confcutdir. @@ -697,6 +704,7 @@ class PytestPluginManager(PluginManager): parg = opt[2:] else: continue + parg = parg.strip() if exclude_only and not parg.startswith("no:"): continue self.consider_pluginarg(parg) @@ -886,10 +894,6 @@ def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: yield from _iter_rewritable_modules(new_package_files) -def _args_converter(args: Iterable[str]) -> Tuple[str, ...]: - return tuple(args) - - @final class Config: """Access to configuration values, pluginmanager and plugin hooks. @@ -903,7 +907,7 @@ class Config: """ @final - @attr.s(frozen=True, auto_attribs=True) + @dataclasses.dataclass(frozen=True) class InvocationParams: """Holds parameters passed during :func:`pytest.main`. @@ -919,13 +923,24 @@ class Config: Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] = attr.ib(converter=_args_converter) + args: Tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] """Extra plugins, might be `None`.""" dir: Path """The directory from which :func:`pytest.main` was invoked.""" + def __init__( + self, + *, + args: Iterable[str], + plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + dir: Path, + ) -> None: + object.__setattr__(self, "args", tuple(args)) + object.__setattr__(self, "plugins", plugins) + object.__setattr__(self, "dir", dir) + class ArgsSource(enum.Enum): """Indicates the source of the test arguments. @@ -998,6 +1013,8 @@ 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 @@ -1057,7 +1074,6 @@ class Config: try: self.parse(args) except UsageError: - # Handle --version and --help here in a minimal fashion. # This gets done via helpconfig normally, but its # pytest_cmdline_main is not called in case of errors. @@ -1122,8 +1138,25 @@ class Config: @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config: "Config") -> None: + # We haven't fully parsed the command line arguments yet, so + # early_config.args it not set yet. But we need it for + # discovering the initial conftests. So "pre-run" the logic here. + # It will be done for real in `parse()`. + args, args_source = early_config._decide_args( + args=early_config.known_args_namespace.file_or_dir, + pyargs=early_config.known_args_namespace.pyargs, + testpaths=early_config.getini("testpaths"), + invocation_dir=early_config.invocation_params.dir, + rootpath=early_config.rootpath, + warn=False, + ) self.pluginmanager._set_initial_conftests( - early_config.known_args_namespace, rootpath=early_config.rootpath + args=args, + pyargs=early_config.known_args_namespace.pyargs, + noconftest=early_config.known_args_namespace.noconftest, + rootpath=early_config.rootpath, + confcutdir=early_config.known_args_namespace.confcutdir, + importmode=early_config.known_args_namespace.importmode, ) def _initini(self, args: Sequence[str]) -> None: @@ -1203,6 +1236,49 @@ class Config: return args + def _decide_args( + self, + *, + args: List[str], + pyargs: List[str], + testpaths: List[str], + invocation_dir: Path, + rootpath: Path, + warn: bool, + ) -> Tuple[List[str], ArgsSource]: + """Decide the args (initial paths/nodeids) to use given the relevant inputs. + + :param warn: Whether can issue warnings. + """ + if args: + source = Config.ArgsSource.ARGS + result = args + else: + if invocation_dir == rootpath: + source = Config.ArgsSource.TESTPATHS + if pyargs: + result = testpaths + else: + result = [] + for path in testpaths: + result.extend(sorted(glob.iglob(path, recursive=True))) + if testpaths and not result: + if warn: + warning_text = ( + "No files were found in testpaths; " + "consider removing or adjusting your testpaths configuration. " + "Searching recursively from the current directory instead." + ) + self.issue_config_time_warning( + PytestConfigWarning(warning_text), stacklevel=3 + ) + else: + result = [] + if not result: + source = Config.ArgsSource.INCOVATION_DIR + result = [str(invocation_dir)] + return result, source + def _preparse(self, args: List[str], addopts: bool = True) -> None: if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") @@ -1241,8 +1317,11 @@ class Config: _pytest.deprecated.STRICT_OPTION, stacklevel=2 ) - if self.known_args_namespace.confcutdir is None and self.inipath is not None: - confcutdir = str(self.inipath.parent) + if self.known_args_namespace.confcutdir is None: + if self.inipath is not None: + confcutdir = str(self.inipath.parent) + else: + confcutdir = str(self.rootpath) self.known_args_namespace.confcutdir = confcutdir try: self.hook.pytest_load_initial_conftests( @@ -1337,8 +1416,8 @@ class Config: def parse(self, args: List[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. - assert not hasattr( - self, "args" + assert ( + self.args == [] ), "can only parse cmdline args at most once per Config object" self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager) @@ -1348,25 +1427,17 @@ class Config: self.hook.pytest_cmdline_preparse(config=self, args=args) self._parser.after_preparse = True # type: ignore try: - source = Config.ArgsSource.ARGS args = self._parser.parse_setoption( args, self.option, namespace=self.option ) - if not args: - if self.invocation_params.dir == self.rootpath: - source = Config.ArgsSource.TESTPATHS - testpaths: List[str] = self.getini("testpaths") - if self.known_args_namespace.pyargs: - args = testpaths - else: - args = [] - for path in testpaths: - args.extend(sorted(glob.iglob(path, recursive=True))) - if not args: - source = Config.ArgsSource.INCOVATION_DIR - args = [str(self.invocation_params.dir)] - self.args = args - self.args_source = source + self.args, self.args_source = self._decide_args( + args=args, + pyargs=self.known_args_namespace.pyargs, + testpaths=self.getini("testpaths"), + invocation_dir=self.invocation_params.dir, + rootpath=self.rootpath, + warn=True, + ) except PrintHelp: pass diff --git a/src/_pytest/config/compat.py b/src/_pytest/config/compat.py index ba267d215..5bd922a4a 100644 --- a/src/_pytest/config/compat.py +++ b/src/_pytest/config/compat.py @@ -43,7 +43,6 @@ class PathAwareHookProxy: @_wraps(hook) def fixed_hook(**kw): - path_value: Optional[Path] = kw.pop(path_var, None) fspath_value: Optional[LEGACY_PATH] = kw.pop(fspath_var, None) if fspath_value is not None: diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 43c236779..234b9e129 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -203,8 +203,7 @@ def determine_setup( else: cwd = Path.cwd() rootdir = get_common_ancestor([cwd, ancestor]) - is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" - if is_fs_root: + if is_fs_root(rootdir): rootdir = ancestor if rootdir_cmd_arg: rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) @@ -216,3 +215,11 @@ def determine_setup( ) assert rootdir is not None return rootdir, inipath, inicfg or {} + + +def is_fs_root(p: Path) -> bool: + r""" + Return True if the given path is pointing to the root of the + file system ("/" on Unix and "C:\\" on Windows for example). + """ + return os.path.splitdrive(str(p))[1] == os.sep diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index b99c3fe2d..a3f80802c 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -3,6 +3,7 @@ import argparse import functools import sys import types +import unittest from typing import Any from typing import Callable from typing import Generator @@ -293,7 +294,9 @@ class PdbInvoke: sys.stdout.write(out) sys.stdout.write(err) assert call.excinfo is not None - _enter_pdb(node, call.excinfo, report) + + if not isinstance(call.excinfo.value, unittest.SkipTest): + _enter_pdb(node, call.excinfo, report) def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: tb = _postmortem_traceback(excinfo) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 771f0890d..455ad62cc 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -531,7 +531,6 @@ class DoctestModule(Module): if _is_mocked(obj): return with _patch_unwrap_mock_aware(): - # Type ignored because this is a private function. super()._find( # type:ignore[misc] tests, obj, name, module, source_lines, globs, seen diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index b9c925582..af879aa44 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -2,7 +2,6 @@ import io import os import sys from typing import Generator -from typing import TextIO import pytest from _pytest.config import Config @@ -11,7 +10,7 @@ from _pytest.nodes import Item from _pytest.stash import StashKey -fault_handler_stderr_key = StashKey[TextIO]() +fault_handler_stderr_fd_key = StashKey[int]() fault_handler_originally_enabled_key = StashKey[bool]() @@ -26,10 +25,9 @@ def pytest_addoption(parser: Parser) -> None: def pytest_configure(config: Config) -> None: import faulthandler - stderr_fd_copy = os.dup(get_stderr_fileno()) - config.stash[fault_handler_stderr_key] = open(stderr_fd_copy, "w") + config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno()) config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() - faulthandler.enable(file=config.stash[fault_handler_stderr_key]) + faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) def pytest_unconfigure(config: Config) -> None: @@ -37,9 +35,9 @@ def pytest_unconfigure(config: Config) -> None: faulthandler.disable() # Close the dup file installed during pytest_configure. - if fault_handler_stderr_key in config.stash: - config.stash[fault_handler_stderr_key].close() - del config.stash[fault_handler_stderr_key] + if fault_handler_stderr_fd_key in config.stash: + os.close(config.stash[fault_handler_stderr_fd_key]) + del config.stash[fault_handler_stderr_fd_key] if config.stash.get(fault_handler_originally_enabled_key, False): # Re-enable the faulthandler if it was originally enabled. faulthandler.enable(file=get_stderr_fileno()) @@ -67,10 +65,10 @@ def get_timeout_config_value(config: Config) -> float: @pytest.hookimpl(hookwrapper=True, trylast=True) def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: timeout = get_timeout_config_value(item.config) - stderr = item.config.stash[fault_handler_stderr_key] - if timeout > 0 and stderr is not None: + if timeout > 0: import faulthandler + stderr = item.config.stash[fault_handler_stderr_fd_key] faulthandler.dump_traceback_later(timeout, file=stderr) try: yield diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 3cb152eeb..66722da27 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,3 +1,4 @@ +import dataclasses import functools import inspect import os @@ -28,8 +29,6 @@ from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -import attr - import _pytest from _pytest import nodes from _pytest._code import getfslineno @@ -47,6 +46,7 @@ from _pytest.compat import getimfunc from _pytest.compat import getlocation from _pytest.compat import is_generator from _pytest.compat import NOTSET +from _pytest.compat import NotSetType from _pytest.compat import overload from _pytest.compat import safe_getattr from _pytest.config import _PluggyPlugin @@ -59,6 +59,7 @@ from _pytest.mark import Mark from _pytest.mark import ParameterSet from _pytest.mark.structures import MarkDecorator from _pytest.outcomes import fail +from _pytest.outcomes import skip from _pytest.outcomes import TEST_OUTCOME from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath @@ -103,7 +104,7 @@ _FixtureCachedResult = Union[ ] -@attr.s(frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class PseudoFixtureDef(Generic[FixtureValue]): cached_result: "_FixtureCachedResult[FixtureValue]" _scope: Scope @@ -113,16 +114,18 @@ def pytest_sessionstart(session: "Session") -> None: session._fixturemanager = FixtureManager(session) -def get_scope_package(node, fixturedef: "FixtureDef[object]"): - import pytest +def get_scope_package( + node: nodes.Item, + fixturedef: "FixtureDef[object]", +) -> Optional[Union[nodes.Item, nodes.Collector]]: + from _pytest.python import Package - cls = pytest.Package - current = node + current: Optional[Union[nodes.Item, nodes.Collector]] = node fixture_package_name = "{}/{}".format(fixturedef.baseid, "__init__.py") while current and ( - type(current) is not cls or fixture_package_name != current.nodeid + not isinstance(current, Package) or fixture_package_name != current.nodeid ): - current = current.parent + current = current.parent # type: ignore[assignment] if current is None: return node.session return current @@ -350,8 +353,10 @@ def get_direct_param_fixture_func(request: "FixtureRequest") -> Any: return request.param -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class FuncFixtureInfo: + __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") + # Original function argument names. argnames: Tuple[str, ...] # Argnames that function immediately requires. These include argnames + @@ -433,7 +438,23 @@ class FixtureRequest: @property def node(self): """Underlying collection node (depends on current request scope).""" - return self._getscopeitem(self._scope) + scope = self._scope + if scope is Scope.Function: + # This might also be a non-function Item despite its attribute name. + node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem + elif scope is Scope.Package: + # FIXME: _fixturedef is not defined on FixtureRequest (this class), + # but on FixtureRequest (a subclass). + node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] + else: + node = get_scope_node(self._pyfuncitem, scope) + if node is None and scope is Scope.Class: + # Fallback to function item itself. + node = self._pyfuncitem + assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format( + scope, self._pyfuncitem + ) + return node def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]": fixturedefs = self._arg2fixturedefs.get(argname, None) @@ -517,11 +538,7 @@ class FixtureRequest: """Add finalizer/teardown function to be called without arguments after the last test within the requesting test context finished execution.""" # XXX usually this method is shadowed by fixturedef specific ones. - self._addfinalizer(finalizer, scope=self.scope) - - def _addfinalizer(self, finalizer: Callable[[], object], scope) -> None: - node = self._getscopeitem(scope) - node.addfinalizer(finalizer) + self.node.addfinalizer(finalizer) def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """Apply a marker to a single test function invocation. @@ -716,28 +733,6 @@ class FixtureRequest: lines.append("%s:%d: def %s%s" % (p, lineno + 1, factory.__name__, args)) return lines - def _getscopeitem( - self, scope: Union[Scope, "_ScopeName"] - ) -> Union[nodes.Item, nodes.Collector]: - if isinstance(scope, str): - scope = Scope(scope) - if scope is Scope.Function: - # This might also be a non-function Item despite its attribute name. - node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem - elif scope is Scope.Package: - # FIXME: _fixturedef is not defined on FixtureRequest (this class), - # but on FixtureRequest (a subclass). - node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined] - else: - node = get_scope_node(self._pyfuncitem, scope) - if node is None and scope is Scope.Class: - # Fallback to function item itself. - node = self._pyfuncitem - assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format( - scope, self._pyfuncitem - ) - return node - def __repr__(self) -> str: return "" % (self.node) @@ -1130,6 +1125,10 @@ def pytest_fixture_setup( except TEST_OUTCOME: exc_info = sys.exc_info() assert exc_info[0] is not None + if isinstance( + exc_info[1], skip.Exception + ) and not fixturefunc.__name__.startswith("xunit_setup"): + exc_info[1]._use_item_location = True # type: ignore[attr-defined] fixturedef.cached_result = (None, my_cache_key, exc_info) raise fixturedef.cached_result = (result, my_cache_key, None) @@ -1177,19 +1176,21 @@ def wrap_function_to_error_out_if_called_directly( @final -@attr.s(frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] = attr.ib(converter=_params_converter) + params: Optional[Tuple[object, ...]] autouse: bool = False ids: Optional[ Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = attr.ib( - default=None, - converter=_ensure_immutable_ids, - ) + ] = None name: Optional[str] = None + _ispytest: dataclasses.InitVar[bool] = False + + def __post_init__(self, _ispytest: bool) -> None: + check_ispytest(_ispytest) + def __call__(self, function: FixtureFunction) -> FixtureFunction: if inspect.isclass(function): raise ValueError("class fixtures not supported (maybe in the future)") @@ -1312,10 +1313,11 @@ def fixture( # noqa: F811 """ fixture_marker = FixtureFunctionMarker( scope=scope, - params=params, + params=tuple(params) if params is not None else None, autouse=autouse, - ids=ids, + ids=None if ids is None else ids if callable(ids) else tuple(ids), name=name, + _ispytest=True, ) # Direct decoration. @@ -1588,13 +1590,52 @@ class FixtureManager: # Separate parametrized setups. items[:] = reorder_items(items) + @overload def parsefactories( - self, node_or_obj, nodeid=NOTSET, unittest: bool = False + self, + node_or_obj: nodes.Node, + *, + unittest: bool = ..., ) -> None: + raise NotImplementedError() + + @overload + def parsefactories( # noqa: F811 + self, + node_or_obj: object, + nodeid: Optional[str], + *, + unittest: bool = ..., + ) -> None: + raise NotImplementedError() + + def parsefactories( # noqa: F811 + self, + node_or_obj: Union[nodes.Node, object], + nodeid: Union[str, NotSetType, None] = NOTSET, + *, + unittest: bool = False, + ) -> None: + """Collect fixtures from a collection node or object. + + Found fixtures are parsed into `FixtureDef`s and saved. + + If `node_or_object` is a collection node (with an underlying Python + object), the node's object is traversed and the node's nodeid is used to + determine the fixtures' visibilty. `nodeid` must not be specified in + this case. + + If `node_or_object` is an object (e.g. a plugin), the object is + traversed and the given `nodeid` is used to determine the fixtures' + visibility. `nodeid` must be specified in this case; None and "" mean + total visibility. + """ if nodeid is not NOTSET: holderobj = node_or_obj else: - holderobj = node_or_obj.obj + assert isinstance(node_or_obj, nodes.Node) + holderobj = cast(object, node_or_obj.obj) # type: ignore[attr-defined] + assert isinstance(node_or_obj.nodeid, str) nodeid = node_or_obj.nodeid if holderobj in self._holderobjseen: return diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 151bc6dff..430870608 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -105,7 +105,7 @@ def pytest_cmdline_parse(): if config.option.debug: # --debug | --debug was provided. path = config.option.debug - debugfile = open(path, "w") + debugfile = open(path, "w", encoding="utf-8") debugfile.write( "versions pytest-%s, " "python-%s\ncwd=%s\nargs=%s\n\n" @@ -164,7 +164,8 @@ def showhelp(config: Config) -> None: tw.write(config._parser.optparser.format_help()) tw.line() tw.line( - "[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:" + "[pytest] ini-options in the first " + "pytest.ini|tox.ini|setup.cfg|pyproject.toml file found:" ) tw.line() diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index cc0828dd1..1f7c368f7 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -21,7 +21,7 @@ if TYPE_CHECKING: from typing_extensions import Literal from _pytest._code.code import ExceptionRepr - from _pytest.code import ExceptionInfo + from _pytest._code.code import ExceptionInfo from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -41,6 +41,7 @@ if TYPE_CHECKING: from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.terminal import TerminalReporter + from _pytest.terminal import TestShortLogReport from _pytest.compat import LEGACY_PATH @@ -505,7 +506,9 @@ def pytest_runtest_logstart( See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. :param nodeid: Full node ID of the item. - :param location: A tuple of ``(filename, lineno, testname)``. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. """ @@ -517,7 +520,9 @@ def pytest_runtest_logfinish( See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. :param nodeid: Full node ID of the item. - :param location: A tuple of ``(filename, lineno, testname)``. + :param location: A tuple of ``(filename, lineno, testname)`` + where ``filename`` is a file path relative to ``config.rootpath`` + and ``lineno`` is 0-based. """ @@ -738,7 +743,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 +772,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,9 +805,9 @@ 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]]]: +) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": """Return result-category, shortletter and verbose word for status reporting. @@ -880,7 +885,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. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 7a5170f32..9242d46d9 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -645,8 +645,8 @@ class LogXML: def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) - if not os.path.isdir(dirname): - os.makedirs(dirname) + # exist_ok avoids filesystem race conditions between checking path existence and requesting creation + os.makedirs(dirname, exist_ok=True) with open(self.logfile, "w", encoding="utf-8") as logfile: suite_stop_time = timing.time() diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index f71e7e96e..af1d0c07e 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,4 +1,5 @@ """Add backward compatibility support for the legacy py path type.""" +import dataclasses import shlex import subprocess from pathlib import Path @@ -7,7 +8,6 @@ from typing import Optional from typing import TYPE_CHECKING from typing import Union -import attr from iniconfig import SectionWrapper from _pytest.cacheprovider import Cache @@ -268,7 +268,7 @@ class LegacyTestdirPlugin: @final -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class TempdirFactory: """Backward compatibility wrapper that implements :class:`py.path.local` for :class:`TempPathFactory`. diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index f9091399f..838134660 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -5,7 +5,11 @@ import os import re from contextlib import contextmanager from contextlib import nullcontext +from datetime import datetime +from datetime import timedelta +from datetime import timezone from io import StringIO +from logging import LogRecord from pathlib import Path from typing import AbstractSet from typing import Dict @@ -53,7 +57,25 @@ def _remove_ansi_escape_sequences(text: str) -> str: return _ANSI_ESCAPE_SEQ.sub("", text) -class ColoredLevelFormatter(logging.Formatter): +class DatetimeFormatter(logging.Formatter): + """A logging formatter which formats record with + :func:`datetime.datetime.strftime` formatter instead of + :func:`time.strftime` in case of microseconds in format string. + """ + + def formatTime(self, record: LogRecord, datefmt=None) -> str: + if datefmt and "%f" in datefmt: + ct = self.converter(record.created) + tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) + # Construct `datetime.datetime` object from `struct_time` + # and msecs information from `record` + dt = datetime(*ct[0:6], microsecond=round(record.msecs * 1000), tzinfo=tz) + return dt.strftime(datefmt) + # Use `logging.Formatter` for non-microsecond formats + return super().formatTime(record, datefmt) + + +class ColoredLevelFormatter(DatetimeFormatter): """A logging formatter which colorizes the %(levelname)..s part of the log format passed to __init__.""" @@ -297,6 +319,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 multiple times.", + ) _HandlerType = TypeVar("_HandlerType", bound=logging.Handler) @@ -369,11 +398,12 @@ class LogCaptureFixture: self._initial_handler_level: Optional[int] = None # Dict of log name -> log level. self._initial_logger_levels: Dict[Optional[str], int] = {} + self._initial_disabled_logging_level: Optional[int] = None def _finalize(self) -> None: """Finalize the fixture. - This restores the log levels changed by :meth:`set_level`. + This restores the log levels and the disabled logging levels changed by :meth:`set_level`. """ # Restore log levels. if self._initial_handler_level is not None: @@ -381,6 +411,10 @@ class LogCaptureFixture: for logger_name, level in self._initial_logger_levels.items(): logger = logging.getLogger(logger_name) logger.setLevel(level) + # Disable logging at the original disabled logging level. + if self._initial_disabled_logging_level is not None: + logging.disable(self._initial_disabled_logging_level) + self._initial_disabled_logging_level = None @property def handler(self) -> LogCaptureHandler: @@ -446,13 +480,51 @@ class LogCaptureFixture: """Reset the list of log records and the captured log text.""" self.handler.clear() + def _force_enable_logging( + self, level: Union[int, str], logger_obj: logging.Logger + ) -> int: + """Enable the desired logging level if the global level was disabled via ``logging.disabled``. + + Only enables logging levels greater than or equal to the requested ``level``. + + Does nothing if the desired ``level`` wasn't disabled. + + :param level: + The logger level caplog should capture. + All logging is enabled if a non-standard logging level string is supplied. + Valid level strings are in :data:`logging._nameToLevel`. + :param logger_obj: The logger object to check. + + :return: The original disabled logging level. + """ + original_disable_level: int = logger_obj.manager.disable # type: ignore[attr-defined] + + if isinstance(level, str): + # Try to translate the level string to an int for `logging.disable()` + level = logging.getLevelName(level) + + if not isinstance(level, int): + # The level provided was not valid, so just un-disable all logging. + logging.disable(logging.NOTSET) + elif not logger_obj.isEnabledFor(level): + # Each level is `10` away from other levels. + # https://docs.python.org/3/library/logging.html#logging-levels + disable_level = max(level - 10, logging.NOTSET) + logging.disable(disable_level) + + return original_disable_level + def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: - """Set the level of a logger for the duration of a test. + """Set the threshold level of a logger for the duration of a test. + + Logging messages which are less severe than this level will not be captured. .. versionchanged:: 3.4 The levels of the loggers changed by this function will be restored to their initial values at the end of the test. + Will enable the requested logging level if it was disabled via :meth:`logging.disable`. + :param level: The level. :param logger: The logger to update. If not given, the root logger. """ @@ -463,6 +535,9 @@ class LogCaptureFixture: if self._initial_handler_level is None: self._initial_handler_level = self.handler.level self.handler.setLevel(level) + initial_disabled_logging_level = self._force_enable_logging(level, logger_obj) + if self._initial_disabled_logging_level is None: + self._initial_disabled_logging_level = initial_disabled_logging_level @contextmanager def at_level( @@ -472,6 +547,8 @@ class LogCaptureFixture: the end of the 'with' statement the level is restored to its original value. + Will enable the requested logging level if it was disabled via :meth:`logging.disable`. + :param level: The level. :param logger: The logger to update. If not given, the root logger. """ @@ -480,11 +557,13 @@ class LogCaptureFixture: logger_obj.setLevel(level) handler_orig_level = self.handler.level self.handler.setLevel(level) + original_disable_level = self._force_enable_logging(level, logger_obj) try: yield finally: logger_obj.setLevel(orig_level) self.handler.setLevel(handler_orig_level) + logging.disable(original_disable_level) @fixture @@ -570,7 +649,7 @@ class LoggingPlugin: config, "log_file_date_format", "log_date_format" ) - log_file_formatter = logging.Formatter( + log_file_formatter = DatetimeFormatter( log_file_format, datefmt=log_file_date_format ) self.log_file_handler.setFormatter(log_file_formatter) @@ -594,6 +673,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. @@ -605,7 +693,7 @@ class LoggingPlugin: create_terminal_writer(self._config), log_format, log_date_format ) else: - formatter = logging.Formatter(log_format, log_date_format) + formatter = DatetimeFormatter(log_format, log_date_format) formatter._style = PercentStyleMultiline( formatter._style._fmt, auto_indent=auto_indent diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 61fb7eaa4..155d4300e 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -1,5 +1,6 @@ """Core implementation of the testing process: init, session, runtest loop.""" import argparse +import dataclasses import fnmatch import functools import importlib @@ -19,8 +20,6 @@ from typing import Type from typing import TYPE_CHECKING from typing import Union -import attr - import _pytest._code from _pytest import nodes from _pytest.compat import final @@ -123,11 +122,12 @@ def pytest_addoption(parser: Parser) -> None: ) group._addoption( "-c", - metavar="file", + "--config-file", + metavar="FILE", type=str, dest="inifilename", - help="Load configuration from `file` instead of trying to locate one of the " - "implicit configuration files", + help="Load configuration from `FILE` instead of trying to locate one of the " + "implicit configuration files.", ) group._addoption( "--continue-on-collection-errors", @@ -400,6 +400,12 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo allow_in_venv = config.getoption("collect_in_virtualenv") if not allow_in_venv and _in_venv(collection_path): return True + + if collection_path.is_dir(): + norecursepatterns = config.getini("norecursedirs") + if any(fnmatch_ex(pat, collection_path) for pat in norecursepatterns): + return True + return None @@ -442,8 +448,10 @@ class Failed(Exception): """Signals a stop as failed test run.""" -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class _bestrelpath_cache(Dict[Path, str]): + __slots__ = ("path",) + path: Path def __missing__(self, path: Path) -> str: @@ -561,9 +569,6 @@ class Session(nodes.FSCollector): ihook = self.gethookproxy(fspath.parent) if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): return False - norecursepatterns = self.config.getini("norecursedirs") - if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns): - return False return True def _collectfile( @@ -684,8 +689,8 @@ class Session(nodes.FSCollector): # are not collected more than once. matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = {} - # Dirnames of pkgs with dunder-init files. - pkg_roots: Dict[str, Package] = {} + # Directories of pkgs with dunder-init files. + pkg_roots: Dict[Path, Package] = {} for argpath, names in self._initial_parts: self.trace("processing argument", (argpath, names)) @@ -706,7 +711,7 @@ class Session(nodes.FSCollector): col = self._collectfile(pkginit, handle_dupes=False) if col: if isinstance(col[0], Package): - pkg_roots[str(parent)] = col[0] + pkg_roots[parent] = col[0] node_cache1[col[0].path] = [col[0]] # If it's a directory argument, recurse and look for any Subpackages. @@ -715,7 +720,7 @@ class Session(nodes.FSCollector): assert not names, f"invalid arg {(argpath, names)!r}" seen_dirs: Set[Path] = set() - for direntry in visit(str(argpath), self._recurse): + for direntry in visit(argpath, self._recurse): if not direntry.is_file(): continue @@ -730,8 +735,8 @@ class Session(nodes.FSCollector): for x in self._collectfile(pkginit): yield x if isinstance(x, Package): - pkg_roots[str(dirpath)] = x - if str(dirpath) in pkg_roots: + pkg_roots[dirpath] = x + if dirpath in pkg_roots: # Do not collect packages here. continue @@ -748,7 +753,7 @@ class Session(nodes.FSCollector): if argpath in node_cache1: col = node_cache1[argpath] else: - collect_root = pkg_roots.get(str(argpath.parent), self) + collect_root = pkg_roots.get(argpath.parent, self) col = collect_root._collectfile(argpath, handle_dupes=False) if col: node_cache1[argpath] = col diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 6717d1135..de46b4c8a 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,4 +1,5 @@ """Generic mechanism for marking and selecting python functions.""" +import dataclasses from typing import AbstractSet from typing import Collection from typing import List @@ -6,8 +7,6 @@ from typing import Optional from typing import TYPE_CHECKING from typing import Union -import attr - from .expression import Expression from .expression import ParseError from .structures import EMPTY_PARAMETERSET_OPTION @@ -130,7 +129,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class KeywordMatcher: """A matcher for keywords. @@ -145,6 +144,8 @@ class KeywordMatcher: any item, as well as names directly assigned to test functions. """ + __slots__ = ("_names",) + _names: AbstractSet[str] @classmethod @@ -201,13 +202,15 @@ def deselect_by_keyword(items: "List[Item]", config: Config) -> None: items[:] = remaining -@attr.s(slots=True, auto_attribs=True) +@dataclasses.dataclass class MarkMatcher: """A matcher for markers which are present. Tries to match on any marker names, attached to the given colitem. """ + __slots__ = ("own_mark_names",) + own_mark_names: AbstractSet[str] @classmethod diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 0a2e7c656..9287bcee5 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -15,8 +15,10 @@ The semantics are: - or/and/not evaluate according to the usual boolean semantics. """ import ast +import dataclasses import enum import re +import sys import types from typing import Callable from typing import Iterator @@ -25,7 +27,10 @@ from typing import NoReturn from typing import Optional from typing import Sequence -import attr +if sys.version_info >= (3, 8): + astNameConstant = ast.Constant +else: + astNameConstant = ast.NameConstant __all__ = [ @@ -44,8 +49,9 @@ class TokenType(enum.Enum): EOF = "end of input" -@attr.s(frozen=True, slots=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Token: + __slots__ = ("type", "value", "pos") type: TokenType value: str pos: int @@ -132,7 +138,7 @@ IDENT_PREFIX = "$" def expression(s: Scanner) -> ast.Expression: if s.accept(TokenType.EOF): - ret: ast.expr = ast.NameConstant(False) + ret: ast.expr = astNameConstant(False) else: ret = expr(s) s.accept(TokenType.EOF, reject=True) diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 31797f563..406da5d9d 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -1,4 +1,5 @@ import collections.abc +import dataclasses import inspect import warnings from typing import Any @@ -20,8 +21,6 @@ from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -import attr - from .._code import getfslineno from ..compat import ascii_escaped from ..compat import final @@ -192,8 +191,10 @@ class ParameterSet(NamedTuple): @final -@attr.s(frozen=True, init=False, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Mark: + """A pytest mark.""" + #: Name of the mark. name: str #: Positional arguments of the mark decorator. @@ -202,9 +203,11 @@ class Mark: kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = attr.ib(default=None, repr=False) + _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = attr.ib(default=None, repr=False) + _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + default=None, repr=False + ) def __init__( self, @@ -262,7 +265,7 @@ class Mark: Markable = TypeVar("Markable", bound=Union[Callable[..., object], type]) -@attr.s(init=False, auto_attribs=True) +@dataclasses.dataclass class MarkDecorator: """A decorator for applying a mark on test functions and classes. @@ -356,12 +359,35 @@ class MarkDecorator: return self.with_args(*args, **kwargs) -def get_unpacked_marks(obj: object) -> Iterable[Mark]: - """Obtain the unpacked marks that are stored on an object.""" - mark_list = getattr(obj, "pytestmark", []) - if not isinstance(mark_list, list): - mark_list = [mark_list] - return normalize_mark_list(mark_list) +def get_unpacked_marks( + obj: Union[object, type], + *, + consider_mro: bool = True, +) -> List[Mark]: + """Obtain the unpacked marks that are stored on an object. + + If obj is a class and consider_mro is true, return marks applied to + this class and all of its super-classes in MRO order. If consider_mro + is false, only return marks applied directly to this class. + """ + if isinstance(obj, type): + if not consider_mro: + mark_lists = [obj.__dict__.get("pytestmark", [])] + else: + mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__] + mark_list = [] + for item in mark_lists: + if isinstance(item, list): + mark_list.extend(item) + else: + mark_list.append(item) + else: + mark_attribute = getattr(obj, "pytestmark", []) + if isinstance(mark_attribute, list): + mark_list = mark_attribute + else: + mark_list = [mark_attribute] + return list(normalize_mark_list(mark_list)) def normalize_mark_list( @@ -395,7 +421,7 @@ def store_mark(obj, mark: Mark) -> None: # Always reassign name to avoid updating pytestmark in a reference that # was only borrowed. - obj.pytestmark = [*get_unpacked_marks(obj), mark] + obj.pytestmark = [*get_unpacked_marks(obj, consider_mro=False), mark] # Typing for builtin pytest marks. This is cheating; it gives builtin marks diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index c6e29ac76..9e51ff335 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -7,6 +7,7 @@ from contextlib import contextmanager from typing import Any from typing import Generator from typing import List +from typing import Mapping from typing import MutableMapping from typing import Optional from typing import overload @@ -129,7 +130,7 @@ class MonkeyPatch: def __init__(self) -> None: self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = [] + self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] self._cwd: Optional[str] = None self._savesyspath: Optional[List[str]] = None @@ -290,12 +291,13 @@ class MonkeyPatch: self._setattr.append((target, name, oldval)) delattr(target, name) - def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None: + def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None: """Set dictionary entry ``name`` to value.""" self._setitem.append((dic, name, dic.get(name, notset))) - dic[name] = value + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dic[name] = value # type: ignore[index] - def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None: + def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: """Delete ``name`` from dict. Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to @@ -306,7 +308,8 @@ class MonkeyPatch: raise KeyError(name) else: self._setitem.append((dic, name, dic.get(name, notset))) - del dic[name] + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dic[name] # type: ignore[attr-defined] def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: """Set environment variable ``name`` to ``value``. @@ -401,11 +404,13 @@ class MonkeyPatch: for dictionary, key, value in reversed(self._setitem): if value is notset: try: - del dictionary[key] + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dictionary[key] # type: ignore[attr-defined] except KeyError: pass # Was already deleted, so we have the desired state. else: - dictionary[key] = value + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dictionary[key] = value # type: ignore[index] self._setitem[:] = [] if self._savesyspath is not None: sys.path[:] = self._savesyspath diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index cfb9b5a36..dbd6b0a42 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -22,6 +22,7 @@ import _pytest._code from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr +from _pytest._code.code import Traceback from _pytest.compat import cached_property from _pytest.compat import LEGACY_PATH from _pytest.config import Config @@ -432,8 +433,8 @@ class Node(metaclass=NodeMeta): assert current is None or isinstance(current, cls) return current - def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: - pass + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: + return excinfo.traceback def _repr_failure_py( self, @@ -449,13 +450,13 @@ class Node(metaclass=NodeMeta): style = "value" if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() + + tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] if self.config.getoption("fulltrace", False): style = "long" + tbfilter = False else: - tb = _pytest._code.Traceback([excinfo.traceback[-1]]) - self._prunetraceback(excinfo) - if len(excinfo.traceback) == 0: - excinfo.traceback = tb + tbfilter = self._traceback_filter if style == "auto": style = "long" # XXX should excinfo.getrepr record all data and toterminal() process it? @@ -486,7 +487,7 @@ class Node(metaclass=NodeMeta): abspath=abspath, showlocals=self.config.getoption("showlocals", False), style=style, - tbfilter=False, # pruned already, or in --fulltrace mode. + tbfilter=tbfilter, truncate_locals=truncate_locals, ) @@ -511,7 +512,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i * "obj": a Python object that the node wraps. * "fspath": just a path - :rtype: A tuple of (str|Path, int) with filename and line number. + :rtype: A tuple of (str|Path, int) with filename and 0-based line number. """ # See Item.location. location: Optional[Tuple[str, Optional[int], str]] = getattr(node, "location", None) @@ -557,13 +558,14 @@ class Collector(Node): return self._repr_failure_py(excinfo, style=tbstyle) - def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: if hasattr(self, "path"): traceback = excinfo.traceback ntraceback = traceback.cut(path=self.path) if ntraceback == traceback: ntraceback = ntraceback.cut(excludepath=tracebackcutdir) - excinfo.traceback = ntraceback.filter() + return excinfo.traceback.filter(excinfo) + return excinfo.traceback def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: @@ -755,7 +757,7 @@ class Item(Node): Returns a tuple with three elements: - The path of the test (default ``self.path``) - - The line number of the test (default ``None``) + - The 0-based line number of the test (default ``None``) - A name of the test to be shown (default ``""``) .. seealso:: :ref:`non-python tests` @@ -764,6 +766,11 @@ class Item(Node): @cached_property def location(self) -> Tuple[str, Optional[int], str]: + """ + Returns a tuple of ``(relfspath, lineno, testname)`` for this item + where ``relfspath`` is file path relative to ``config.rootpath`` + and lineno is a 0-based line number. + """ location = self.reportinfo() path = absolutepath(os.fspath(location[0])) relfspath = self.session._node_location_to_relpath(path) diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index e46b663dd..1be97dda4 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -157,8 +157,12 @@ def skip( The message to show the user as reason for the skip. :param allow_module_level: - Allows this function to be called at module level, skipping the rest - of the module. Defaults to False. + Allows this function to be called at module level. + Raising the skip exception at module level will stop + the execution of the module and prevent the collection of all tests in the module, + even those defined before the `skip` call. + + Defaults to False. :param msg: Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. @@ -219,7 +223,6 @@ def _resolve_msg_to_reason( """ __tracebackhide__ = True if msg is not None: - if reason: from pytest import UsageError diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index c5a411b59..70383e4b5 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -6,6 +6,7 @@ import itertools import os import shutil import sys +import types import uuid import warnings from enum import Enum @@ -26,8 +27,11 @@ from typing import Callable from typing import Dict from typing import Iterable from typing import Iterator +from typing import List from typing import Optional from typing import Set +from typing import Tuple +from typing import Type from typing import TypeVar from typing import Union @@ -63,21 +67,33 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: return path.joinpath(".lock") -def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool: +def on_rm_rf_error( + func, + path: str, + excinfo: Union[ + BaseException, + Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], + ], + *, + start_path: Path, +) -> bool: """Handle known read-only errors during rmtree. The returned value is used only by our own tests. """ - exctype, excvalue = exc[:2] + if isinstance(excinfo, BaseException): + exc = excinfo + else: + exc = excinfo[1] # Another process removed the file in the middle of the "rm_rf" (xdist for example). # More context: https://github.com/pytest-dev/pytest/issues/5974#issuecomment-543799018 - if isinstance(excvalue, FileNotFoundError): + if isinstance(exc, FileNotFoundError): return False - if not isinstance(excvalue, PermissionError): + if not isinstance(exc, PermissionError): warnings.warn( - PytestWarning(f"(rm_rf) error removing {path}\n{exctype}: {excvalue}") + PytestWarning(f"(rm_rf) error removing {path}\n{type(exc)}: {exc}") ) return False @@ -86,7 +102,7 @@ def on_rm_rf_error(func, path: str, exc, *, start_path: Path) -> bool: warnings.warn( PytestWarning( "(rm_rf) unknown function {} when removing {}:\n{}: {}".format( - func, path, exctype, excvalue + func, path, type(exc), exc ) ) ) @@ -149,7 +165,10 @@ def rm_rf(path: Path) -> None: are read-only.""" path = ensure_extended_length_path(path) onerror = partial(on_rm_rf_error, start_path=path) - shutil.rmtree(str(path), onerror=onerror) + if sys.version_info >= (3, 12): + shutil.rmtree(str(path), onexc=onerror) + else: + shutil.rmtree(str(path), onerror=onerror) def find_prefixed(root: Path, prefix: str) -> Iterator[Path]: @@ -335,15 +354,26 @@ def cleanup_candidates(root: Path, prefix: str, keep: int) -> Iterator[Path]: yield path +def cleanup_dead_symlinks(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_symlinks(root) + def make_numbered_dir_with_cleanup( root: Path, @@ -357,8 +387,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 +496,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 `__import__. + being imported with `importlib.import_module`. * `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 use `__import__` and 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 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 +576,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) @@ -638,30 +670,38 @@ def resolve_package_path(path: Path) -> Optional[Path]: return result +def scandir(path: Union[str, "os.PathLike[str]"]) -> List["os.DirEntry[str]"]: + """Scan a directory recursively, in breadth-first order. + + The returned entries are sorted. + """ + entries = [] + with os.scandir(path) as s: + # Skip entries with symlink loops and other brokenness, so the caller + # doesn't have to deal with it. + for entry in s: + try: + entry.is_file() + except OSError as err: + if _ignore_error(err): + continue + raise + entries.append(entry) + entries.sort(key=lambda entry: entry.name) + return entries + + def visit( path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] ) -> Iterator["os.DirEntry[str]"]: """Walk a directory recursively, in breadth-first order. + The `recurse` predicate determines whether a directory is recursed. + Entries at each directory level are sorted. """ - - # Skip entries with symlink loops and other brokenness, so the caller doesn't - # have to deal with it. - entries = [] - for entry in os.scandir(path): - try: - entry.is_file() - except OSError as err: - if _ignore_error(err): - continue - raise - entries.append(entry) - - entries.sort(key=lambda entry: entry.name) - + entries = scandir(path) yield from entries - for entry in entries: if entry.is_dir() and recurse(entry): yield from visit(entry.path, recurse) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index a9299944d..3df52ebe8 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -6,6 +6,7 @@ import collections.abc import contextlib import gc import importlib +import locale import os import platform import re @@ -129,6 +130,7 @@ class LsofFdLeakChecker: stderr=subprocess.DEVNULL, check=True, text=True, + encoding=locale.getpreferredencoding(False), ).stdout def isopen(line: str) -> bool: diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 1e30d42ce..6ba568c0f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1,4 +1,5 @@ """Python test discovery, setup and run of test functions.""" +import dataclasses import enum import fnmatch import inspect @@ -27,8 +28,6 @@ from typing import Tuple from typing import TYPE_CHECKING from typing import Union -import attr - import _pytest from _pytest import fixtures from _pytest import nodes @@ -36,6 +35,7 @@ from _pytest._code import filter_traceback from _pytest._code import getfslineno from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr +from _pytest._code.code import Traceback from _pytest._io import TerminalWriter from _pytest._io.saferepr import saferepr from _pytest.compat import ascii_escaped @@ -57,7 +57,6 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config.argparsing import Parser from _pytest.deprecated import check_ispytest -from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH from _pytest.deprecated import INSTANCE_COLLECTOR from _pytest.deprecated import NOSE_SUPPORT_METHOD from _pytest.fixtures import FuncFixtureInfo @@ -403,8 +402,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: @@ -668,7 +667,7 @@ class Package(Module): config=None, session=None, nodeid=None, - path=Optional[Path], + path: Optional[Path] = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. # nodes.FSCollector.__init__(self, fspath, parent=parent) @@ -700,14 +699,6 @@ class Package(Module): func = partial(_call_with_optional_argument, teardown_module, self.obj) self.addfinalizer(func) - def gethookproxy(self, fspath: "os.PathLike[str]"): - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.gethookproxy(fspath) - - def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool: - warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2) - return self.session.isinitpath(path) - def _recurse(self, direntry: "os.DirEntry[str]") -> bool: if direntry.name == "__pycache__": return False @@ -715,9 +706,6 @@ class Package(Module): ihook = self.session.gethookproxy(fspath.parent) if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config): return False - norecursepatterns = self.config.getini("norecursedirs") - if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns): - return False return True def _collectfile( @@ -746,11 +734,13 @@ class Package(Module): def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: this_path = self.path.parent - init_module = this_path / "__init__.py" - if init_module.is_file() and path_matches_patterns( - init_module, self.config.getini("python_files") + + # Always collect the __init__ first. + if self.session.isinitpath(self.path) or path_matches_patterns( + self.path, self.config.getini("python_files") ): - yield Module.from_parent(self, path=init_module) + yield Module.from_parent(self, path=self.path) + pkg_prefixes: Set[Path] = set() for direntry in visit(str(this_path), recurse=self._recurse): path = Path(direntry.path) @@ -790,7 +780,8 @@ def _call_with_optional_argument(func, arg) -> None: def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: """Return the attribute from the given object to be used as a setup/teardown - xunit-style function, but only if not marked as a fixture to avoid calling it twice.""" + xunit-style function, but only if not marked as a fixture to avoid calling it twice. + """ for name in names: meth: Optional[object] = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: @@ -848,7 +839,7 @@ class Class(PyCollector): other fixtures (#517). """ setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) - teardown_class = getattr(self.obj, "teardown_class", None) + teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) if setup_class is None and teardown_class is None: return @@ -885,12 +876,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 = getattr(self.obj, teardown_name, None) + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) emit_nose_teardown_warning = False if teardown_method is None and has_nose: teardown_name = "teardown" emit_nose_teardown_warning = True - teardown_method = getattr(self.obj, teardown_name, None) + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return @@ -956,10 +947,20 @@ def hasnew(obj: object) -> bool: @final -@attr.s(frozen=True, auto_attribs=True, slots=True) +@dataclasses.dataclass(frozen=True) class IdMaker: """Make IDs for a parametrization.""" + __slots__ = ( + "argnames", + "parametersets", + "idfn", + "ids", + "config", + "nodeid", + "func_name", + ) + # The argnames of the parametrization. argnames: Sequence[str] # The ParameterSets of the parametrization. @@ -1109,7 +1110,7 @@ class IdMaker: @final -@attr.s(frozen=True, slots=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class CallSpec2: """A planned parameterized invocation of a test function. @@ -1120,18 +1121,18 @@ class CallSpec2: # arg name -> arg value which will be passed to the parametrized test # function (direct parameterization). - funcargs: Dict[str, object] = attr.Factory(dict) + funcargs: Dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg value which will be passed to a fixture of the same name # (indirect parametrization). - params: Dict[str, object] = attr.Factory(dict) + params: Dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: Dict[str, int] = attr.Factory(dict) + indices: Dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. - _arg2scope: Dict[str, Scope] = attr.Factory(dict) + _arg2scope: Dict[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". - _idlist: List[str] = attr.Factory(list) + _idlist: List[str] = dataclasses.field(default_factory=list) # Marks which will be applied to the item. - marks: List[Mark] = attr.Factory(list) + marks: List[Mark] = dataclasses.field(default_factory=list) def setmulti( self, @@ -1163,9 +1164,9 @@ class CallSpec2: return CallSpec2( funcargs=funcargs, params=params, - arg2scope=arg2scope, indices=indices, - idlist=[*self._idlist, id], + _arg2scope=arg2scope, + _idlist=[*self._idlist, id], marks=[*self.marks, *normalize_mark_list(marks)], ) @@ -1791,7 +1792,7 @@ class Function(PyobjMixin, nodes.Item): def setup(self) -> None: self._request._fillfixtures() - def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None: + def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False): code = _pytest._code.Code.from_function(get_real_func(self.obj)) path, firstlineno = code.path, code.firstlineno @@ -1803,14 +1804,21 @@ class Function(PyobjMixin, nodes.Item): ntraceback = ntraceback.filter(filter_traceback) if not ntraceback: ntraceback = traceback + ntraceback = ntraceback.filter(excinfo) - excinfo.traceback = ntraceback.filter() # issue364: mark all but first and last frames to # only show a single-line message for each frame. if self.config.getoption("tbstyle", "auto") == "auto": - if len(excinfo.traceback) > 2: - for entry in excinfo.traceback[1:-1]: - entry.set_repr_style("short") + if len(ntraceback) > 2: + ntraceback = Traceback( + entry + if i == 0 or i == len(ntraceback) - 1 + else entry.with_repr_style("short") + for i, entry in enumerate(ntraceback) + ) + + return ntraceback + return excinfo.traceback # TODO: Type ignored -- breaks Liskov Substitution. def repr_failure( # type: ignore[override] diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 515d437f0..4213bd098 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -8,7 +8,7 @@ from types import TracebackType from typing import Any from typing import Callable from typing import cast -from typing import Generic +from typing import ContextManager from typing import List from typing import Mapping from typing import Optional @@ -269,10 +269,16 @@ class ApproxMapping(ApproxBase): max_abs_diff = max( max_abs_diff, abs(approx_value.expected - other_value) ) - max_rel_diff = max( - max_rel_diff, - abs((approx_value.expected - other_value) / approx_value.expected), - ) + 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 + ), + ) different_ids.append(approx_key) message_data = [ @@ -801,8 +807,8 @@ 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 - exception types are excepted. + The expected exception type, or a tuple if one of multiple possible + exception types are expected. :kwparam str | typing.Pattern[str] | None match: If specified, a string containing a regular expression, or a regular expression object, that is tested against the string @@ -918,10 +924,10 @@ def raises( # noqa: F811 f"any special code to say 'this should never raise an exception'." ) if isinstance(expected_exception, type): - excepted_exceptions: Tuple[Type[E], ...] = (expected_exception,) + expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) else: - excepted_exceptions = expected_exception - for exc in excepted_exceptions: + expected_exceptions = expected_exception + for exc in expected_exceptions: if not isinstance(exc, type) or not issubclass(exc, BaseException): msg = "expected exception must be a BaseException type, not {}" # type: ignore[unreachable] not_a = exc.__name__ if isinstance(exc, type) else type(exc).__name__ @@ -944,11 +950,7 @@ def raises( # noqa: F811 try: func(*args[1:], **kwargs) except expected_exception as e: - # We just caught the exception - there is a traceback. - assert e.__traceback__ is not None - return _pytest._code.ExceptionInfo.from_exc_info( - (type(e), e, e.__traceback__) - ) + return _pytest._code.ExceptionInfo.from_exception(e) fail(message) @@ -957,7 +959,7 @@ raises.Exception = fail.Exception # type: ignore @final -class RaisesContext(Generic[E]): +class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, expected_exception: Union[Type[E], Tuple[Type[E], ...]], diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index c35f7087e..74e8794b2 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,3 +1,4 @@ +import dataclasses import os from io import StringIO from pprint import pprint @@ -16,8 +17,6 @@ from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -import attr - from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionRepr @@ -263,6 +262,8 @@ class TestReport(BaseReport): when: "Literal['setup', 'call', 'teardown']", sections: Iterable[Tuple[str, str]] = (), duration: float = 0, + start: float = 0, + stop: float = 0, user_properties: Optional[Iterable[Tuple[str, object]]] = None, **extra, ) -> None: @@ -272,6 +273,8 @@ class TestReport(BaseReport): #: A (filesystempath, lineno, domaininfo) tuple indicating the #: actual location of a test item - it might be different from the #: collected one e.g. if a method is inherited from a different module. + #: The filesystempath may be relative to ``config.rootdir``. + #: The line number is 0-based. self.location: Tuple[str, Optional[int], str] = location #: A name -> value dictionary containing all keywords and @@ -300,6 +303,11 @@ class TestReport(BaseReport): #: Time it took to run just the test. self.duration: float = duration + #: The system time when the call started, in seconds since the epoch. + self.start: float = start + #: The system time when the call ended, in seconds since the epoch. + self.stop: float = stop + self.__dict__.update(extra) def __repr__(self) -> str: @@ -318,6 +326,8 @@ class TestReport(BaseReport): # Remove "collect" from the Literal type -- only for collection calls. assert when != "collect" duration = call.duration + start = call.start + stop = call.stop keywords = {x: 1 for x in item.keywords} excinfo = call.excinfo sections = [] @@ -337,6 +347,9 @@ class TestReport(BaseReport): elif isinstance(excinfo.value, skip.Exception): outcome = "skipped" r = excinfo._getreprcrash() + assert ( + r is not None + ), "There should always be a traceback entry for skipping a test." if excinfo.value._use_item_location: path, line = item.reportinfo()[:2] assert line is not None @@ -362,6 +375,8 @@ class TestReport(BaseReport): when, sections, duration, + start, + stop, user_properties=item.user_properties, ) @@ -407,7 +422,9 @@ class CollectReport(BaseReport): self.__dict__.update(extra) @property - def location(self): + def location( # type:ignore[override] + self, + ) -> Optional[Tuple[str, Optional[int], str]]: return (self.fspath, None, self.fspath) def __repr__(self) -> str: @@ -459,15 +476,15 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: def serialize_repr_entry( entry: Union[ReprEntry, ReprEntryNative] ) -> Dict[str, Any]: - data = attr.asdict(entry) + data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): - data[key] = attr.asdict(value) + data[key] = dataclasses.asdict(value) entry_data = {"type": type(entry).__name__, "data": data} return entry_data def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: - result = attr.asdict(reprtraceback) + result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries ] @@ -477,7 +494,7 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: reprcrash: Optional[ReprFileLocation], ) -> Optional[Dict[str, Any]]: if reprcrash is not None: - return attr.asdict(reprcrash) + return dataclasses.asdict(reprcrash) else: return None @@ -573,7 +590,6 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: and "reprcrash" in reportdict["longrepr"] and "reprtraceback" in reportdict["longrepr"] ): - reprtraceback = deserialize_repr_traceback( reportdict["longrepr"]["reprtraceback"] ) @@ -594,7 +610,10 @@ def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: ExceptionChainRepr, ReprExceptionInfo ] = ExceptionChainRepr(chain) else: - exception_info = ReprExceptionInfo(reprtraceback, reprcrash) + exception_info = ReprExceptionInfo( + reprtraceback=reprtraceback, + reprcrash=reprcrash, + ) for section in reportdict["longrepr"]["sections"]: exception_info.addsection(*section) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 584c3229d..f861c05a4 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -1,5 +1,6 @@ """Basic collect and runtest protocol implementations.""" import bdb +import dataclasses import os import sys from typing import Callable @@ -14,8 +15,6 @@ from typing import TYPE_CHECKING from typing import TypeVar from typing import Union -import attr - from .reports import BaseReport from .reports import CollectErrorRepr from .reports import CollectReport @@ -35,6 +34,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 @@ -265,7 +267,7 @@ TResult = TypeVar("TResult", covariant=True) @final -@attr.s(repr=False, init=False, auto_attribs=True) +@dataclasses.dataclass class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" @@ -512,22 +514,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 diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index b20442350..26ce73758 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -1,4 +1,5 @@ """Support for skip/xfail functions and markers.""" +import dataclasses import os import platform import sys @@ -9,8 +10,6 @@ from typing import Optional from typing import Tuple from typing import Type -import attr - from _pytest.config import Config from _pytest.config import hookimpl from _pytest.config.argparsing import Parser @@ -157,7 +156,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, return result, reason -@attr.s(slots=True, frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Skip: """The result of evaluate_skip_marks().""" @@ -192,10 +191,12 @@ def evaluate_skip_marks(item: Item) -> Optional[Skip]: return None -@attr.s(slots=True, frozen=True, auto_attribs=True) +@dataclasses.dataclass(frozen=True) class Xfail: """The result of evaluate_xfail_marks().""" + __slots__ = ("reason", "run", "strict", "raises") + reason: str run: bool strict: bool diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 84f1a6ce8..74ad9dbd4 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -48,6 +48,10 @@ 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, []) @@ -119,4 +123,8 @@ 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) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 9739a467a..b0cdb58ce 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -3,10 +3,12 @@ This is a good source for looking at the various reporting hooks. """ import argparse +import dataclasses import datetime import inspect import platform import sys +import textwrap import warnings from collections import Counter from functools import partial @@ -19,6 +21,7 @@ from typing import Dict from typing import Generator from typing import List from typing import Mapping +from typing import NamedTuple from typing import Optional from typing import Sequence from typing import Set @@ -27,7 +30,6 @@ from typing import Tuple from typing import TYPE_CHECKING from typing import Union -import attr import pluggy import _pytest._version @@ -111,6 +113,26 @@ class MoreQuietAction(argparse.Action): namespace.quiet = getattr(namespace, "quiet", 0) + 1 +class TestShortLogReport(NamedTuple): + """Used to store the test status result category, shortletter and verbose word. + For example ``"rerun", "R", ("RERUN", {"yellow": True})``. + + :ivar category: + The class of result, for example ``“passed”``, ``“skipped”``, ``“error”``, or the empty string. + + :ivar letter: + The short letter shown as testing progresses, for example ``"."``, ``"s"``, ``"E"``, or the empty string. + + :ivar word: + Verbose word is shown as testing progresses in verbose mode, for example ``"PASSED"``, ``"SKIPPED"``, + ``"ERROR"``, or the empty string. + """ + + category: str + letter: str + word: Union[str, Tuple[str, Mapping[str, bool]]] + + def pytest_addoption(parser: Parser) -> None: group = parser.getgroup("terminal reporting", "Reporting", after="general") group._addoption( @@ -178,6 +200,12 @@ def pytest_addoption(parser: Parser) -> None: default=False, help="Show locals in tracebacks (disabled by default)", ) + group._addoption( + "--no-showlocals", + action="store_false", + dest="showlocals", + help="Hide locals in tracebacks (negate --showlocals passed through addopts)", + ) group._addoption( "--tb", metavar="style", @@ -223,7 +251,8 @@ def pytest_addoption(parser: Parser) -> None: parser.addini( "console_output_style", help='Console output: "classic", or with additional progress information ' - '("progress" (percentage) | "count")', + '("progress" (percentage) | "count" | "progress-even-when-capture-no" (forces ' + "progress even when capture=no)", default="progress", ) @@ -281,7 +310,7 @@ def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: return outcome, letter, outcome.upper() -@attr.s(auto_attribs=True) +@dataclasses.dataclass class WarningReport: """Simple structure to hold warnings information captured by ``pytest_warning_recorded``. @@ -340,14 +369,19 @@ class TerminalReporter: def _determine_show_progress_info(self) -> "Literal['progress', 'count', False]": """Return whether we should display progress information based on the current config.""" - # do not show progress if we are not capturing output (#3038) - if self.config.getoption("capture", "no") == "no": + # do not show progress if we are not capturing output (#3038) unless explicitly + # overridden by progress-even-when-capture-no + if ( + self.config.getoption("capture", "no") == "no" + and self.config.getini("console_output_style") + != "progress-even-when-capture-no" + ): return False # do not show progress if we are showing fixture setup/teardown if self.config.getoption("setupshow", False): return False cfg: str = self.config.getini("console_output_style") - if cfg == "progress": + if cfg == "progress" or cfg == "progress-even-when-capture-no": return "progress" elif cfg == "count": return "count" @@ -414,6 +448,28 @@ class TerminalReporter: self._tw.line() self.currentfspath = None + def wrap_write( + self, + content: str, + *, + flush: bool = False, + margin: int = 8, + line_sep: str = "\n", + **markup: bool, + ) -> None: + """Wrap message with margin for progress info.""" + width_of_current_line = self._tw.width_of_current_line + wrapped = line_sep.join( + textwrap.wrap( + " " * width_of_current_line + content, + width=self._screen_width - margin, + drop_whitespace=True, + replace_whitespace=False, + ), + ) + wrapped = wrapped[width_of_current_line:] + self._tw.write(wrapped, flush=flush, **markup) + def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: self._tw.write(content, flush=flush, **markup) @@ -513,10 +569,11 @@ class TerminalReporter: def pytest_runtest_logreport(self, report: TestReport) -> None: self._tests_ran = True rep = report - res: Tuple[ - str, str, Union[str, Tuple[str, Mapping[str, bool]]] - ] = self.config.hook.pytest_report_teststatus(report=rep, config=self.config) - category, letter, word = res + + res = TestShortLogReport( + *self.config.hook.pytest_report_teststatus(report=rep, config=self.config) + ) + category, letter, word = res.category, res.letter, res.word if not isinstance(word, tuple): markup = None else: @@ -560,7 +617,7 @@ class TerminalReporter: formatted_reason = f" ({reason})" if reason and formatted_reason is not None: - self._tw.write(formatted_reason) + self.wrap_write(formatted_reason) if self._show_progress_info: self._write_progress_information_filling_space() else: @@ -727,16 +784,14 @@ class TerminalReporter: self.write_line(line) def pytest_report_header(self, config: Config) -> List[str]: - line = "rootdir: %s" % config.rootpath + result = [f"rootdir: {config.rootpath}"] if config.inipath: - line += ", configfile: " + bestrelpath(config.rootpath, config.inipath) + result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) if config.args_source == Config.ArgsSource.TESTPATHS: testpaths: List[str] = config.getini("testpaths") - line += ", testpaths: {}".format(", ".join(testpaths)) - - result = [line] + result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 9497a0d49..3cc2bace5 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -1,40 +1,66 @@ """Support for providing temporary directories to test functions.""" +import dataclasses import os import re -import sys import tempfile from pathlib import Path +from shutil import rmtree +from typing import Any +from typing import Dict +from typing import Generator from typing import Optional +from typing import TYPE_CHECKING +from typing import Union -import attr +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"] + + +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 _pytest.compat import final +from .pathlib import cleanup_dead_symlinks +from _pytest.compat import final, get_user_id 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) +@dataclasses.dataclass class TempPathFactory: """Factory for temporary directories under the common base temp directory. The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp = attr.ib(type=Optional[Path]) - _trace = attr.ib() - _basetemp = attr.ib(type=Optional[Path]) + _given_basetemp: Optional[Path] + # pluggy TagTracerSub, not currently exposed, so Any. + _trace: Any + _basetemp: Optional[Path] + _retention_count: int + _retention_policy: "RetentionType" def __init__( self, given_basetemp: Optional[Path], + retention_count: int, + retention_policy: "RetentionType", trace, basetemp: Optional[Path] = None, *, @@ -49,6 +75,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 +91,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 input: {policy}." + ) + return cls( given_basetemp=config.option.basetemp, trace=config.trace.get("tmpdir"), + retention_count=count, + retention_policy=policy, _ispytest=True, ) @@ -133,23 +175,23 @@ class TempPathFactory: # Also, to keep things private, fixup any world-readable temp # rootdir's permissions. Historically 0o755 was used, so we can't # just error out on this, at least for a while. - if sys.platform != "win32": - uid = os.getuid() + uid = get_user_id() + if uid is not None: rootdir_stat = rootdir.stat() - # getuid shouldn't fail, but cpython defines such a case. - # Let's hope for the best. - if uid != -1: - if rootdir_stat.st_uid != uid: - raise OSError( - f"The temporary directory {rootdir} is not owned by the current user. " - "Fix this and try again." - ) - if (rootdir_stat.st_mode & 0o077) != 0: - os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + if rootdir_stat.st_uid != uid: + raise OSError( + f"The temporary directory {rootdir} is not owned by the current user. " + "Fix this and try again." + ) + if (rootdir_stat.st_mode & 0o077) != 0: + os.chmod(rootdir, rootdir_stat.st_mode & ~0o077) + keep = self._retention_count + if self._retention_policy == "none": + keep = 0 basetemp = make_numbered_dir_with_cleanup( prefix="pytest-", root=rootdir, - keep=3, + keep=keep, lock_timeout=LOCK_TIMEOUT, mode=0o700, ) @@ -184,6 +226,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="all", + ) + + @fixture(scope="session") def tmp_path_factory(request: FixtureRequest) -> TempPathFactory: """Return a :class:`pytest.TempPathFactory` instance for the test session.""" @@ -200,17 +257,68 @@ 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 old bases are removed after 3 sessions, to aid in debugging. + This behavior can be configured with :confval:`tmp_path_retention_count` and + :confval:`tmp_path_retention_policy`. + If ``--basetemp`` is used then it is cleared each session. See :ref:`base temporary directory`. The returned object is a :class:`pathlib.Path` object. """ - 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] + + +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 + basetemp = tmp_path_factory._basetemp + if basetemp is None: + return + + policy = tmp_path_factory._retention_policy + if ( + exitstatus == 0 + and policy == "failed" + and tmp_path_factory._given_basetemp is None + ): + if basetemp.is_dir(): + # 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(basetemp, ignore_errors=True) + + # Remove dead symlinks. + if basetemp.is_dir(): + cleanup_dead_symlinks(basetemp) + + +@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 diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index c2df98653..d42a12a3a 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -298,6 +298,9 @@ class TestCaseFunction(Function): def stopTest(self, testcase: "unittest.TestCase") -> None: pass + def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: + pass + def runtest(self) -> None: from _pytest.debugging import maybe_wrap_pytest_function_for_tracing @@ -331,15 +334,16 @@ class TestCaseFunction(Function): finally: delattr(self._testcase, self.name) - def _prunetraceback( + def _traceback_filter( self, excinfo: _pytest._code.ExceptionInfo[BaseException] - ) -> None: - super()._prunetraceback(excinfo) - traceback = excinfo.traceback.filter( - lambda x: not x.frame.f_globals.get("__unittest") + ) -> _pytest._code.Traceback: + traceback = super()._traceback_filter(excinfo) + ntraceback = traceback.filter( + lambda x: not x.frame.f_globals.get("__unittest"), ) - if traceback: - excinfo.traceback = traceback + if not ntraceback: + ntraceback = traceback + return ntraceback @hookimpl(tryfirst=True) diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 620860c1b..f6b0a3a69 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,3 +1,4 @@ +import dataclasses import inspect import warnings from types import FunctionType @@ -6,8 +7,6 @@ from typing import Generic from typing import Type from typing import TypeVar -import attr - from _pytest.compat import final @@ -57,6 +56,12 @@ class PytestRemovedIn8Warning(PytestDeprecationWarning): __module__ = "pytest" +class PytestRemovedIn9Warning(PytestDeprecationWarning): + """Warning class for features that will be removed in pytest 9.""" + + __module__ = "pytest" + + class PytestReturnNotNoneWarning(PytestRemovedIn8Warning): """Warning emitted when a test function is returning value other than None.""" @@ -130,7 +135,7 @@ _W = TypeVar("_W", bound=PytestWarning) @final -@attr.s(auto_attribs=True) +@dataclasses.dataclass class UnformattedWarning(Generic[_W]): """A warning meant to be formatted during runtime. @@ -150,7 +155,7 @@ def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: """ Issue the warning :param:`message` for the definition of the given :param:`method` - this helps to log warnigns for functions defined prior to finding an issue with them + this helps to log warnings for functions defined prior to finding an issue with them (like hook wrappers being marked in a legacy mechanism) """ lineno = method.__code__.co_firstlineno diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 4aaa94452..bb293ec08 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -49,6 +49,8 @@ def catch_warnings_for_item( warnings.filterwarnings("always", category=DeprecationWarning) warnings.filterwarnings("always", category=PendingDeprecationWarning) + warnings.filterwarnings("error", category=pytest.PytestRemovedIn8Warning) + apply_warning_filters(config_filters, cmdline_filters) # apply filters from "filterwarnings" marks diff --git a/src/py.py b/src/py.py new file mode 100644 index 000000000..7813c9b93 --- /dev/null +++ b/src/py.py @@ -0,0 +1,10 @@ +# shim for pylib going away +# if pylib is installed this file will get skipped +# (`py/__init__.py` has higher precedence) +import sys + +import _pytest._py.error as error +import _pytest._py.path as path + +sys.modules["py.error"] = error +sys.modules["py.path"] = path diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index f25ecde9c..58ddb3288 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -62,6 +62,7 @@ from _pytest.reports import TestReport from _pytest.runner import CallInfo from _pytest.stash import Stash from _pytest.stash import StashKey +from _pytest.terminal import TestShortLogReport from _pytest.tmpdir import TempPathFactory from _pytest.warning_types import PytestAssertRewriteWarning from _pytest.warning_types import PytestCacheWarning @@ -70,6 +71,7 @@ from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestExperimentalApiWarning from _pytest.warning_types import PytestRemovedIn8Warning +from _pytest.warning_types import PytestRemovedIn9Warning from _pytest.warning_types import PytestReturnNotNoneWarning from _pytest.warning_types import PytestUnhandledCoroutineWarning from _pytest.warning_types import PytestUnhandledThreadExceptionWarning @@ -130,6 +132,7 @@ __all__ = [ "PytestDeprecationWarning", "PytestExperimentalApiWarning", "PytestRemovedIn8Warning", + "PytestRemovedIn9Warning", "PytestReturnNotNoneWarning", "Pytester", "PytestPluginManager", @@ -152,6 +155,7 @@ __all__ = [ "TempPathFactory", "Testdir", "TestReport", + "TestShortLogReport", "UsageError", "WarningsRecorder", "warns", diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py new file mode 100644 index 000000000..348682b53 --- /dev/null +++ b/testing/_py/test_local.py @@ -0,0 +1,1576 @@ +import contextlib +import multiprocessing +import os +import sys +import time +import warnings +from unittest import mock + +import pytest +from py import error +from py.path import local + + +@contextlib.contextmanager +def ignore_encoding_warning(): + with warnings.catch_warnings(): + with contextlib.suppress(NameError): # new in 3.10 + warnings.simplefilter("ignore", EncodingWarning) + yield + + +class CommonFSTests: + def test_constructor_equality(self, path1): + p = path1.__class__(path1) + assert p == path1 + + def test_eq_nonstring(self, path1): + p1 = path1.join("sampledir") + p2 = path1.join("sampledir") + assert p1 == p2 + + def test_new_identical(self, path1): + assert path1 == path1.new() + + def test_join(self, path1): + p = path1.join("sampledir") + strp = str(p) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + + def test_join_normalized(self, path1): + newpath = path1.join(path1.sep + "sampledir") + strp = str(newpath) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + newpath = path1.join((path1.sep * 2) + "sampledir") + strp = str(newpath) + assert strp.endswith("sampledir") + assert strp.startswith(str(path1)) + + def test_join_noargs(self, path1): + newpath = path1.join() + assert path1 == newpath + + def test_add_something(self, path1): + p = path1.join("sample") + p = p + "dir" + assert p.check() + assert p.exists() + assert p.isdir() + assert not p.isfile() + + def test_parts(self, path1): + newpath = path1.join("sampledir", "otherfile") + par = newpath.parts()[-3:] + assert par == [path1, path1.join("sampledir"), newpath] + + revpar = newpath.parts(reverse=True)[:3] + assert revpar == [newpath, path1.join("sampledir"), path1] + + def test_common(self, path1): + other = path1.join("sampledir") + x = other.common(path1) + assert x == path1 + + # def test_parents_nonexisting_file(self, path1): + # newpath = path1 / 'dirnoexist' / 'nonexisting file' + # par = list(newpath.parents()) + # assert par[:2] == [path1 / 'dirnoexist', path1] + + def test_basename_checks(self, path1): + newpath = path1.join("sampledir") + assert newpath.check(basename="sampledir") + assert newpath.check(notbasename="xyz") + assert newpath.basename == "sampledir" + + def test_basename(self, path1): + newpath = path1.join("sampledir") + assert newpath.check(basename="sampledir") + assert newpath.basename, "sampledir" + + def test_dirname(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirname == str(path1) + + def test_dirpath(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirpath() == path1 + + def test_dirpath_with_args(self, path1): + newpath = path1.join("sampledir") + assert newpath.dirpath("x") == path1.join("x") + + def test_newbasename(self, path1): + newpath = path1.join("samplefile") + newbase = newpath.new(basename="samplefile2") + assert newbase.basename == "samplefile2" + assert newbase.dirpath() == newpath.dirpath() + + def test_not_exists(self, path1): + assert not path1.join("does_not_exist").check() + assert path1.join("does_not_exist").check(exists=0) + + def test_exists(self, path1): + assert path1.join("samplefile").check() + assert path1.join("samplefile").check(exists=1) + assert path1.join("samplefile").exists() + assert path1.join("samplefile").isfile() + assert not path1.join("samplefile").isdir() + + def test_dir(self, path1): + # print repr(path1.join("sampledir")) + assert path1.join("sampledir").check(dir=1) + assert path1.join("samplefile").check(notdir=1) + assert not path1.join("samplefile").check(dir=1) + assert path1.join("samplefile").exists() + assert not path1.join("samplefile").isdir() + assert path1.join("samplefile").isfile() + + def test_fnmatch_file(self, path1): + assert path1.join("samplefile").check(fnmatch="s*e") + assert path1.join("samplefile").fnmatch("s*e") + assert not path1.join("samplefile").fnmatch("s*x") + assert not path1.join("samplefile").check(fnmatch="s*x") + + # def test_fnmatch_dir(self, path1): + + # pattern = path1.sep.join(['s*file']) + # sfile = path1.join("samplefile") + # assert sfile.check(fnmatch=pattern) + + def test_relto(self, path1): + p = path1.join("sampledir", "otherfile") + assert p.relto(path1) == p.sep.join(["sampledir", "otherfile"]) + assert p.check(relto=path1) + assert path1.check(notrelto=p) + assert not path1.check(relto=p) + + def test_bestrelpath(self, path1): + curdir = path1 + sep = curdir.sep + s = curdir.bestrelpath(curdir) + assert s == "." + s = curdir.bestrelpath(curdir.join("hello", "world")) + assert s == "hello" + sep + "world" + + s = curdir.bestrelpath(curdir.dirpath().join("sister")) + assert s == ".." + sep + "sister" + assert curdir.bestrelpath(curdir.dirpath()) == ".." + + assert curdir.bestrelpath("hello") == "hello" + + def test_relto_not_relative(self, path1): + l1 = path1.join("bcde") + l2 = path1.join("b") + assert not l1.relto(l2) + assert not l2.relto(l1) + + def test_listdir(self, path1): + p = path1.listdir() + assert path1.join("sampledir") in p + assert path1.join("samplefile") in p + with pytest.raises(error.ENOTDIR): + path1.join("samplefile").listdir() + + def test_listdir_fnmatchstring(self, path1): + p = path1.listdir("s*dir") + assert len(p) + assert p[0], path1.join("sampledir") + + def test_listdir_filter(self, path1): + p = path1.listdir(lambda x: x.check(dir=1)) + assert path1.join("sampledir") in p + assert not path1.join("samplefile") in p + + def test_listdir_sorted(self, path1): + p = path1.listdir(lambda x: x.check(basestarts="sample"), sort=True) + assert path1.join("sampledir") == p[0] + assert path1.join("samplefile") == p[1] + assert path1.join("samplepickle") == p[2] + + def test_visit_nofilter(self, path1): + lst = [] + for i in path1.visit(): + lst.append(i.relto(path1)) + assert "sampledir" in lst + assert path1.sep.join(["sampledir", "otherfile"]) in lst + + def test_visit_norecurse(self, path1): + lst = [] + for i in path1.visit(None, lambda x: x.basename != "sampledir"): + lst.append(i.relto(path1)) + assert "sampledir" in lst + assert not path1.sep.join(["sampledir", "otherfile"]) in lst + + @pytest.mark.parametrize( + "fil", + ["*dir", "*dir", pytest.mark.skip("sys.version_info <" " (3,6)")(b"*dir")], + ) + def test_visit_filterfunc_is_string(self, path1, fil): + lst = [] + for i in path1.visit(fil): + lst.append(i.relto(path1)) + assert len(lst), 2 + assert "sampledir" in lst + assert "otherdir" in lst + + def test_visit_ignore(self, path1): + p = path1.join("nonexisting") + assert list(p.visit(ignore=error.ENOENT)) == [] + + def test_visit_endswith(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(endswith="file")): + p.append(i.relto(path1)) + assert path1.sep.join(["sampledir", "otherfile"]) in p + assert "samplefile" in p + + def test_cmp(self, path1): + path1 = path1.join("samplefile") + path2 = path1.join("samplefile2") + assert (path1 < path2) == ("samplefile" < "samplefile2") + assert not (path1 < path1) + + def test_simple_read(self, path1): + with ignore_encoding_warning(): + x = path1.join("samplefile").read("r") + assert x == "samplefile\n" + + def test_join_div_operator(self, path1): + newpath = path1 / "/sampledir" / "/test//" + newpath2 = path1.join("sampledir", "test") + assert newpath == newpath2 + + def test_ext(self, path1): + newpath = path1.join("sampledir.ext") + assert newpath.ext == ".ext" + newpath = path1.join("sampledir") + assert not newpath.ext + + def test_purebasename(self, path1): + newpath = path1.join("samplefile.py") + assert newpath.purebasename == "samplefile" + + def test_multiple_parts(self, path1): + newpath = path1.join("samplefile.py") + dirname, purebasename, basename, ext = newpath._getbyspec( + "dirname,purebasename,basename,ext" + ) + assert str(path1).endswith(dirname) # be careful with win32 'drive' + assert purebasename == "samplefile" + assert basename == "samplefile.py" + assert ext == ".py" + + def test_dotted_name_ext(self, path1): + newpath = path1.join("a.b.c") + ext = newpath.ext + assert ext == ".c" + assert newpath.ext == ".c" + + def test_newext(self, path1): + newpath = path1.join("samplefile.py") + newext = newpath.new(ext=".txt") + assert newext.basename == "samplefile.txt" + assert newext.purebasename == "samplefile" + + def test_readlines(self, path1): + fn = path1.join("samplefile") + with ignore_encoding_warning(): + contents = fn.readlines() + assert contents == ["samplefile\n"] + + def test_readlines_nocr(self, path1): + fn = path1.join("samplefile") + with ignore_encoding_warning(): + contents = fn.readlines(cr=0) + assert contents == ["samplefile", ""] + + def test_file(self, path1): + assert path1.join("samplefile").check(file=1) + + def test_not_file(self, path1): + assert not path1.join("sampledir").check(file=1) + assert path1.join("sampledir").check(file=0) + + def test_non_existent(self, path1): + assert path1.join("sampledir.nothere").check(dir=0) + assert path1.join("sampledir.nothere").check(file=0) + assert path1.join("sampledir.nothere").check(notfile=1) + assert path1.join("sampledir.nothere").check(notdir=1) + assert path1.join("sampledir.nothere").check(notexists=1) + assert not path1.join("sampledir.nothere").check(notfile=0) + + # pattern = path1.sep.join(['s*file']) + # sfile = path1.join("samplefile") + # assert sfile.check(fnmatch=pattern) + + def test_size(self, path1): + url = path1.join("samplefile") + assert url.size() > len("samplefile") + + def test_mtime(self, path1): + url = path1.join("samplefile") + assert url.mtime() > 0 + + def test_relto_wrong_type(self, path1): + with pytest.raises(TypeError): + path1.relto(42) + + def test_load(self, path1): + p = path1.join("samplepickle") + obj = p.load() + assert type(obj) is dict + assert obj.get("answer", None) == 42 + + def test_visit_filesonly(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(file=1)): + p.append(i.relto(path1)) + assert "sampledir" not in p + assert path1.sep.join(["sampledir", "otherfile"]) in p + + def test_visit_nodotfiles(self, path1): + p = [] + for i in path1.visit(lambda x: x.check(dotfile=0)): + p.append(i.relto(path1)) + assert "sampledir" in p + assert path1.sep.join(["sampledir", "otherfile"]) in p + assert ".dotfile" not in p + + def test_visit_breadthfirst(self, path1): + lst = [] + for i in path1.visit(bf=True): + lst.append(i.relto(path1)) + for i, p in enumerate(lst): + if path1.sep in p: + for j in range(i, len(lst)): + assert path1.sep in lst[j] + break + else: + pytest.fail("huh") + + def test_visit_sort(self, path1): + lst = [] + for i in path1.visit(bf=True, sort=True): + lst.append(i.relto(path1)) + for i, p in enumerate(lst): + if path1.sep in p: + break + assert lst[:i] == sorted(lst[:i]) + assert lst[i:] == sorted(lst[i:]) + + def test_endswith(self, path1): + def chk(p): + return p.check(endswith="pickle") + + assert not chk(path1) + assert not chk(path1.join("samplefile")) + assert chk(path1.join("somepickle")) + + def test_copy_file(self, path1): + otherdir = path1.join("otherdir") + initpy = otherdir.join("__init__.py") + copied = otherdir.join("copied") + initpy.copy(copied) + try: + assert copied.check() + s1 = initpy.read_text(encoding="utf-8") + s2 = copied.read_text(encoding="utf-8") + assert s1 == s2 + finally: + if copied.check(): + copied.remove() + + def test_copy_dir(self, path1): + otherdir = path1.join("otherdir") + copied = path1.join("newdir") + try: + otherdir.copy(copied) + assert copied.check(dir=1) + assert copied.join("__init__.py").check(file=1) + s1 = otherdir.join("__init__.py").read_text(encoding="utf-8") + s2 = copied.join("__init__.py").read_text(encoding="utf-8") + assert s1 == s2 + finally: + if copied.check(dir=1): + copied.remove(rec=1) + + def test_remove_file(self, path1): + d = path1.ensure("todeleted") + assert d.check() + d.remove() + assert not d.check() + + def test_remove_dir_recursive_by_default(self, path1): + d = path1.ensure("to", "be", "deleted") + assert d.check() + p = path1.join("to") + p.remove() + assert not p.check() + + def test_ensure_dir(self, path1): + b = path1.ensure_dir("001", "002") + assert b.basename == "002" + assert b.isdir() + + def test_mkdir_and_remove(self, path1): + tmpdir = path1 + with pytest.raises(error.EEXIST): + tmpdir.mkdir("sampledir") + new = tmpdir.join("mktest1") + new.mkdir() + assert new.check(dir=1) + new.remove() + + new = tmpdir.mkdir("mktest") + assert new.check(dir=1) + new.remove() + assert tmpdir.join("mktest") == new + + def test_move_file(self, path1): + p = path1.join("samplefile") + newp = p.dirpath("moved_samplefile") + p.move(newp) + try: + assert newp.check(file=1) + assert not p.check() + finally: + dp = newp.dirpath() + if hasattr(dp, "revert"): + dp.revert() + else: + newp.move(p) + assert p.check() + + def test_move_dir(self, path1): + source = path1.join("sampledir") + dest = path1.join("moveddir") + source.move(dest) + assert dest.check(dir=1) + assert dest.join("otherfile").check(file=1) + assert not source.join("sampledir").check() + + def test_fspath_protocol_match_strpath(self, path1): + assert path1.__fspath__() == path1.strpath + + def test_fspath_func_match_strpath(self, path1): + from os import fspath + + assert fspath(path1) == path1.strpath + + @pytest.mark.skip("sys.version_info < (3,6)") + def test_fspath_open(self, path1): + f = path1.join("opentestfile") + open(f) + + @pytest.mark.skip("sys.version_info < (3,6)") + def test_fspath_fsencode(self, path1): + from os import fsencode + + assert fsencode(path1) == fsencode(path1.strpath) + + +def setuptestfs(path): + if path.join("samplefile").check(): + return + # print "setting up test fs for", repr(path) + samplefile = path.ensure("samplefile") + samplefile.write_text("samplefile\n", encoding="utf-8") + + execfile = path.ensure("execfile") + execfile.write_text("x=42", encoding="utf-8") + + execfilepy = path.ensure("execfile.py") + execfilepy.write_text("x=42", encoding="utf-8") + + d = {1: 2, "hello": "world", "answer": 42} + path.ensure("samplepickle").dump(d) + + sampledir = path.ensure("sampledir", dir=1) + sampledir.ensure("otherfile") + + otherdir = path.ensure("otherdir", dir=1) + otherdir.ensure("__init__.py") + + module_a = otherdir.ensure("a.py") + module_a.write_text("from .b import stuff as result\n", encoding="utf-8") + module_b = otherdir.ensure("b.py") + module_b.write_text('stuff="got it"\n', encoding="utf-8") + module_c = otherdir.ensure("c.py") + module_c.write_text( + """import py; +import otherdir.a +value = otherdir.a.result +""", + encoding="utf-8", + ) + module_d = otherdir.ensure("d.py") + module_d.write_text( + """import py; +from otherdir import a +value2 = a.result +""", + encoding="utf-8", + ) + + +win32only = pytest.mark.skipif( + "not (sys.platform == 'win32' or getattr(os, '_name', None) == 'nt')" +) +skiponwin32 = pytest.mark.skipif( + "sys.platform == 'win32' or getattr(os, '_name', None) == 'nt'" +) + +ATIME_RESOLUTION = 0.01 + + +@pytest.fixture(scope="session") +def path1(tmpdir_factory): + path = tmpdir_factory.mktemp("path") + setuptestfs(path) + yield path + assert path.join("samplefile").check() + + +@pytest.fixture +def fake_fspath_obj(request): + class FakeFSPathClass: + def __init__(self, path): + self._path = path + + def __fspath__(self): + return self._path + + return FakeFSPathClass(os.path.join("this", "is", "a", "fake", "path")) + + +def batch_make_numbered_dirs(rootdir, repeats): + for i in range(repeats): + dir_ = local.make_numbered_dir(prefix="repro-", rootdir=rootdir) + file_ = dir_.join("foo") + file_.write_text("%s" % i, encoding="utf-8") + actual = int(file_.read_text(encoding="utf-8")) + assert ( + actual == i + ), f"int(file_.read_text(encoding='utf-8')) is {actual} instead of {i}" + dir_.join(".lock").remove(ignore_errors=True) + return True + + +class TestLocalPath(CommonFSTests): + def test_join_normpath(self, tmpdir): + assert tmpdir.join(".") == tmpdir + p = tmpdir.join("../%s" % tmpdir.basename) + assert p == tmpdir + p = tmpdir.join("..//%s/" % tmpdir.basename) + assert p == tmpdir + + @skiponwin32 + def test_dirpath_abs_no_abs(self, tmpdir): + p = tmpdir.join("foo") + assert p.dirpath("/bar") == tmpdir.join("bar") + assert tmpdir.dirpath("/bar", abs=True) == local("/bar") + + def test_gethash(self, tmpdir): + from hashlib import md5 + from hashlib import sha1 as sha + + fn = tmpdir.join("testhashfile") + data = b"hello" + fn.write(data, mode="wb") + assert fn.computehash("md5") == md5(data).hexdigest() + assert fn.computehash("sha1") == sha(data).hexdigest() + with pytest.raises(ValueError): + fn.computehash("asdasd") + + def test_remove_removes_readonly_file(self, tmpdir): + readonly_file = tmpdir.join("readonly").ensure() + readonly_file.chmod(0) + readonly_file.remove() + assert not readonly_file.check(exists=1) + + def test_remove_removes_readonly_dir(self, tmpdir): + readonly_dir = tmpdir.join("readonlydir").ensure(dir=1) + readonly_dir.chmod(int("500", 8)) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_removes_dir_and_readonly_file(self, tmpdir): + readonly_dir = tmpdir.join("readonlydir").ensure(dir=1) + readonly_file = readonly_dir.join("readonlyfile").ensure() + readonly_file.chmod(0) + readonly_dir.remove() + assert not readonly_dir.check(exists=1) + + def test_remove_routes_ignore_errors(self, tmpdir, monkeypatch): + lst = [] + monkeypatch.setattr("shutil.rmtree", lambda *args, **kwargs: lst.append(kwargs)) + tmpdir.remove() + assert not lst[0]["ignore_errors"] + for val in (True, False): + lst[:] = [] + tmpdir.remove(ignore_errors=val) + assert lst[0]["ignore_errors"] == val + + def test_initialize_curdir(self): + assert str(local()) == os.getcwd() + + @skiponwin32 + def test_chdir_gone(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + pytest.raises(error.ENOENT, local) + assert path1.chdir() is None + assert os.getcwd() == str(path1) + + with pytest.raises(error.ENOENT): + with p.as_cwd(): + raise NotImplementedError + + @skiponwin32 + def test_chdir_gone_in_as_cwd(self, path1): + p = path1.ensure("dir_to_be_removed", dir=1) + p.chdir() + p.remove() + + with path1.as_cwd() as old: + assert old is None + + def test_as_cwd(self, path1): + dir = path1.ensure("subdir", dir=1) + old = local() + with dir.as_cwd() as x: + assert x == old + assert local() == dir + assert os.getcwd() == str(old) + + def test_as_cwd_exception(self, path1): + old = local() + dir = path1.ensure("subdir", dir=1) + with pytest.raises(ValueError): + with dir.as_cwd(): + raise ValueError() + assert old == local() + + def test_initialize_reldir(self, path1): + with path1.as_cwd(): + p = local("samplefile") + assert p.check() + + def test_tilde_expansion(self, monkeypatch, tmpdir): + monkeypatch.setenv("HOME", str(tmpdir)) + p = local("~", expanduser=True) + assert p == os.path.expanduser("~") + + @pytest.mark.skipif( + not sys.platform.startswith("win32"), reason="case insensitive only on windows" + ) + def test_eq_hash_are_case_insensitive_on_windows(self): + a = local("/some/path") + b = local("/some/PATH") + assert a == b + assert hash(a) == hash(b) + assert a in {b} + assert a in {b: "b"} + + def test_eq_with_strings(self, path1): + path1 = path1.join("sampledir") + path2 = str(path1) + assert path1 == path2 + assert path2 == path1 + path3 = path1.join("samplefile") + assert path3 != path2 + assert path2 != path3 + + def test_eq_with_none(self, path1): + assert path1 != None # noqa: E711 + + def test_eq_non_ascii_unicode(self, path1): + path2 = path1.join("temp") + path3 = path1.join("ação") + path4 = path1.join("ディレクトリ") + + assert path2 != path3 + assert path2 != path4 + assert path4 != path3 + + def test_gt_with_strings(self, path1): + path2 = path1.join("sampledir") + path3 = str(path1.join("ttt")) + assert path3 > path2 + assert path2 < path3 + assert path2 < "ttt" + assert "ttt" > path2 + path4 = path1.join("aaa") + lst = [path2, path4, path3] + assert sorted(lst) == [path4, path2, path3] + + def test_open_and_ensure(self, path1): + p = path1.join("sub1", "sub2", "file") + with p.open("w", ensure=1, encoding="utf-8") as f: + f.write("hello") + assert p.read_text(encoding="utf-8") == "hello" + + def test_write_and_ensure(self, path1): + p = path1.join("sub1", "sub2", "file") + p.write_text("hello", ensure=1, encoding="utf-8") + assert p.read_text(encoding="utf-8") == "hello" + + @pytest.mark.parametrize("bin", (False, True)) + def test_dump(self, tmpdir, bin): + path = tmpdir.join("dumpfile%s" % int(bin)) + try: + d = {"answer": 42} + path.dump(d, bin=bin) + f = path.open("rb+") + import pickle + + dnew = pickle.load(f) + assert d == dnew + finally: + f.close() + + def test_setmtime(self): + import tempfile + import time + + try: + fd, name = tempfile.mkstemp() + os.close(fd) + except AttributeError: + name = tempfile.mktemp() + open(name, "w").close() + try: + mtime = int(time.time()) - 100 + path = local(name) + assert path.mtime() != mtime + path.setmtime(mtime) + assert path.mtime() == mtime + path.setmtime() + assert path.mtime() != mtime + finally: + os.remove(name) + + def test_normpath(self, path1): + new1 = path1.join("/otherdir") + new2 = path1.join("otherdir") + assert str(new1) == str(new2) + + def test_mkdtemp_creation(self): + d = local.mkdtemp() + try: + assert d.check(dir=1) + finally: + d.remove(rec=1) + + def test_tmproot(self): + d = local.mkdtemp() + tmproot = local.get_temproot() + try: + assert d.check(dir=1) + assert d.dirpath() == tmproot + finally: + d.remove(rec=1) + + def test_chdir(self, tmpdir): + old = local() + try: + res = tmpdir.chdir() + assert str(res) == str(old) + assert os.getcwd() == str(tmpdir) + finally: + old.chdir() + + def test_ensure_filepath_withdir(self, tmpdir): + newfile = tmpdir.join("test1", "test") + newfile.ensure() + assert newfile.check(file=1) + newfile.write_text("42", encoding="utf-8") + newfile.ensure() + s = newfile.read_text(encoding="utf-8") + assert s == "42" + + def test_ensure_filepath_withoutdir(self, tmpdir): + newfile = tmpdir.join("test1file") + t = newfile.ensure() + assert t == newfile + assert newfile.check(file=1) + + def test_ensure_dirpath(self, tmpdir): + newfile = tmpdir.join("test1", "testfile") + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + + def test_ensure_non_ascii_unicode(self, tmpdir): + newfile = tmpdir.join("ação", "ディレクトリ") + t = newfile.ensure(dir=1) + assert t == newfile + assert newfile.check(dir=1) + + @pytest.mark.xfail(run=False, reason="unreliable est for long filenames") + def test_long_filenames(self, tmpdir): + if sys.platform == "win32": + pytest.skip("win32: work around needed for path length limit") + # see http://codespeak.net/pipermail/py-dev/2008q2/000922.html + + # testing paths > 260 chars (which is Windows' limitation, but + # 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 # type:ignore[unreachable] + l1 = tmpdir.join(newfilename) + l1.ensure(file=True) + l1.write_text("foo", encoding="utf-8") + l2 = tmpdir.join(newfilename) + assert l2.read_text(encoding="utf-8") == "foo" + + def test_visit_depth_first(self, tmpdir): + tmpdir.ensure("a", "1") + tmpdir.ensure("b", "2") + p3 = tmpdir.ensure("breadth") + lst = list(tmpdir.visit(lambda x: x.check(file=1))) + assert len(lst) == 3 + # check that breadth comes last + assert lst[2] == p3 + + def test_visit_rec_fnmatch(self, tmpdir): + p1 = tmpdir.ensure("a", "123") + tmpdir.ensure(".b", "345") + lst = list(tmpdir.visit("???", rec="[!.]*")) + assert len(lst) == 1 + # check that breadth comes last + assert lst[0] == p1 + + def test_fnmatch_file_abspath(self, tmpdir): + b = tmpdir.join("a", "b") + assert b.fnmatch(os.sep.join("ab")) + pattern = os.sep.join([str(tmpdir), "*", "b"]) + assert b.fnmatch(pattern) + + def test_sysfind(self): + name = sys.platform == "win32" and "cmd" or "test" + x = local.sysfind(name) + assert x.check(file=1) + assert local.sysfind("jaksdkasldqwe") is None + assert local.sysfind(name, paths=[]) is None + x2 = local.sysfind(name, paths=[x.dirpath()]) + assert x2 == x + + def test_fspath_protocol_other_class(self, fake_fspath_obj): + # py.path is always absolute + py_path = local(fake_fspath_obj) + str_path = fake_fspath_obj.__fspath__() + assert py_path.check(endswith=str_path) + assert py_path.join(fake_fspath_obj).strpath == os.path.join( + py_path.strpath, str_path + ) + + def test_make_numbered_dir_multiprocess_safe(self, tmpdir): + # https://github.com/pytest-dev/py/issues/30 + with multiprocessing.Pool() as pool: + results = [ + pool.apply_async(batch_make_numbered_dirs, [tmpdir, 100]) + for _ in range(20) + ] + for r in results: + assert r.get() + + +class TestExecutionOnWindows: + pytestmark = win32only + + def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch): + monkeypatch.setenv("PATH", str(tmpdir), prepend=os.pathsep) + tmpdir.ensure("hello") + h = tmpdir.ensure("hello.bat") + x = local.sysfind("hello") + assert x == h + + +class TestExecution: + pytestmark = skiponwin32 + + def test_sysfind_no_permisson_ignored(self, monkeypatch, tmpdir): + noperm = tmpdir.ensure("noperm", dir=True) + monkeypatch.setenv("PATH", str(noperm), prepend=":") + noperm.chmod(0) + try: + assert local.sysfind("jaksdkasldqwe") is None + finally: + noperm.chmod(0o644) + + def test_sysfind_absolute(self): + x = local.sysfind("test") + assert x.check(file=1) + y = local.sysfind(str(x)) + assert y.check(file=1) + assert y == x + + def test_sysfind_multiple(self, tmpdir, monkeypatch): + monkeypatch.setenv( + "PATH", "{}:{}".format(tmpdir.ensure("a"), tmpdir.join("b")), prepend=":" + ) + tmpdir.ensure("b", "a") + x = local.sysfind("a", checker=lambda x: x.dirpath().basename == "b") + assert x.basename == "a" + assert x.dirpath().basename == "b" + assert local.sysfind("a", checker=lambda x: None) is None + + def test_sysexec(self): + x = local.sysfind("ls") + out = x.sysexec("-a") + for x in local().listdir(): + assert out.find(x.basename) != -1 + + def test_sysexec_failing(self): + try: + from py._process.cmdexec import ExecutionFailed # py library + except ImportError: + ExecutionFailed = RuntimeError # py vendored + x = local.sysfind("false") + with pytest.raises(ExecutionFailed): + x.sysexec("aksjdkasjd") + + def test_make_numbered_dir(self, tmpdir): + tmpdir.ensure("base.not_an_int", dir=1) + for i in range(10): + numdir = local.make_numbered_dir( + prefix="base.", rootdir=tmpdir, keep=2, lock_timeout=0 + ) + assert numdir.check() + assert numdir.basename == "base.%d" % i + if i >= 1: + assert numdir.new(ext=str(i - 1)).check() + if i >= 2: + assert numdir.new(ext=str(i - 2)).check() + if i >= 3: + assert not numdir.new(ext=str(i - 3)).check() + + def test_make_numbered_dir_case(self, tmpdir): + """make_numbered_dir does not make assumptions on the underlying + filesystem based on the platform and will assume it _could_ be case + insensitive. + + See issues: + - https://github.com/pytest-dev/pytest/issues/708 + - https://github.com/pytest-dev/pytest/issues/3451 + """ + d1 = local.make_numbered_dir( + prefix="CAse.", + rootdir=tmpdir, + keep=2, + lock_timeout=0, + ) + d2 = local.make_numbered_dir( + prefix="caSE.", + rootdir=tmpdir, + keep=2, + lock_timeout=0, + ) + assert str(d1).lower() != str(d2).lower() + assert str(d2).endswith(".1") + + def test_make_numbered_dir_NotImplemented_Error(self, tmpdir, monkeypatch): + def notimpl(x, y): + raise NotImplementedError(42) + + monkeypatch.setattr(os, "symlink", notimpl) + x = tmpdir.make_numbered_dir(rootdir=tmpdir, lock_timeout=0) + assert x.relto(tmpdir) + assert x.check() + + def test_locked_make_numbered_dir(self, tmpdir): + for i in range(10): + numdir = local.make_numbered_dir(prefix="base2.", rootdir=tmpdir, keep=2) + assert numdir.check() + assert numdir.basename == "base2.%d" % i + for j in range(i): + assert numdir.new(ext=str(j)).check() + + def test_error_preservation(self, path1): + pytest.raises(EnvironmentError, path1.join("qwoeqiwe").mtime) + pytest.raises(EnvironmentError, path1.join("qwoeqiwe").read) + + # def test_parentdirmatch(self): + # local.parentdirmatch('std', startmodule=__name__) + # + + +class TestImport: + @pytest.fixture(autouse=True) + def preserve_sys(self): + with mock.patch.dict(sys.modules): + with mock.patch.object(sys, "path", list(sys.path)): + yield + + def test_pyimport(self, path1): + obj = path1.join("execfile.py").pyimport() + assert obj.x == 42 + assert obj.__name__ == "execfile" + + def test_pyimport_renamed_dir_creates_mismatch(self, tmpdir, monkeypatch): + p = tmpdir.ensure("a", "test_x123.py") + p.pyimport() + tmpdir.join("a").move(tmpdir.join("b")) + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + + # Errors can be ignored. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "1") + tmpdir.join("b", "test_x123.py").pyimport() + + # PY_IGNORE_IMPORTMISMATCH=0 does not ignore error. + monkeypatch.setenv("PY_IGNORE_IMPORTMISMATCH", "0") + with pytest.raises(tmpdir.ImportMismatchError): + tmpdir.join("b", "test_x123.py").pyimport() + + def test_pyimport_messy_name(self, tmpdir): + # http://bitbucket.org/hpk42/py-trunk/issue/129 + path = tmpdir.ensure("foo__init__.py") + path.pyimport() + + def test_pyimport_dir(self, tmpdir): + p = tmpdir.join("hello_123") + p_init = p.ensure("__init__.py") + m = p.pyimport() + assert m.__name__ == "hello_123" + m = p_init.pyimport() + assert m.__name__ == "hello_123" + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join("execfile.py").pyimport(modname="0x.y.z") + assert obj.x == 42 + assert obj.__name__ == "0x.y.z" + + def test_pyimport_a(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("a.py").pyimport() + assert mod.result == "got it" + assert mod.__name__ == "otherdir.a" + + def test_pyimport_b(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("b.py").pyimport() + assert mod.stuff == "got it" + assert mod.__name__ == "otherdir.b" + + def test_pyimport_c(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("c.py").pyimport() + assert mod.value == "got it" + + def test_pyimport_d(self, path1): + otherdir = path1.join("otherdir") + mod = otherdir.join("d.py").pyimport() + assert mod.value2 == "got it" + + def test_pyimport_and_import(self, tmpdir): + tmpdir.ensure("xxxpackage", "__init__.py") + mod1path = tmpdir.ensure("xxxpackage", "module1.py") + mod1 = mod1path.pyimport() + assert mod1.__name__ == "xxxpackage.module1" + from xxxpackage import module1 + + assert module1 is mod1 + + def test_pyimport_check_filepath_consistency(self, monkeypatch, tmpdir): + name = "pointsback123" + ModuleType = type(os) + p = tmpdir.ensure(name + ".py") + for ending in (".pyc", "$py.class", ".pyo"): + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + ending) + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + newmod = p.pyimport() + assert mod == newmod + monkeypatch.undo() + mod = ModuleType(name) + pseudopath = tmpdir.ensure(name + "123.py") + mod.__file__ = str(pseudopath) + monkeypatch.setitem(sys.modules, name, mod) + excinfo = pytest.raises(pseudopath.ImportMismatchError, p.pyimport) + modname, modfile, orig = excinfo.value.args + assert modname == name + assert modfile == pseudopath + assert orig == p + assert issubclass(pseudopath.ImportMismatchError, ImportError) + + def test_issue131_pyimport_on__init__(self, tmpdir): + # __init__.py files may be namespace packages, and thus the + # __file__ of an imported module may not be ourselves + # see issue + p1 = tmpdir.ensure("proja", "__init__.py") + p2 = tmpdir.ensure("sub", "proja", "__init__.py") + m1 = p1.pyimport() + m2 = p2.pyimport() + assert m1 == m2 + + def test_ensuresyspath_append(self, tmpdir): + root1 = tmpdir.mkdir("root1") + file1 = root1.ensure("x123.py") + assert str(root1) not in sys.path + file1.pyimport(ensuresyspath="append") + assert str(root1) == sys.path[-1] + assert str(root1) not in sys.path[:-1] + + +class TestImportlibImport: + OPTS = {"ensuresyspath": "importlib"} + + def test_pyimport(self, path1): + obj = path1.join("execfile.py").pyimport(**self.OPTS) + assert obj.x == 42 + assert obj.__name__ == "execfile" + + def test_pyimport_dir_fails(self, tmpdir): + p = tmpdir.join("hello_123") + p.ensure("__init__.py") + with pytest.raises(ImportError): + p.pyimport(**self.OPTS) + + def test_pyimport_execfile_different_name(self, path1): + obj = path1.join("execfile.py").pyimport(modname="0x.y.z", **self.OPTS) + assert obj.x == 42 + assert obj.__name__ == "0x.y.z" + + def test_pyimport_relative_import_fails(self, path1): + otherdir = path1.join("otherdir") + with pytest.raises(ImportError): + otherdir.join("a.py").pyimport(**self.OPTS) + + def test_pyimport_doesnt_use_sys_modules(self, tmpdir): + p = tmpdir.ensure("file738jsk.py") + mod = p.pyimport(**self.OPTS) + assert mod.__name__ == "file738jsk" + assert "file738jsk" not in sys.modules + + +def test_pypkgdir(tmpdir): + pkg = tmpdir.ensure("pkg1", dir=1) + pkg.ensure("__init__.py") + pkg.ensure("subdir/__init__.py") + assert pkg.pypkgpath() == pkg + assert pkg.join("subdir", "__init__.py").pypkgpath() == pkg + + +def test_pypkgdir_unimportable(tmpdir): + pkg = tmpdir.ensure("pkg1-1", dir=1) # unimportable + pkg.ensure("__init__.py") + subdir = pkg.ensure("subdir/__init__.py").dirpath() + assert subdir.pypkgpath() == subdir + assert subdir.ensure("xyz.py").pypkgpath() == subdir + assert not pkg.pypkgpath() + + +def test_isimportable(): + try: + from py.path import isimportable # py vendored version + except ImportError: + from py._path.local import isimportable # py library + + assert not isimportable("") + assert isimportable("x") + assert isimportable("x1") + assert isimportable("x_1") + assert isimportable("_") + assert isimportable("_1") + assert not isimportable("x-1") + assert not isimportable("x:1") + + +def test_homedir_from_HOME(monkeypatch): + path = os.getcwd() + monkeypatch.setenv("HOME", path) + assert local._gethomedir() == local(path) + + +def test_homedir_not_exists(monkeypatch): + monkeypatch.delenv("HOME", raising=False) + monkeypatch.delenv("HOMEDRIVE", raising=False) + homedir = local._gethomedir() + assert homedir is None + + +def test_samefile(tmpdir): + assert tmpdir.samefile(tmpdir) + p = tmpdir.ensure("hello") + assert p.samefile(p) + with p.dirpath().as_cwd(): + assert p.samefile(p.basename) + if sys.platform == "win32": + p1 = p.__class__(str(p).lower()) + p2 = p.__class__(str(p).upper()) + assert p1.samefile(p2) + + +@pytest.mark.skipif(not hasattr(os, "symlink"), reason="os.symlink not available") +def test_samefile_symlink(tmpdir): + p1 = tmpdir.ensure("foo.txt") + p2 = tmpdir.join("linked.txt") + try: + os.symlink(str(p1), str(p2)) + except (OSError, NotImplementedError) as e: + # on Windows this might fail if the user doesn't have special symlink permissions + # pypy3 on Windows doesn't implement os.symlink and raises NotImplementedError + pytest.skip(str(e.args[0])) + + assert p1.samefile(p2) + + +def test_listdir_single_arg(tmpdir): + tmpdir.ensure("hello") + assert tmpdir.listdir("hello")[0].basename == "hello" + + +def test_mkdtemp_rootdir(tmpdir): + dtmp = local.mkdtemp(rootdir=tmpdir) + assert tmpdir.listdir() == [dtmp] + + +class TestWINLocalPath: + pytestmark = win32only + + def test_owner_group_not_implemented(self, path1): + with pytest.raises(NotImplementedError): + path1.stat().owner + with pytest.raises(NotImplementedError): + path1.stat().group + + def test_chmod_simple_int(self, path1): + mode = path1.stat().mode + # Ensure that we actually change the mode to something different. + path1.chmod(mode == 0 and 1 or 0) + try: + print(path1.stat().mode) + print(mode) + assert path1.stat().mode != mode + finally: + path1.chmod(mode) + assert path1.stat().mode == mode + + def test_path_comparison_lowercase_mixed(self, path1): + t1 = path1.join("a_path") + t2 = path1.join("A_path") + assert t1 == t1 + assert t1 == t2 + + def test_relto_with_mixed_case(self, path1): + t1 = path1.join("a_path", "fiLe") + t2 = path1.join("A_path") + assert t1.relto(t2) == "fiLe" + + def test_allow_unix_style_paths(self, path1): + t1 = path1.join("a_path") + assert t1 == str(path1) + "\\a_path" + t1 = path1.join("a_path/") + assert t1 == str(path1) + "\\a_path" + t1 = path1.join("dir/a_path") + assert t1 == str(path1) + "\\dir\\a_path" + + def test_sysfind_in_currentdir(self, path1): + cmd = local.sysfind("cmd") + root = cmd.new(dirname="", basename="") # c:\ in most installations + with root.as_cwd(): + x = local.sysfind(cmd.relto(root)) + assert x.check(file=1) + + def test_fnmatch_file_abspath_posix_pattern_on_win32(self, tmpdir): + # path-matching patterns might contain a posix path separator '/' + # Test that we can match that pattern on windows. + import posixpath + + b = tmpdir.join("a", "b") + assert b.fnmatch(posixpath.sep.join("ab")) + pattern = posixpath.sep.join([str(tmpdir), "*", "b"]) + assert b.fnmatch(pattern) + + +class TestPOSIXLocalPath: + pytestmark = skiponwin32 + + def test_hardlink(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("Hello", encoding="utf-8") + nlink = filepath.stat().nlink + linkpath.mklinkto(filepath) + assert filepath.stat().nlink == nlink + 1 + + def test_symlink_are_identical(self, tmpdir): + filepath = tmpdir.join("file") + filepath.write_text("Hello", encoding="utf-8") + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(filepath) + assert linkpath.readlink() == str(filepath) + + def test_symlink_isfile(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("", encoding="utf-8") + linkpath.mksymlinkto(filepath) + assert linkpath.check(file=1) + assert not linkpath.check(link=0, file=1) + assert linkpath.islink() + + def test_symlink_relative(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("Hello", encoding="utf-8") + linkpath.mksymlinkto(filepath, absolute=False) + assert linkpath.readlink() == "file" + assert filepath.read_text(encoding="utf-8") == linkpath.read_text( + encoding="utf-8" + ) + + def test_symlink_not_existing(self, tmpdir): + linkpath = tmpdir.join("testnotexisting") + assert not linkpath.check(link=1) + assert linkpath.check(link=0) + + def test_relto_with_root(self, path1, tmpdir): + y = path1.join("x").relto(local("/")) + assert y[0] == str(path1)[1] + + def test_visit_recursive_symlink(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(tmpdir) + visitor = tmpdir.visit(None, lambda x: x.check(link=0)) + assert list(visitor) == [linkpath] + + def test_symlink_isdir(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(tmpdir) + assert linkpath.check(dir=1) + assert not linkpath.check(link=0, dir=1) + + def test_symlink_remove(self, tmpdir): + linkpath = tmpdir.join("test") + linkpath.mksymlinkto(linkpath) # point to itself + assert linkpath.check(link=1) + linkpath.remove() + assert not linkpath.check() + + def test_realpath_file(self, tmpdir): + linkpath = tmpdir.join("test") + filepath = tmpdir.join("file") + filepath.write_text("", encoding="utf-8") + linkpath.mksymlinkto(filepath) + realpath = linkpath.realpath() + assert realpath.basename == "file" + + def test_owner(self, path1, tmpdir): + from pwd import getpwuid # type:ignore[attr-defined] + from grp import getgrgid # type:ignore[attr-defined] + + stat = path1.stat() + assert stat.path == path1 + + uid = stat.uid + gid = stat.gid + owner = getpwuid(uid)[0] + group = getgrgid(gid)[0] + + assert uid == stat.uid + assert owner == stat.owner + assert gid == stat.gid + assert group == stat.group + + def test_stat_helpers(self, tmpdir, monkeypatch): + path1 = tmpdir.ensure("file") + stat1 = path1.stat() + stat2 = tmpdir.stat() + assert stat1.isfile() + assert stat2.isdir() + assert not stat1.islink() + assert not stat2.islink() + + def test_stat_non_raising(self, tmpdir): + path1 = tmpdir.join("file") + pytest.raises(error.ENOENT, lambda: path1.stat()) + res = path1.stat(raising=False) + assert res is None + + def test_atime(self, tmpdir): + import time + + path = tmpdir.ensure("samplefile") + now = time.time() + atime1 = path.atime() + # we could wait here but timer resolution is very + # system dependent + path.read_binary() + time.sleep(ATIME_RESOLUTION) + atime2 = path.atime() + time.sleep(ATIME_RESOLUTION) + duration = time.time() - now + assert (atime2 - atime1) <= duration + + def test_commondir(self, path1): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = path1.join("something") + p2 = path1.join("otherthing") + assert p1.common(p2) == path1 + assert p2.common(p1) == path1 + + def test_commondir_nocommon(self, path1): + # XXX This is here in local until we find a way to implement this + # using the subversion command line api. + p1 = path1.join("something") + p2 = local(path1.sep + "blabla") + assert p1.common(p2) == "/" + + def test_join_to_root(self, path1): + root = path1.parts()[0] + assert len(str(root)) == 1 + assert str(root.join("a")) == "/a" + + def test_join_root_to_root_with_no_abs(self, path1): + nroot = path1.join("/") + assert str(path1) == str(nroot) + assert path1 == nroot + + def test_chmod_simple_int(self, path1): + mode = path1.stat().mode + path1.chmod(int(mode / 2)) + try: + assert path1.stat().mode != mode + finally: + path1.chmod(mode) + assert path1.stat().mode == mode + + def test_chmod_rec_int(self, path1): + # XXX fragile test + def recfilter(x): + return x.check(dotfile=0, link=0) + + oldmodes = {} + for x in path1.visit(rec=recfilter): + oldmodes[x] = x.stat().mode + path1.chmod(int("772", 8), rec=recfilter) + try: + for x in path1.visit(rec=recfilter): + assert x.stat().mode & int("777", 8) == int("772", 8) + finally: + for x, y in oldmodes.items(): + x.chmod(y) + + def test_copy_archiving(self, tmpdir): + unicode_fn = "something-\342\200\223.txt" + f = tmpdir.ensure("a", unicode_fn) + a = f.dirpath() + oldmode = f.stat().mode + newmode = oldmode ^ 1 + f.chmod(newmode) + b = tmpdir.join("b") + a.copy(b, mode=True) + assert b.join(f.basename).stat().mode == newmode + + def test_copy_stat_file(self, tmpdir): + src = tmpdir.ensure("src") + dst = tmpdir.join("dst") + # a small delay before the copy + time.sleep(ATIME_RESOLUTION) + src.copy(dst, stat=True) + oldstat = src.stat() + newstat = dst.stat() + assert oldstat.mode == newstat.mode + assert (dst.atime() - src.atime()) < ATIME_RESOLUTION + assert (dst.mtime() - src.mtime()) < ATIME_RESOLUTION + + def test_copy_stat_dir(self, tmpdir): + test_files = ["a", "b", "c"] + src = tmpdir.join("src") + for f in test_files: + src.join(f).write_text(f, ensure=True, encoding="utf-8") + dst = tmpdir.join("dst") + # a small delay before the copy + time.sleep(ATIME_RESOLUTION) + src.copy(dst, stat=True) + for f in test_files: + oldstat = src.join(f).stat() + newstat = dst.join(f).stat() + assert (newstat.atime - oldstat.atime) < ATIME_RESOLUTION + assert (newstat.mtime - oldstat.mtime) < ATIME_RESOLUTION + assert oldstat.mode == newstat.mode + + def test_chown_identity(self, path1): + owner = path1.stat().owner + group = path1.stat().group + path1.chown(owner, group) + + def test_chown_dangling_link(self, path1): + owner = path1.stat().owner + group = path1.stat().group + x = path1.join("hello") + x.mksymlinkto("qlwkejqwlek") + try: + path1.chown(owner, group, rec=1) + finally: + x.remove(rec=0) + + def test_chown_identity_rec_mayfail(self, path1): + owner = path1.stat().owner + group = path1.stat().group + path1.chown(owner, group) + + +class TestUnicodePy2Py3: + def test_join_ensure(self, tmpdir, monkeypatch): + if sys.version_info >= (3, 0) and "LANG" not in os.environ: + pytest.skip("cannot run test without locale") + x = local(tmpdir.strpath) + part = "hällo" + y = x.ensure(part) + assert x.join(part) == y + + def test_listdir(self, tmpdir): + if sys.version_info >= (3, 0) and "LANG" not in os.environ: + pytest.skip("cannot run test without locale") + x = local(tmpdir.strpath) + part = "hällo" + y = x.ensure(part) + assert x.listdir(part)[0] == y + + @pytest.mark.xfail(reason="changing read/write might break existing usages") + def test_read_write(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + with ignore_encoding_warning(): + x.write(part) + assert x.read() == part + x.write(part.encode(sys.getdefaultencoding())) + assert x.read() == part.encode(sys.getdefaultencoding()) + + +class TestBinaryAndTextMethods: + def test_read_binwrite(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + part_utf8 = part.encode("utf8") + x.write_binary(part_utf8) + assert x.read_binary() == part_utf8 + s = x.read_text(encoding="utf8") + assert s == part + assert isinstance(s, str) + + def test_read_textwrite(self, tmpdir): + x = tmpdir.join("hello") + part = "hällo" + part_utf8 = part.encode("utf8") + x.write_text(part, encoding="utf8") + assert x.read_binary() == part_utf8 + assert x.read_text(encoding="utf8") == part + + def test_default_encoding(self, tmpdir): + x = tmpdir.join("hello") + # Can't use UTF8 as the default encoding (ASCII) doesn't support it + part = "hello" + x.write_text(part, "ascii") + s = x.read_text("ascii") + assert s == part + assert type(s) == type(part) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index c7139b538..de9e92d00 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,9 +1,8 @@ +import dataclasses import os import sys import types -import attr - import pytest from _pytest.compat import importlib_metadata from _pytest.config import ExitCode @@ -115,11 +114,11 @@ class TestGeneralUsage: loaded = [] - @attr.s + @dataclasses.dataclass class DummyEntryPoint: - name = attr.ib() - module = attr.ib() - group = "pytest11" + name: str + module: str + group: str = "pytest11" def load(self): __import__(self.module) @@ -132,10 +131,10 @@ class TestGeneralUsage: DummyEntryPoint("mycov", "mycov_module"), ] - @attr.s + @dataclasses.dataclass class DummyDist: - entry_points = attr.ib() - files = () + entry_points: object + files: object = () def my_dists(): return (DummyDist(entry_points),) @@ -268,7 +267,7 @@ class TestGeneralUsage: def test_issue109_sibling_conftests_not_loaded(self, pytester: Pytester) -> None: sub1 = pytester.mkdir("sub1") sub2 = pytester.mkdir("sub2") - sub1.joinpath("conftest.py").write_text("assert 0") + sub1.joinpath("conftest.py").write_text("assert 0", encoding="utf-8") result = pytester.runpytest(sub2) assert result.ret == ExitCode.NO_TESTS_COLLECTED sub2.joinpath("__init__.py").touch() @@ -468,7 +467,7 @@ class TestGeneralUsage: assert "invalid" in str(excinfo.value) p = pytester.path.joinpath("test_test_plugins_given_as_strings.py") - p.write_text("def test_foo(): pass") + p.write_text("def test_foo(): pass", encoding="utf-8") mod = types.ModuleType("myplugin") monkeypatch.setitem(sys.modules, "myplugin", mod) assert pytest.main(args=[str(pytester.path)], plugins=["myplugin"]) == 0 @@ -588,7 +587,7 @@ class TestInvocationVariants: def test_pyargs_importerror(self, pytester: Pytester, monkeypatch) -> None: monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False) path = pytester.mkpydir("tpkg") - path.joinpath("test_hello.py").write_text("raise ImportError") + path.joinpath("test_hello.py").write_text("raise ImportError", encoding="utf-8") result = pytester.runpytest("--pyargs", "tpkg.test_hello", syspathinsert=True) assert result.ret != 0 @@ -598,10 +597,10 @@ class TestInvocationVariants: def test_pyargs_only_imported_once(self, pytester: Pytester) -> None: pkg = pytester.mkpydir("foo") pkg.joinpath("test_foo.py").write_text( - "print('hello from test_foo')\ndef test(): pass" + "print('hello from test_foo')\ndef test(): pass", encoding="utf-8" ) pkg.joinpath("conftest.py").write_text( - "def pytest_configure(config): print('configuring')" + "def pytest_configure(config): print('configuring')", encoding="utf-8" ) result = pytester.runpytest( @@ -614,7 +613,7 @@ class TestInvocationVariants: def test_pyargs_filename_looks_like_module(self, pytester: Pytester) -> None: pytester.path.joinpath("conftest.py").touch() - pytester.path.joinpath("t.py").write_text("def test(): pass") + pytester.path.joinpath("t.py").write_text("def test(): pass", encoding="utf-8") result = pytester.runpytest("--pyargs", "t.py") assert result.ret == ExitCode.OK @@ -623,8 +622,12 @@ class TestInvocationVariants: monkeypatch.delenv("PYTHONDONTWRITEBYTECODE", False) path = pytester.mkpydir("tpkg") - path.joinpath("test_hello.py").write_text("def test_hello(): pass") - path.joinpath("test_world.py").write_text("def test_world(): pass") + path.joinpath("test_hello.py").write_text( + "def test_hello(): pass", encoding="utf-8" + ) + path.joinpath("test_world.py").write_text( + "def test_world(): pass", encoding="utf-8" + ) result = pytester.runpytest("--pyargs", "tpkg") assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) @@ -663,13 +666,15 @@ class TestInvocationVariants: ns = d.joinpath("ns_pkg") ns.mkdir() ns.joinpath("__init__.py").write_text( - "__import__('pkg_resources').declare_namespace(__name__)" + "__import__('pkg_resources').declare_namespace(__name__)", + encoding="utf-8", ) lib = ns.joinpath(dirname) lib.mkdir() lib.joinpath("__init__.py").touch() lib.joinpath(f"test_{dirname}.py").write_text( - f"def test_{dirname}(): pass\ndef test_other():pass" + f"def test_{dirname}(): pass\ndef test_other():pass", + encoding="utf-8", ) # The structure of the test directory is now: @@ -694,7 +699,18 @@ class TestInvocationVariants: # mixed module and filenames: monkeypatch.chdir("world") - result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world") + + # pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages. + # pgk_resources has been deprecated entirely. + # 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", + r"-Wignore:pkg_resources is deprecated", + ) + result = pytester.runpytest( + "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", *ignore_w + ) assert result.ret == 0 result.stdout.fnmatch_lines( [ @@ -744,10 +760,10 @@ class TestInvocationVariants: lib.mkdir() lib.joinpath("__init__.py").touch() lib.joinpath("test_bar.py").write_text( - "def test_bar(): pass\ndef test_other(a_fixture):pass" + "def test_bar(): pass\ndef test_other(a_fixture):pass", encoding="utf-8" ) lib.joinpath("conftest.py").write_text( - "import pytest\n@pytest.fixture\ndef a_fixture():pass" + "import pytest\n@pytest.fixture\ndef a_fixture():pass", encoding="utf-8" ) d_local = pytester.mkdir("symlink_root") @@ -872,7 +888,6 @@ class TestDurations: ) def test_calls_show_2(self, pytester: Pytester, mock_timing) -> None: - pytester.makepyfile(self.source) result = pytester.runpytest_inprocess("--durations=2") assert result.ret == 0 @@ -1037,14 +1052,14 @@ def test_fixture_values_leak(pytester: Pytester) -> None: """ pytester.makepyfile( """ - import attr + import dataclasses import gc import pytest import weakref - @attr.s - class SomeObj(object): - name = attr.ib() + @dataclasses.dataclass + class SomeObj: + name: str fix_of_test1_ref = None session_ref = None @@ -1149,7 +1164,6 @@ def test_usage_error_code(pytester: Pytester) -> None: assert result.ret == ExitCode.USAGE_ERROR -@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning") def test_warn_on_async_function(pytester: Pytester) -> None: # In the below we .close() the coroutine only to avoid # "RuntimeWarning: coroutine 'test_2' was never awaited" @@ -1166,7 +1180,7 @@ def test_warn_on_async_function(pytester: Pytester) -> None: return coro """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wdefault") result.stdout.fnmatch_lines( [ "test_async.py::test_1", @@ -1182,7 +1196,6 @@ def test_warn_on_async_function(pytester: Pytester) -> None: ) -@pytest.mark.filterwarnings("default::pytest.PytestUnhandledCoroutineWarning") def test_warn_on_async_gen_function(pytester: Pytester) -> None: pytester.makepyfile( test_async=""" @@ -1194,7 +1207,7 @@ def test_warn_on_async_gen_function(pytester: Pytester) -> None: return test_2() """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wdefault") result.stdout.fnmatch_lines( [ "test_async.py::test_1", @@ -1267,8 +1280,7 @@ def test_tee_stdio_captures_and_live_prints(pytester: Pytester) -> None: result.stderr.fnmatch_lines(["*@this is stderr@*"]) # now ensure the output is in the junitxml - with open(pytester.path.joinpath("output.xml")) as f: - fullXml = f.read() + fullXml = pytester.path.joinpath("output.xml").read_text(encoding="utf-8") assert "@this is stdout@\n" in fullXml assert "@this is stderr@\n" in fullXml @@ -1294,12 +1306,12 @@ def test_no_brokenpipeerror_message(pytester: Pytester) -> None: popen.stderr.close() -def test_function_return_non_none_warning(testdir) -> None: - testdir.makepyfile( +def test_function_return_non_none_warning(pytester: Pytester) -> None: + pytester.makepyfile( """ def test_stuff(): return "something" """ ) - res = testdir.runpytest() + res = pytester.runpytest() res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"]) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index e428b9c5c..e5c030c4d 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -11,7 +11,7 @@ from typing import Tuple from typing import TYPE_CHECKING from typing import Union -import _pytest +import _pytest._code import pytest from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -53,6 +53,20 @@ def test_excinfo_from_exc_info_simple() -> None: assert info.type == ValueError +def test_excinfo_from_exception_simple() -> None: + try: + raise ValueError + except ValueError as e: + assert e.__traceback__ is not None + info = _pytest._code.ExceptionInfo.from_exception(e) + assert info.type == ValueError + + +def test_excinfo_from_exception_missing_traceback_assertion() -> None: + with pytest.raises(AssertionError, match=r"must have.*__traceback__"): + _pytest._code.ExceptionInfo.from_exception(ValueError()) + + def test_excinfo_getstatement(): def g(): raise ValueError @@ -172,7 +186,7 @@ class TestTraceback_f_g_h: def test_traceback_filter(self): traceback = self.excinfo.traceback - ntraceback = traceback.filter() + ntraceback = traceback.filter(self.excinfo) assert len(ntraceback) == len(traceback) - 1 @pytest.mark.parametrize( @@ -203,7 +217,7 @@ class TestTraceback_f_g_h: excinfo = pytest.raises(ValueError, h) traceback = excinfo.traceback - ntraceback = traceback.filter() + ntraceback = traceback.filter(excinfo) print(f"old: {traceback!r}") print(f"new: {ntraceback!r}") @@ -276,7 +290,7 @@ class TestTraceback_f_g_h: excinfo = pytest.raises(ValueError, fail) assert excinfo.traceback.recursionindex() is None - def test_traceback_getcrashentry(self): + def test_getreprcrash(self): def i(): __tracebackhide__ = True raise ValueError @@ -292,14 +306,13 @@ class TestTraceback_f_g_h: g() excinfo = pytest.raises(ValueError, f) - tb = excinfo.traceback - entry = tb.getcrashentry() + reprcrash = excinfo._getreprcrash() + assert reprcrash is not None co = _pytest._code.Code.from_function(h) - assert entry.frame.code.path == co.path - assert entry.lineno == co.firstlineno + 1 - assert entry.frame.code.name == "h" + assert reprcrash.path == str(co.path) + assert reprcrash.lineno == co.firstlineno + 1 + 1 - def test_traceback_getcrashentry_empty(self): + def test_getreprcrash_empty(self): def g(): __tracebackhide__ = True raise ValueError @@ -309,12 +322,7 @@ class TestTraceback_f_g_h: g() excinfo = pytest.raises(ValueError, f) - tb = excinfo.traceback - entry = tb.getcrashentry() - co = _pytest._code.Code.from_function(g) - assert entry.frame.code.path == co.path - assert entry.lineno == co.firstlineno + 2 - assert entry.frame.code.name == "g" + assert excinfo._getreprcrash() is None def test_excinfo_exconly(): @@ -366,7 +374,7 @@ def test_excinfo_no_sourcecode(): def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None: # XXX: simplified locally testable version - tmp_path.joinpath("test.txt").write_text("{{ h()}}:") + tmp_path.joinpath("test.txt").write_text("{{ h()}}:", encoding="utf-8") jinja2 = pytest.importorskip("jinja2") loader = jinja2.FileSystemLoader(str(tmp_path)) @@ -443,7 +451,7 @@ class TestFormattedExcinfo: source = textwrap.dedent(source) modpath = tmp_path.joinpath("mod.py") tmp_path.joinpath("__init__.py").touch() - modpath.write_text(source) + modpath.write_text(source, encoding="utf-8") importlib.invalidate_caches() return import_path(modpath, root=tmp_path) @@ -463,6 +471,24 @@ class TestFormattedExcinfo: assert lines[0] == "| def f(x):" assert lines[1] == " pass" + def test_repr_source_out_of_bounds(self): + pr = FormattedExcinfo() + source = _pytest._code.Source( + """\ + def f(x): + pass + """ + ).strip() + pr.flow_marker = "|" # type: ignore[misc] + + lines = pr.get_source(source, 100) + assert len(lines) == 1 + assert lines[0] == "| ???" + + lines = pr.get_source(source, -100) + assert len(lines) == 1 + assert lines[0] == "| ???" + def test_repr_source_excinfo(self) -> None: """Check if indentation is right.""" try: @@ -598,7 +624,7 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.func1) - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) p = FormattedExcinfo() reprtb = p.repr_traceback_entry(excinfo.traceback[-1]) @@ -631,7 +657,7 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120) - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) @@ -658,7 +684,7 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d") - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) entry = excinfo.traceback[-1] p = FormattedExcinfo(funcargs=True) reprfuncargs = p.repr_args(entry) @@ -932,7 +958,7 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.f) - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) repr = excinfo.getrepr() repr.toterminal(tw_mock) assert tw_mock.lines[0] == "" @@ -966,7 +992,7 @@ raise ValueError() ) excinfo = pytest.raises(ValueError, mod.f) tmp_path.joinpath("mod.py").unlink() - excinfo.traceback = excinfo.traceback.filter() + excinfo.traceback = excinfo.traceback.filter(excinfo) repr = excinfo.getrepr() repr.toterminal(tw_mock) assert tw_mock.lines[0] == "" @@ -997,8 +1023,8 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.f) - tmp_path.joinpath("mod.py").write_text("asdf") - excinfo.traceback = excinfo.traceback.filter() + tmp_path.joinpath("mod.py").write_text("asdf", encoding="utf-8") + excinfo.traceback = excinfo.traceback.filter(excinfo) repr = excinfo.getrepr() repr.toterminal(tw_mock) assert tw_mock.lines[0] == "" @@ -1095,9 +1121,11 @@ raise ValueError() """ ) excinfo = pytest.raises(ValueError, mod.f) - excinfo.traceback = excinfo.traceback.filter() - excinfo.traceback[1].set_repr_style("short") - excinfo.traceback[2].set_repr_style("short") + excinfo.traceback = excinfo.traceback.filter(excinfo) + excinfo.traceback = _pytest._code.Traceback( + entry if i not in (1, 2) else entry.with_repr_style("short") + for i, entry in enumerate(excinfo.traceback) + ) r = excinfo.getrepr(style="long") r.toterminal(tw_mock) for line in tw_mock.lines: @@ -1363,7 +1391,7 @@ raise ValueError() with pytest.raises(TypeError) as excinfo: mod.f() # previously crashed with `AttributeError: list has no attribute get` - excinfo.traceback.filter() + excinfo.traceback.filter(excinfo) @pytest.mark.parametrize("style", ["short", "long"]) @@ -1557,3 +1585,66 @@ def test_exceptiongroup(pytester: Pytester, outer_chain, inner_chain) -> None: # with py>=3.11 does not depend on exceptiongroup, though there is a toxenv for it pytest.importorskip("exceptiongroup") _exceptiongroup_common(pytester, outer_chain, inner_chain, native=False) + + +@pytest.mark.parametrize("tbstyle", ("long", "short", "auto", "line", "native")) +def test_all_entries_hidden(pytester: Pytester, tbstyle: str) -> None: + """Regression test for #10903.""" + pytester.makepyfile( + """ + def test(): + __tracebackhide__ = True + 1 / 0 + """ + ) + result = pytester.runpytest("--tb", tbstyle) + assert result.ret == 1 + if tbstyle != "line": + result.stdout.fnmatch_lines(["*ZeroDivisionError: division by zero"]) + if tbstyle not in ("line", "native"): + result.stdout.fnmatch_lines(["All traceback entries are hidden.*"]) + + +def test_hidden_entries_of_chained_exceptions_are_not_shown(pytester: Pytester) -> None: + """Hidden entries of chained exceptions are not shown (#1904).""" + p = pytester.makepyfile( + """ + def g1(): + __tracebackhide__ = True + str.does_not_exist + + def f3(): + __tracebackhide__ = True + 1 / 0 + + def f2(): + try: + f3() + except Exception: + g1() + + def f1(): + __tracebackhide__ = True + f2() + + def test(): + f1() + """ + ) + result = pytester.runpytest(str(p), "--tb=short") + assert result.ret == 1 + result.stdout.fnmatch_lines( + [ + "*.py:11: in f2", + " f3()", + "E ZeroDivisionError: division by zero", + "", + "During handling of the above exception, another exception occurred:", + "*.py:20: in test", + " f1()", + "*.py:13: in f2", + " g1()", + "E AttributeError:*'does_not_exist'", + ], + consecutive=True, + ) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 52417f2f8..dc35c9496 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -294,7 +294,7 @@ def test_source_of_class_at_eof_without_newline(_sys_snapshot, tmp_path: Path) - """ ) path = tmp_path.joinpath("a.py") - path.write_text(str(source)) + path.write_text(str(source), encoding="utf-8") mod: Any = import_path(path, root=tmp_path) s2 = Source(mod.A) assert str(source).strip() == str(s2).strip() diff --git a/testing/conftest.py b/testing/conftest.py index 107aad86b..8e77fcae5 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,3 +1,4 @@ +import dataclasses import re import sys from typing import List @@ -104,7 +105,7 @@ def tw_mock(): @pytest.fixture -def dummy_yaml_custom_test(pytester: Pytester): +def dummy_yaml_custom_test(pytester: Pytester) -> None: """Writes a conftest file that collects and executes a dummy yaml test. Taken from the docs, but stripped down to the bare minimum, useful for @@ -157,6 +158,7 @@ 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()} @@ -191,20 +193,18 @@ def mock_timing(monkeypatch: MonkeyPatch): Time is static, and only advances through `sleep` calls, thus tests might sleep over large numbers and obtain accurate time() calls at the end, making tests reliable and instant. """ - import attr - @attr.s + @dataclasses.dataclass class MockTiming: + _current_time: float = 1590150050.0 - _current_time = attr.ib(default=1590150050.0) - - def sleep(self, seconds): + def sleep(self, seconds: float) -> None: self._current_time += seconds - def time(self): + def time(self) -> float: return self._current_time - def patch(self): + def patch(self) -> None: from _pytest import timing monkeypatch.setattr(timing, "sleep", self.sleep) diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index c4868a8a0..cf067bac9 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -103,7 +103,7 @@ def test_strict_option_is_deprecated(pytester: Pytester) -> None: def test_foo(): pass """ ) - result = pytester.runpytest("--strict") + result = pytester.runpytest("--strict", "-Wdefault::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines( [ "'unknown' not found in `markers` configuration option", @@ -189,7 +189,7 @@ class TestSkipMsgArgumentDeprecated: pytest.skip(msg="skippedmsg") """ ) - result = pytester.runpytest(p) + result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines( [ "*PytestRemovedIn8Warning: pytest.skip(msg=...) is now deprecated, " @@ -208,7 +208,7 @@ class TestSkipMsgArgumentDeprecated: pytest.fail(msg="failedmsg") """ ) - result = pytester.runpytest(p) + result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines( [ "*PytestRemovedIn8Warning: pytest.fail(msg=...) is now deprecated, " @@ -227,7 +227,7 @@ class TestSkipMsgArgumentDeprecated: pytest.exit(msg="exitmsg") """ ) - result = pytester.runpytest(p) + result = pytester.runpytest(p, "-Wdefault::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines( [ "*PytestRemovedIn8Warning: pytest.exit(msg=...) is now deprecated, " @@ -245,7 +245,7 @@ def test_deprecation_of_cmdline_preparse(pytester: Pytester) -> None: """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines( [ "*PytestRemovedIn8Warning: The pytest_cmdline_preparse hook is deprecated*", @@ -350,7 +350,7 @@ def test_nose_deprecated_with_setup(pytester: Pytester) -> None: ... """ ) - output = pytester.runpytest() + output = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning") message = [ "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", "*test_nose_deprecated_with_setup.py::test_omits_warnings is using nose method: `setup_fn_no_op` (setup)", @@ -378,7 +378,7 @@ def test_nose_deprecated_setup_teardown(pytester: Pytester) -> None: ... """ ) - output = pytester.runpytest() + output = pytester.runpytest("-Wdefault::pytest.PytestRemovedIn8Warning") message = [ "*PytestRemovedIn8Warning: Support for nose tests is deprecated and will be removed in a future release.", "*test_nose_deprecated_setup_teardown.py::Test::test is using nose-specific method: `setup(self)`", diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py index e69de29bb..9cd366295 100644 --- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py +++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/__init__.py @@ -0,0 +1,2 @@ +def test_init(): + pass diff --git a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py index f17482385..8f2d73cfa 100644 --- a/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py +++ b/testing/example_scripts/collect/package_init_given_as_arg/pkg/test_foo.py @@ -1,2 +1,2 @@ -def test(): +def test_foo(): pass diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index 6fe718b53..b5a04a99f 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -254,7 +254,7 @@ class TestTerminalWriterLineWidth: pytest.param( True, True, - "{kw}assert{hl-reset} {number}0{hl-reset}\n", + "{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", id="with markup and code_highlight", ), pytest.param( diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index e9e73d05f..8eaa2de96 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -1,3 +1,4 @@ +# mypy: disable-error-code="attr-defined" import logging import pytest @@ -8,6 +9,19 @@ logger = logging.getLogger(__name__) sublogger = logging.getLogger(__name__ + ".baz") +@pytest.fixture +def cleanup_disabled_logging(): + """Simple fixture that ensures that a test doesn't disable logging. + + This is necessary because ``logging.disable()`` is global, so a test disabling logging + and not cleaning up after will break every test that runs after it. + + This behavior was moved to a fixture so that logging will be un-disabled even if the test fails an assertion. + """ + yield + logging.disable(logging.NOTSET) + + def test_fixture_help(pytester: Pytester) -> None: result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines(["*caplog*"]) @@ -28,10 +42,27 @@ def test_change_level(caplog): assert "CRITICAL" in caplog.text +def test_change_level_logging_disabled(caplog, cleanup_disabled_logging): + logging.disable(logging.CRITICAL) + assert logging.root.manager.disable == logging.CRITICAL + caplog.set_level(logging.WARNING) + logger.info("handler INFO level") + logger.warning("handler WARNING level") + + caplog.set_level(logging.CRITICAL, logger=sublogger.name) + sublogger.warning("logger SUB_WARNING level") + sublogger.critical("logger SUB_CRITICAL level") + + assert "INFO" not in caplog.text + assert "WARNING" in caplog.text + assert "SUB_WARNING" not in caplog.text + assert "SUB_CRITICAL" in caplog.text + + def test_change_level_undo(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test. - Tests the logging output themselves (affacted both by logger and handler levels). + Tests the logging output themselves (affected both by logger and handler levels). """ pytester.makepyfile( """ @@ -54,6 +85,37 @@ def test_change_level_undo(pytester: Pytester) -> None: result.stdout.no_fnmatch_line("*log from test2*") +def test_change_disabled_level_undo( + pytester: Pytester, cleanup_disabled_logging +) -> None: + """Ensure that '_force_enable_logging' in 'set_level' is undone after the end of the test. + + Tests the logging output themselves (affected by disabled logging level). + """ + pytester.makepyfile( + """ + import logging + + def test1(caplog): + logging.disable(logging.CRITICAL) + caplog.set_level(logging.INFO) + # using + operator here so fnmatch_lines doesn't match the code in the traceback + logging.info('log from ' + 'test1') + assert 0 + + def test2(caplog): + # using + operator here so fnmatch_lines doesn't match the code in the traceback + # use logging.warning because we need a level that will show up if logging.disabled + # isn't reset to ``CRITICAL`` after test1. + logging.warning('log from ' + 'test2') + assert 0 + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines(["*log from test1*", "*2 failed in *"]) + result.stdout.no_fnmatch_line("*log from test2*") + + def test_change_level_undos_handler_level(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test (handler). @@ -97,6 +159,65 @@ def test_with_statement(caplog): assert "CRITICAL" in caplog.text +def test_with_statement_logging_disabled(caplog, cleanup_disabled_logging): + logging.disable(logging.CRITICAL) + assert logging.root.manager.disable == logging.CRITICAL + with caplog.at_level(logging.WARNING): + logger.debug("handler DEBUG level") + logger.info("handler INFO level") + logger.warning("handler WARNING level") + logger.error("handler ERROR level") + logger.critical("handler CRITICAL level") + + assert logging.root.manager.disable == logging.INFO + + with caplog.at_level(logging.CRITICAL, logger=sublogger.name): + sublogger.warning("logger SUB_WARNING level") + sublogger.critical("logger SUB_CRITICAL level") + + assert "DEBUG" not in caplog.text + assert "INFO" not in caplog.text + assert "WARNING" in caplog.text + assert "ERROR" in caplog.text + assert " CRITICAL" in caplog.text + assert "SUB_WARNING" not in caplog.text + assert "SUB_CRITICAL" in caplog.text + assert logging.root.manager.disable == logging.CRITICAL + + +@pytest.mark.parametrize( + "level_str,expected_disable_level", + [ + ("CRITICAL", logging.ERROR), + ("ERROR", logging.WARNING), + ("WARNING", logging.INFO), + ("INFO", logging.DEBUG), + ("DEBUG", logging.NOTSET), + ("NOTSET", logging.NOTSET), + ("NOTVALIDLEVEL", logging.NOTSET), + ], +) +def test_force_enable_logging_level_string( + caplog, cleanup_disabled_logging, level_str, expected_disable_level +): + """Test _force_enable_logging using a level string. + + ``expected_disable_level`` is one level below ``level_str`` because the disabled log level + always needs to be *at least* one level lower than the level that caplog is trying to capture. + """ + test_logger = logging.getLogger("test_str_level_force_enable") + # Emulate a testing environment where all logging is disabled. + logging.disable(logging.CRITICAL) + # Make sure all logging is disabled. + assert not test_logger.isEnabledFor(logging.CRITICAL) + # Un-disable logging for `level_str`. + caplog._force_enable_logging(level_str, test_logger) + # Make sure that the disabled level is now one below the requested logging level. + # We don't use `isEnabledFor` here because that also checks the level set by + # `logging.setLevel()` which is irrelevant to `logging.disable()`. + assert test_logger.manager.disable == expected_disable_level + + def test_log_access(caplog): caplog.set_level(logging.INFO) logger.info("boo %s", "arg") diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 323ff7b24..0c8e3fd08 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -81,7 +81,7 @@ def test_root_logger_affected(pytester: Pytester) -> None: # not the info one, because the default level of the root logger is # WARNING. assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "info text going to logger" not in contents assert "warning text going to logger" in contents @@ -656,7 +656,7 @@ def test_log_file_cli(pytester: Pytester) -> None: # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents @@ -687,7 +687,7 @@ def test_log_file_cli_level(pytester: Pytester) -> None: # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents @@ -738,7 +738,7 @@ def test_log_file_ini(pytester: Pytester) -> None: # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents @@ -777,7 +777,7 @@ def test_log_file_ini_level(pytester: Pytester) -> None: # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 assert os.path.isfile(log_file) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "This log message will be shown" in contents assert "This log message won't be shown" not in contents @@ -985,7 +985,7 @@ def test_log_in_hooks(pytester: Pytester) -> None: ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*sessionstart*", "*runtestloop*", "*sessionfinish*"]) - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert "sessionstart" in contents assert "runtestloop" in contents @@ -1021,7 +1021,7 @@ def test_log_in_runtest_logreport(pytester: Pytester) -> None: """ ) pytester.runpytest() - with open(log_file) as rfh: + with open(log_file, encoding="utf-8") as rfh: contents = rfh.read() assert contents.count("logreport") == 3 @@ -1065,11 +1065,11 @@ def test_log_set_path(pytester: Pytester) -> None: """ ) pytester.runpytest() - with open(os.path.join(report_dir_base, "test_first")) as rfh: + with open(os.path.join(report_dir_base, "test_first"), encoding="utf-8") as rfh: content = rfh.read() assert "message from test 1" in content - with open(os.path.join(report_dir_base, "test_second")) as rfh: + with open(os.path.join(report_dir_base, "test_second"), encoding="utf-8") as rfh: content = rfh.read() assert "message from test 2" in content @@ -1165,3 +1165,169 @@ 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(pytester: Pytester) -> None: + pytester.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 = pytester.runpytest("--log-disable=disabled", "-s") + assert result.ret == ExitCode.OK + assert not result.stderr.lines + + +def test_disable_loggers_does_not_propagate(pytester: Pytester) -> None: + pytester.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 = pytester.runpytest("--log-disable=parent.child", "-s") + assert result.ret == ExitCode.OK + assert not result.stderr.lines + + +def test_log_disabling_works_with_log_cli(pytester: Pytester) -> None: + pytester.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 = pytester.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 + + +def test_without_date_format_log(pytester: Pytester) -> None: + """Check that date is not printed by default.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines( + ["WARNING test_without_date_format_log:test_without_date_format_log.py:6 text"] + ) + + +def test_date_format_log(pytester: Pytester) -> None: + """Check that log_date_format affects output.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + pytester.makeini( + """ + [pytest] + log_format=%(asctime)s; %(levelname)s; %(message)s + log_date_format=%Y-%m-%d %H:%M:%S + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.re_match_lines([r"^[0-9-]{10} [0-9:]{8}; WARNING; text"]) + + +def test_date_format_percentf_log(pytester: Pytester) -> None: + """Make sure that microseconds are printed in log.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + pytester.makeini( + """ + [pytest] + log_format=%(asctime)s; %(levelname)s; %(message)s + log_date_format=%Y-%m-%d %H:%M:%S.%f + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.re_match_lines([r"^[0-9-]{10} [0-9:]{8}.[0-9]{6}; WARNING; text"]) + + +def test_date_format_percentf_tz_log(pytester: Pytester) -> None: + """Make sure that timezone and microseconds are properly formatted together.""" + pytester.makepyfile( + """ + import logging + + logger = logging.getLogger(__name__) + + def test_foo(): + logger.warning('text') + assert False + """ + ) + pytester.makeini( + """ + [pytest] + log_format=%(asctime)s; %(levelname)s; %(message)s + log_date_format=%Y-%m-%d %H:%M:%S.%f%z + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.re_match_lines( + [r"^[0-9-]{10} [0-9:]{8}.[0-9]{6}[+-][0-9\.]+; WARNING; text"] + ) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index e76231d26..d56720c02 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,15 +1,15 @@ -anyio[curio,trio]==3.6.1 -django==4.1.2 -pytest-asyncio==0.19.0 -pytest-bdd==6.0.1 -pytest-cov==4.0.0 +anyio[curio,trio]==3.7.0 +django==4.2.2 +pytest-asyncio==0.21.0 +pytest-bdd==6.1.1 +pytest-cov==4.1.0 pytest-django==4.5.2 pytest-flakes==4.0.5 -pytest-html==3.1.1 -pytest-mock==3.10.0 -pytest-rerunfailures==10.2 -pytest-sugar==0.9.5 +pytest-html==3.2.0 +pytest-mock==3.11.1 +pytest-rerunfailures==11.1.2 +pytest-sugar==0.9.7 pytest-trio==0.7.0 -pytest-twisted==1.13.4 +pytest-twisted==1.14.0 twisted==22.8.0 -pytest-xvfb==2.0.0 +pytest-xvfb==3.0.0 diff --git a/testing/python/approx.py b/testing/python/approx.py index 6acb466ff..631e52b56 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -630,6 +630,19 @@ 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") diff --git a/testing/python/collect.py b/testing/python/collect.py index ac3edd395..8de216d8f 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -60,7 +60,8 @@ class TestModule: """.format( str(root2) ) - ) + ), + encoding="utf-8", ) with monkeypatch.context() as mp: mp.chdir(root2) @@ -832,7 +833,8 @@ class TestConftestCustomization: mod = outcome.get_result() mod.obj.hello = "world" """ - ) + ), + encoding="utf-8", ) b.joinpath("test_module.py").write_text( textwrap.dedent( @@ -840,7 +842,8 @@ class TestConftestCustomization: def test_hello(): assert hello == "world" """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -861,7 +864,8 @@ class TestConftestCustomization: for func in result: func._some123 = "world" """ - ) + ), + encoding="utf-8", ) b.joinpath("test_module.py").write_text( textwrap.dedent( @@ -874,7 +878,8 @@ class TestConftestCustomization: def test_hello(obj): assert obj == "world" """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=1) @@ -897,25 +902,29 @@ class TestConftestCustomization: def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None: """Ensure we can collect files with weird file extensions as Python modules (#2369)""" - # We'll implement a little finder and loader to import files containing + # Implement a little meta path finder to import files containing # Python source code whose file extension is ".narf". pytester.makeconftest( """ - import sys, os, imp + import sys + import os.path + from importlib.util import spec_from_loader + from importlib.machinery import SourceFileLoader from _pytest.python import Module - class Loader(object): - def load_module(self, name): - return imp.load_source(name, name + ".narf") - class Finder(object): - def find_module(self, name, path=None): - if os.path.exists(name + ".narf"): - return Loader() - sys.meta_path.append(Finder()) + class MetaPathFinder: + def find_spec(self, fullname, path, target=None): + if os.path.exists(fullname + ".narf"): + return spec_from_loader( + fullname, + SourceFileLoader(fullname, fullname + ".narf"), + ) + sys.meta_path.append(MetaPathFinder()) def pytest_collect_file(file_path, parent): if file_path.suffix == ".narf": - return Module.from_parent(path=file_path, parent=parent)""" + return Module.from_parent(path=file_path, parent=parent) + """ ) pytester.makefile( ".narf", @@ -970,7 +979,8 @@ def test_setup_only_available_in_subdir(pytester: Pytester) -> None: def pytest_runtest_teardown(item): assert item.path.stem == "test_in_sub1" """ - ) + ), + encoding="utf-8", ) sub2.joinpath("conftest.py").write_text( textwrap.dedent( @@ -983,10 +993,11 @@ def test_setup_only_available_in_subdir(pytester: Pytester) -> None: def pytest_runtest_teardown(item): assert item.path.stem == "test_in_sub2" """ - ) + ), + encoding="utf-8", ) - sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") - sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass") + sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass", encoding="utf-8") + sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass", encoding="utf-8") result = pytester.runpytest("-v", "-s") result.assert_outcomes(passed=2) @@ -1003,9 +1014,9 @@ class TestTracebackCutting: with pytest.raises(pytest.skip.Exception) as excinfo: pytest.skip("xxx") assert excinfo.traceback[-1].frame.code.name == "skip" - assert excinfo.traceback[-1].ishidden() + assert excinfo.traceback[-1].ishidden(excinfo) assert excinfo.traceback[-2].frame.code.name == "test_skip_simple" - assert not excinfo.traceback[-2].ishidden() + assert not excinfo.traceback[-2].ishidden(excinfo) def test_traceback_argsetup(self, pytester: Pytester) -> None: pytester.makeconftest( @@ -1374,7 +1385,8 @@ def test_skip_duplicates_by_default(pytester: Pytester) -> None: def test_real(): pass """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest(str(a), str(a)) result.stdout.fnmatch_lines(["*collected 1 item*"]) @@ -1394,7 +1406,8 @@ def test_keep_duplicates(pytester: Pytester) -> None: def test_real(): pass """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("--keep-duplicates", str(a), str(a)) result.stdout.fnmatch_lines(["*collected 2 item*"]) @@ -1407,10 +1420,15 @@ def test_package_collection_infinite_recursion(pytester: Pytester) -> None: def test_package_collection_init_given_as_argument(pytester: Pytester) -> None: - """Regression test for #3749""" + """Regression test for #3749, #8976, #9263, #9313. + + Specifying an __init__.py file directly should collect only the __init__.py + Module, not the entire package. + """ p = pytester.copy_example("collect/package_init_given_as_arg") - result = pytester.runpytest(p / "pkg" / "__init__.py") - result.stdout.fnmatch_lines(["*1 passed*"]) + items, hookrecorder = pytester.inline_genitems(p / "pkg" / "__init__.py") + assert len(items) == 1 + assert items[0].name == "test_init" def test_package_with_modules(pytester: Pytester) -> None: @@ -1439,8 +1457,12 @@ def test_package_with_modules(pytester: Pytester) -> None: sub2_test = sub2.joinpath("test") sub2_test.mkdir(parents=True) - sub1_test.joinpath("test_in_sub1.py").write_text("def test_1(): pass") - sub2_test.joinpath("test_in_sub2.py").write_text("def test_2(): pass") + sub1_test.joinpath("test_in_sub1.py").write_text( + "def test_1(): pass", encoding="utf-8" + ) + sub2_test.joinpath("test_in_sub2.py").write_text( + "def test_2(): pass", encoding="utf-8" + ) # Execute from . result = pytester.runpytest("-v", "-s") @@ -1484,9 +1506,11 @@ def test_package_ordering(pytester: Pytester) -> None: sub2_test = sub2.joinpath("test") sub2_test.mkdir(parents=True) - root.joinpath("Test_root.py").write_text("def test_1(): pass") - sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass") - sub2_test.joinpath("test_sub2.py").write_text("def test_3(): pass") + root.joinpath("Test_root.py").write_text("def test_1(): pass", encoding="utf-8") + sub1.joinpath("Test_sub1.py").write_text("def test_2(): pass", encoding="utf-8") + sub2_test.joinpath("test_sub2.py").write_text( + "def test_3(): pass", encoding="utf-8" + ) # Execute from . result = pytester.runpytest("-v", "-s") diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 3ce5cb34d..e62db8c26 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -287,7 +287,8 @@ class TestFillFixtures: def spam(): return 'spam' """ - ) + ), + encoding="utf-8", ) testfile = subdir.joinpath("test_spam.py") testfile.write_text( @@ -296,7 +297,8 @@ class TestFillFixtures: def test_spam(spam): assert spam == "spam" """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) @@ -359,7 +361,8 @@ class TestFillFixtures: def spam(request): return request.param """ - ) + ), + encoding="utf-8", ) testfile = subdir.joinpath("test_spam.py") testfile.write_text( @@ -371,7 +374,8 @@ class TestFillFixtures: assert spam == params['spam'] params['spam'] += 1 """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) @@ -403,7 +407,8 @@ class TestFillFixtures: def spam(request): return request.param """ - ) + ), + encoding="utf-8", ) testfile = subdir.joinpath("test_spam.py") testfile.write_text( @@ -415,7 +420,8 @@ class TestFillFixtures: assert spam == params['spam'] params['spam'] += 1 """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines(["*3 passed*"]) @@ -1037,10 +1043,11 @@ class TestRequestBasic: def arg1(): pass """ - ) + ), + encoding="utf-8", ) p = b.joinpath("test_module.py") - p.write_text("def test_func(arg1): pass") + p.write_text("def test_func(arg1): pass", encoding="utf-8") result = pytester.runpytest(p, "--fixtures") assert result.ret == 0 result.stdout.fnmatch_lines( @@ -1617,7 +1624,8 @@ class TestFixtureManagerParseFactories: def one(): return 1 """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1625,7 +1633,8 @@ class TestFixtureManagerParseFactories: def test_x(one): assert one == 1 """ - ) + ), + encoding="utf-8", ) sub = package.joinpath("sub") sub.mkdir() @@ -1638,7 +1647,8 @@ class TestFixtureManagerParseFactories: def one(): return 2 """ - ) + ), + encoding="utf-8", ) sub.joinpath("test_y.py").write_text( textwrap.dedent( @@ -1646,7 +1656,8 @@ class TestFixtureManagerParseFactories: def test_x(one): assert one == 2 """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -1671,7 +1682,8 @@ class TestFixtureManagerParseFactories: def teardown_module(): values[:] = [] """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1680,7 +1692,8 @@ class TestFixtureManagerParseFactories: def test_x(): assert values == ["package"] """ - ) + ), + encoding="utf-8", ) package = pytester.mkdir("package2") package.joinpath("__init__.py").write_text( @@ -1692,7 +1705,8 @@ class TestFixtureManagerParseFactories: def teardown_module(): values[:] = [] """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1701,7 +1715,8 @@ class TestFixtureManagerParseFactories: def test_x(): assert values == ["package2"] """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -1714,7 +1729,7 @@ class TestFixtureManagerParseFactories: ) pytester.syspathinsert(pytester.path.name) package = pytester.mkdir("package") - package.joinpath("__init__.py").write_text("") + package.joinpath("__init__.py").write_text("", encoding="utf-8") package.joinpath("conftest.py").write_text( textwrap.dedent( """\ @@ -1731,7 +1746,8 @@ class TestFixtureManagerParseFactories: yield values values.pop() """ - ) + ), + encoding="utf-8", ) package.joinpath("test_x.py").write_text( textwrap.dedent( @@ -1742,7 +1758,8 @@ class TestFixtureManagerParseFactories: def test_package(one): assert values == ["package-auto", "package"] """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) @@ -1892,8 +1909,12 @@ class TestAutouseDiscovery: """ ) conftest.rename(a.joinpath(conftest.name)) - a.joinpath("test_something.py").write_text("def test_func(): pass") - b.joinpath("test_otherthing.py").write_text("def test_func(): pass") + a.joinpath("test_something.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) + b.joinpath("test_otherthing.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) result = pytester.runpytest() result.stdout.fnmatch_lines( """ @@ -1939,7 +1960,8 @@ class TestAutouseManagement: import sys sys._myapp = "hello" """ - ) + ), + encoding="utf-8", ) sub = pkgdir.joinpath("tests") sub.mkdir() @@ -1952,7 +1974,8 @@ class TestAutouseManagement: def test_app(): assert sys._myapp == "hello" """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run("-s") reprec.assertoutcome(passed=1) @@ -2882,7 +2905,7 @@ class TestFixtureMarker: def browser(request): def finalize(): - sys.stdout.write_text('Finalized') + sys.stdout.write_text('Finalized', encoding='utf-8') request.addfinalizer(finalize) return {} """ @@ -2900,7 +2923,8 @@ class TestFixtureMarker: def test_browser(browser): assert browser['visited'] is True """ - ) + ), + encoding="utf-8", ) reprec = pytester.runpytest("-s") for test in ["test_browser"]: @@ -3338,6 +3362,10 @@ 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( @@ -3851,7 +3879,8 @@ class TestParameterizedSubRequest: def fix_with_param(request): return request.param """ - ) + ), + encoding="utf-8", ) testfile = tests_dir.joinpath("test_foos.py") @@ -3863,7 +3892,8 @@ class TestParameterizedSubRequest: def test_foo(request): request.getfixturevalue('fix_with_param') """ - ) + ), + encoding="utf-8", ) os.chdir(tests_dir) @@ -4192,7 +4222,7 @@ class TestScopeOrdering: └── test_2.py """ root = pytester.mkdir("root") - root.joinpath("__init__.py").write_text("values = []") + root.joinpath("__init__.py").write_text("values = []", encoding="utf-8") sub1 = root.joinpath("sub1") sub1.mkdir() sub1.joinpath("__init__.py").touch() @@ -4207,7 +4237,8 @@ class TestScopeOrdering: yield values assert values.pop() == "pre-sub1" """ - ) + ), + encoding="utf-8", ) sub1.joinpath("test_1.py").write_text( textwrap.dedent( @@ -4216,7 +4247,8 @@ class TestScopeOrdering: def test_1(fix): assert values == ["pre-sub1"] """ - ) + ), + encoding="utf-8", ) sub2 = root.joinpath("sub2") sub2.mkdir() @@ -4232,7 +4264,8 @@ class TestScopeOrdering: yield values assert values.pop() == "pre-sub2" """ - ) + ), + encoding="utf-8", ) sub2.joinpath("test_2.py").write_text( textwrap.dedent( @@ -4241,7 +4274,8 @@ class TestScopeOrdering: def test_2(fix): assert values == ["pre-sub2"] """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) diff --git a/testing/python/integration.py b/testing/python/integration.py index 6b5c53c98..054c14a39 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -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 diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 2fed22718..a9e9b5269 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1,3 +1,4 @@ +import dataclasses import itertools import re import sys @@ -12,7 +13,6 @@ from typing import Sequence from typing import Tuple from typing import Union -import attr import hypothesis from hypothesis import strategies @@ -39,14 +39,14 @@ class TestMetafunc: def __init__(self, names): self.names_closure = names - @attr.s + @dataclasses.dataclass class DefinitionMock(python.FunctionDefinition): - obj = attr.ib() - _nodeid = attr.ib() + _nodeid: str + obj: object names = getfuncargnames(func) fixtureinfo: Any = FuncFixtureInfoMock(names) - definition: Any = DefinitionMock._create(func, "mock::nodeid") + definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid") return python.Metafunc(definition, fixtureinfo, config, _ispytest=True) def test_no_funcargs(self) -> None: @@ -140,9 +140,9 @@ class TestMetafunc: """Unit test for _find_parametrized_scope (#3941).""" from _pytest.python import _find_parametrized_scope - @attr.s + @dataclasses.dataclass class DummyFixtureDef: - _scope = attr.ib() + _scope: Scope fixtures_defs = cast( Dict[str, Sequence[fixtures.FixtureDef[object]]], @@ -1443,7 +1443,8 @@ class TestMetafuncFunctional: def pytest_generate_tests(metafunc): assert metafunc.function.__name__ == "test_1" """ - ) + ), + encoding="utf-8", ) sub2.joinpath("conftest.py").write_text( textwrap.dedent( @@ -1451,10 +1452,15 @@ class TestMetafuncFunctional: def pytest_generate_tests(metafunc): assert metafunc.function.__name__ == "test_2" """ - ) + ), + encoding="utf-8", + ) + sub1.joinpath("test_in_sub1.py").write_text( + "def test_1(): pass", encoding="utf-8" + ) + sub2.joinpath("test_in_sub2.py").write_text( + "def test_2(): pass", encoding="utf-8" ) - sub1.joinpath("test_in_sub1.py").write_text("def test_1(): pass") - sub2.joinpath("test_in_sub2.py").write_text("def test_2(): pass") result = pytester.runpytest("--keep-duplicates", "-v", "-s", sub1, sub2, sub1) result.assert_outcomes(passed=3) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index d8844f2e4..7119b3b5a 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -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, ] ) @@ -1367,14 +1392,14 @@ def test_sequence_comparison_uses_repr(pytester: Pytester) -> None: def test_assertrepr_loaded_per_dir(pytester: Pytester) -> None: pytester.makepyfile(test_base=["def test_base(): assert 1 == 2"]) a = pytester.mkdir("a") - a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2") + a.joinpath("test_a.py").write_text("def test_a(): assert 1 == 2", encoding="utf-8") a.joinpath("conftest.py").write_text( - 'def pytest_assertrepr_compare(): return ["summary a"]' + 'def pytest_assertrepr_compare(): return ["summary a"]', encoding="utf-8" ) b = pytester.mkdir("b") - b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2") + b.joinpath("test_b.py").write_text("def test_b(): assert 1 == 2", encoding="utf-8") b.joinpath("conftest.py").write_text( - 'def pytest_assertrepr_compare(): return ["summary b"]' + 'def pytest_assertrepr_compare(): return ["summary b"]', encoding="utf-8" ) result = pytester.runpytest() @@ -1664,15 +1689,7 @@ def test_raise_assertion_error_raising_repr(pytester: Pytester) -> None: """ ) result = pytester.runpytest() - if sys.version_info >= (3, 11): - # python 3.11 has native support for un-str-able exceptions - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) - else: - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) + result.stdout.fnmatch_lines(["E AssertionError: "]) def test_issue_1944(pytester: Pytester) -> None: diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 3c98392ed..778f843e6 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -160,7 +160,8 @@ class TestAssertionRewrite: "def special_asserter():\n" " def special_assert(x, y):\n" " assert x == y\n" - " return special_assert\n" + " return special_assert\n", + encoding="utf-8", ) pytester.makeconftest('pytest_plugins = ["plugin"]') pytester.makepyfile("def test(special_asserter): special_asserter(1, 2)\n") @@ -173,7 +174,9 @@ class TestAssertionRewrite: pytester.makepyfile(test_y="x = 1") xdir = pytester.mkdir("x") pytester.mkpydir(str(xdir.joinpath("test_Y"))) - xdir.joinpath("test_Y").joinpath("__init__.py").write_text("x = 2") + xdir.joinpath("test_Y").joinpath("__init__.py").write_text( + "x = 2", encoding="utf-8" + ) pytester.makepyfile( "import test_y\n" "import test_Y\n" @@ -726,7 +729,7 @@ class TestAssertionRewrite: class TestRewriteOnImport: def test_pycache_is_a_file(self, pytester: Pytester) -> None: - pytester.path.joinpath("__pycache__").write_text("Hello") + pytester.path.joinpath("__pycache__").write_text("Hello", encoding="utf-8") pytester.makepyfile( """ def test_rewritten(): @@ -903,7 +906,8 @@ def test_rewritten(): pkg.joinpath("test_blah.py").write_text( """ def test_rewritten(): - assert "@py_builtins" in globals()""" + assert "@py_builtins" in globals()""", + encoding="utf-8", ) assert pytester.runpytest().ret == 0 @@ -1066,7 +1070,7 @@ class TestAssertionRewriteHookDetails: source = tmp_path / "source.py" pyc = Path(str(source) + "c") - source.write_text("def test(): pass") + source.write_text("def test(): pass", encoding="utf-8") py_compile.compile(str(source), str(pyc)) contents = pyc.read_bytes() @@ -1092,7 +1096,7 @@ class TestAssertionRewriteHookDetails: fn = tmp_path / "source.py" pyc = Path(str(fn) + "c") - fn.write_text("def test(): assert True") + fn.write_text("def test(): assert True", encoding="utf-8") source_stat, co = _rewrite_test(fn, config) _write_pyc(state, co, source_stat, pyc) @@ -1157,7 +1161,7 @@ class TestAssertionRewriteHookDetails: return False def rewrite_self(): - with open(__file__, 'w') as self: + with open(__file__, 'w', encoding='utf-8') as self: self.write('def reloaded(): return True') """, test_fun=""" @@ -1187,9 +1191,10 @@ class TestAssertionRewriteHookDetails: data = pkgutil.get_data('foo.test_foo', 'data.txt') assert data == b'Hey' """ - ) + ), + encoding="utf-8", ) - path.joinpath("data.txt").write_text("Hey") + path.joinpath("data.txt").write_text("Hey", encoding="utf-8") result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 passed*"]) @@ -1265,6 +1270,267 @@ class TestIssue2121: result.stdout.fnmatch_lines(["*E*assert (1 + 1) == 3"]) +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="walrus operator not available in py<38" +) +class TestIssue10743: + def test_assertion_walrus_operator(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def change_value(value): + return value.lower() + + def test_walrus_conversion(): + a = "Hello" + assert not my_func(a, a := change_value(a)) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_dont_rewrite(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + 'PYTEST_DONT_REWRITE' + def my_func(before, after): + return before == after + + def change_value(value): + return value.lower() + + def test_walrus_conversion_dont_rewrite(): + a = "Hello" + assert not my_func(a, a := change_value(a)) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_inline_walrus_operator(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def test_walrus_conversion_inline(): + a = "Hello" + assert not my_func(a, a := a.lower()) + assert a == "hello" + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_inline_walrus_operator_reverse(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def my_func(before, after): + return before == after + + def test_walrus_conversion_reverse(): + a = "Hello" + assert my_func(a := a.lower(), a) + assert a == 'hello' + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_no_variable_name_conflict( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_no_conflict(): + a = "Hello" + assert a == (b := a.lower()) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"]) + + def test_assertion_walrus_operator_true_assertion_and_changes_variable_value( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_succeed(): + a = "Hello" + assert a != (a := a.lower()) + assert a == 'hello' + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_fail_assertion(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_walrus_conversion_fails(): + a = "Hello" + assert a == (a := a.lower()) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*AssertionError: assert 'Hello' == 'hello'"]) + + def test_assertion_walrus_operator_boolean_composite( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert a and True and ((a := False) is False) and (a is False) and ((a := None) is None) + assert a is None + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_compare_boolean_fails( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert not (a and ((a := False) is False)) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert not (True and False is False)"]) + + def test_assertion_walrus_operator_boolean_none_fails( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_boolean_value(): + a = True + assert not (a and ((a := None) is None)) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert not (True and None is None)"]) + + def test_assertion_walrus_operator_value_changes_cleared_after_each_test( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def test_walrus_operator_change_value(): + a = True + assert (a := None) is None + + def test_walrus_operator_not_override_value(): + a = True + assert a is True + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="walrus operator not available in py<38" +) +class TestIssue11028: + def test_assertion_walrus_operator_in_operand(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_in_string(): + assert (obj := "foo") in obj + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_in_operand_json_dumps( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + import json + + def test_json_encoder(): + assert (obj := "foo") in json.dumps(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a): + return a + + def test_call_other_function_arg(): + assert (obj := "foo") == f(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function_keyword_arg( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a='test'): + return a + + def test_call_other_function_k_arg(): + assert (obj := "foo") == f(a=obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function_arg_as_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a='test'): + return a + + def test_function_of_function(): + assert (obj := "foo") == f(f(obj)) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_gt_operand_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def add_one(a): + return a + 1 + + def test_gt(): + assert (obj := 4) > add_one(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert 4 > 5", "*where 5 = add_one(4)"]) + + @pytest.mark.skipif( sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems" ) diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 2baa3c8f1..e2e195ca7 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -38,7 +38,9 @@ class TestNewAPI: @pytest.mark.filterwarnings("ignore:could not create cache path") def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None: pytester.makeini("[pytest]") - pytester.path.joinpath(".pytest_cache").write_text("gone wrong") + pytester.path.joinpath(".pytest_cache").write_text( + "gone wrong", encoding="utf-8" + ) config = pytester.parseconfigure() cache = config.cache assert cache is not None @@ -87,7 +89,7 @@ class TestNewAPI: "*= warnings summary =*", "*/cacheprovider.py:*", " */cacheprovider.py:*: PytestCacheWarning: could not create cache path " - f"{unwritable_cache_dir}/v/cache/nodeids", + f"{unwritable_cache_dir}/v/cache/nodeids: *", ' config.cache.set("cache/nodeids", sorted(self.cached_nodeids))', "*1 failed, 3 warnings in*", ] @@ -420,7 +422,13 @@ class TestLastFailed: result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) - def test_terminal_report_lastfailed(self, pytester: Pytester) -> None: + @pytest.mark.parametrize("parent", ("session", "package")) + def test_terminal_report_lastfailed(self, pytester: Pytester, parent: str) -> None: + if parent == "package": + pytester.makepyfile( + __init__="", + ) + test_a = pytester.makepyfile( test_a=""" def test_a1(): pass @@ -494,7 +502,6 @@ class TestLastFailed: def test_lastfailed_collectfailure( self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: - pytester.makepyfile( test_maybe=""" import os @@ -849,6 +856,33 @@ class TestLastFailed: ] ) + def test_lastfailed_skip_collection_with_nesting(self, pytester: Pytester) -> None: + """Check that file skipping works even when the file with failures is + nested at a different level of the collection tree.""" + pytester.makepyfile( + **{ + "test_1.py": """ + def test_1(): pass + """, + "pkg/__init__.py": "", + "pkg/test_2.py": """ + def test_2(): assert False + """, + } + ) + # first run + result = pytester.runpytest() + result.stdout.fnmatch_lines(["collected 2 items", "*1 failed*1 passed*"]) + # second run - test_1.py is skipped. + result = pytester.runpytest("--lf") + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "run-last-failure: rerun previous 1 failure (skipped 1 file)", + "*= 1 failed in *", + ] + ) + def test_lastfailed_with_known_failures_not_being_selected( self, pytester: Pytester ) -> None: @@ -1053,6 +1087,28 @@ class TestLastFailed: result = pytester.runpytest("--lf") result.assert_outcomes(failed=3) + def test_non_python_file_skipped( + self, + pytester: Pytester, + dummy_yaml_custom_test: None, + ) -> None: + pytester.makepyfile( + **{ + "test_bad.py": """def test_bad(): assert False""", + }, + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines(["collected 2 items", "* 1 failed, 1 passed in *"]) + + result = pytester.runpytest("--lf") + result.stdout.fnmatch_lines( + [ + "collected 1 item", + "run-last-failure: rerun previous 1 failure (skipped 1 file)", + "* 1 failed in *", + ] + ) + class TestNewFirst: def test_newfirst_usecase(self, pytester: Pytester) -> None: @@ -1080,7 +1136,9 @@ class TestNewFirst: ["*test_2/test_2.py::test_1 PASSED*", "*test_1/test_1.py::test_1 PASSED*"] ) - p1.write_text("def test_1(): assert 1\n" "def test_2(): assert 1\n") + p1.write_text( + "def test_1(): assert 1\n" "def test_2(): assert 1\n", encoding="utf-8" + ) os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) result = pytester.runpytest("--nf", "--collect-only", "-q") @@ -1153,7 +1211,8 @@ class TestNewFirst: p1.write_text( "import pytest\n" "@pytest.mark.parametrize('num', [1, 2, 3])\n" - "def test_1(num): assert num\n" + "def test_1(num): assert num\n", + encoding="utf-8", ) os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9))) @@ -1205,7 +1264,7 @@ def test_gitignore(pytester: Pytester) -> None: assert gitignore_path.read_text(encoding="UTF-8") == msg # Does not overwrite existing/custom one. - gitignore_path.write_text("custom") + gitignore_path.write_text("custom", encoding="utf-8") cache.set("something", "else") assert gitignore_path.read_text(encoding="UTF-8") == "custom" @@ -1249,3 +1308,8 @@ 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 diff --git a/testing/test_capture.py b/testing/test_capture.py index 00cab1933..b6ea81613 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -750,9 +750,10 @@ def test_setup_failure_does_not_kill_capturing(pytester: Pytester) -> None: def pytest_runtest_setup(item): raise ValueError(42) """ - ) + ), + encoding="utf-8", ) - sub1.joinpath("test_mod.py").write_text("def test_func1(): pass") + sub1.joinpath("test_mod.py").write_text("def test_func1(): pass", encoding="utf-8") result = pytester.runpytest(pytester.path, "--traceconfig") result.stdout.fnmatch_lines(["*ValueError(42)*", "*1 error*"]) @@ -890,7 +891,7 @@ def test_dontreadfrominput() -> None: from _pytest.capture import DontReadFromInput f = DontReadFromInput() - assert f.buffer is f + assert f.buffer is f # type: ignore[comparison-overlap] assert not f.isatty() pytest.raises(OSError, f.read) pytest.raises(OSError, f.readlines) @@ -906,7 +907,10 @@ def test_dontreadfrominput() -> None: pytest.raises(UnsupportedOperation, f.write, b"") pytest.raises(UnsupportedOperation, f.writelines, []) assert not f.writable() + assert isinstance(f.encoding, str) f.close() # just for completeness + with f: + pass def test_captureresult() -> None: @@ -1049,6 +1053,7 @@ class TestFDCapture: ) ) # Should not crash with missing "_old". + assert isinstance(cap.syscapture, capture.SysCapture) assert repr(cap.syscapture) == ( " _state='done' tmpfile={!r}>".format( cap.syscapture.tmpfile @@ -1349,6 +1354,7 @@ def test_capsys_results_accessible_by_attribute(capsys: CaptureFixture[str]) -> def test_fdcapture_tmpfile_remains_the_same() -> None: cap = StdCaptureFD(out=False, err=True) + assert isinstance(cap.err, capture.FDCapture) try: cap.start_capturing() capfile = cap.err.tmpfile @@ -1518,9 +1524,9 @@ def test_global_capture_with_live_logging(pytester: Pytester) -> None: def pytest_runtest_logreport(report): if "test_global" in report.nodeid: if report.when == "teardown": - with open("caplog", "w") as f: + with open("caplog", "w", encoding="utf-8") as f: f.write(report.caplog) - with open("capstdout", "w") as f: + with open("capstdout", "w", encoding="utf-8") as f: f.write(report.capstdout) """ ) @@ -1550,14 +1556,14 @@ def test_global_capture_with_live_logging(pytester: Pytester) -> None: result = pytester.runpytest_subprocess("--log-cli-level=INFO") assert result.ret == 0 - with open("caplog") as f: + with open("caplog", encoding="utf-8") as f: caplog = f.read() assert "fix setup" in caplog assert "something in test" in caplog assert "fix teardown" in caplog - with open("capstdout") as f: + with open("capstdout", encoding="utf-8") as f: capstdout = f.read() assert "fix setup" in capstdout diff --git a/testing/test_collection.py b/testing/test_collection.py index 58e1d862a..c370951b5 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -140,7 +140,7 @@ class TestCollectFS: ensure_file(tmp_path / ".bzr" / "test_notfound.py") ensure_file(tmp_path / "normal" / "test_found.py") for x in tmp_path.rglob("test_*.py"): - x.write_text("def test_hello(): pass", "utf-8") + x.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest("--collect-only") s = result.stdout.str() @@ -162,7 +162,7 @@ class TestCollectFS: bindir = "Scripts" if sys.platform.startswith("win") else "bin" ensure_file(pytester.path / "virtual" / bindir / fname) testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py") - testfile.write_text("def test_hello(): pass") + testfile.write_text("def test_hello(): pass", encoding="utf-8") # by default, ignore tests inside a virtualenv result = pytester.runpytest() @@ -192,7 +192,7 @@ class TestCollectFS: # norecursedirs takes priority ensure_file(pytester.path / ".virtual" / bindir / fname) testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py") - testfile.write_text("def test_hello(): pass") + testfile.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest("--collect-in-virtualenv") result.stdout.no_fnmatch_line("*test_invenv*") # ...unless the virtualenv is explicitly given on the CLI @@ -231,10 +231,14 @@ class TestCollectFS: ) tmp_path = pytester.path ensure_file(tmp_path / "mydir" / "test_hello.py").write_text( - "def test_1(): pass" + "def test_1(): pass", encoding="utf-8" + ) + ensure_file(tmp_path / "xyz123" / "test_2.py").write_text( + "def test_2(): 0/0", encoding="utf-8" + ) + ensure_file(tmp_path / "xy" / "test_ok.py").write_text( + "def test_3(): pass", encoding="utf-8" ) - ensure_file(tmp_path / "xyz123" / "test_2.py").write_text("def test_2(): 0/0") - ensure_file(tmp_path / "xy" / "test_ok.py").write_text("def test_3(): pass") rec = pytester.inline_run() rec.assertoutcome(passed=1) rec = pytester.inline_run("xyz123/test_2.py") @@ -248,12 +252,14 @@ class TestCollectFS: """ ) tmp_path = pytester.path - ensure_file(tmp_path / "a" / "test_1.py").write_text("def test_a(): pass") + ensure_file(tmp_path / "a" / "test_1.py").write_text( + "def test_a(): pass", encoding="utf-8" + ) ensure_file(tmp_path / "b" / "tests" / "test_2.py").write_text( - "def test_b(): pass" + "def test_b(): pass", encoding="utf-8" ) ensure_file(tmp_path / "c" / "tests" / "test_3.py").write_text( - "def test_c(): pass" + "def test_c(): pass", encoding="utf-8" ) # executing from rootdir only tests from `testpaths` directories @@ -349,8 +355,8 @@ class TestCustomConftests: """ ) sub = pytester.mkdir("xy123") - ensure_file(sub / "test_hello.py").write_text("syntax error") - sub.joinpath("conftest.py").write_text("syntax error") + ensure_file(sub / "test_hello.py").write_text("syntax error", encoding="utf-8") + sub.joinpath("conftest.py").write_text("syntax error", encoding="utf-8") pytester.makepyfile("def test_hello(): pass") pytester.makepyfile(test_one="syntax error") result = pytester.runpytest("--fulltrace") @@ -735,6 +741,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).""" @@ -1046,13 +1066,18 @@ def test_fixture_scope_sibling_conftests(pytester: Pytester) -> None: def fix(): return 1 """ - ) + ), + encoding="utf-8", + ) + foo_path.joinpath("test_foo.py").write_text( + "def test_foo(fix): assert fix == 1", encoding="utf-8" ) - foo_path.joinpath("test_foo.py").write_text("def test_foo(fix): assert fix == 1") # Tests in `food/` should not see the conftest fixture from `foo/` food_path = pytester.mkpydir("food") - food_path.joinpath("test_food.py").write_text("def test_food(fix): assert fix == 1") + food_path.joinpath("test_food.py").write_text( + "def test_food(fix): assert fix == 1", encoding="utf-8" + ) res = pytester.runpytest() assert res.ret == 1 @@ -1183,7 +1208,8 @@ def test_collect_with_chdir_during_import(pytester: Pytester) -> None: os.chdir(%r) """ % (str(subdir),) - ) + ), + encoding="utf-8", ) pytester.makepyfile( """ @@ -1213,8 +1239,12 @@ def test_collect_pyargs_with_testpaths( ) -> None: testmod = pytester.mkdir("testmod") # NOTE: __init__.py is not collected since it does not match python_files. - testmod.joinpath("__init__.py").write_text("def test_func(): pass") - testmod.joinpath("test_file.py").write_text("def test_func(): pass") + testmod.joinpath("__init__.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) + testmod.joinpath("test_file.py").write_text( + "def test_func(): pass", encoding="utf-8" + ) root = pytester.mkdir("root") root.joinpath("pytest.ini").write_text( @@ -1224,7 +1254,8 @@ def test_collect_pyargs_with_testpaths( addopts = --pyargs testpaths = testmod """ - ) + ), + encoding="utf-8", ) monkeypatch.setenv("PYTHONPATH", str(pytester.path), prepend=os.pathsep) with monkeypatch.context() as mp: @@ -1233,6 +1264,56 @@ def test_collect_pyargs_with_testpaths( result.stdout.fnmatch_lines(["*1 passed in*"]) +def test_initial_conftests_with_testpaths(pytester: Pytester) -> None: + """The testpaths ini option should load conftests in those paths as 'initial' (#10987).""" + p = pytester.mkdir("some_path") + p.joinpath("conftest.py").write_text( + textwrap.dedent( + """ + def pytest_sessionstart(session): + raise Exception("pytest_sessionstart hook successfully run") + """ + ), + encoding="utf-8", + ) + pytester.makeini( + """ + [pytest] + testpaths = some_path + """ + ) + + # No command line args - falls back to testpaths. + result = pytester.runpytest() + assert result.ret == ExitCode.INTERNAL_ERROR + result.stdout.fnmatch_lines( + "INTERNALERROR* Exception: pytest_sessionstart hook successfully run" + ) + + # No fallback. + result = pytester.runpytest(".") + assert result.ret == ExitCode.NO_TESTS_COLLECTED + + +def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None: + """Long option values do not break initial conftests handling (#10169).""" + option_value = "x" * 1024 * 1000 + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addoption("--xx", default=None) + """ + ) + pytester.makepyfile( + f""" + def test_foo(request): + assert request.config.getoption("xx") == {option_value!r} + """ + ) + result = pytester.runpytest(f"--xx={option_value}") + assert result.ret == 0 + + def test_collect_symlink_file_arg(pytester: Pytester) -> None: """Collect a direct symlink works even if it does not match python_files (#4325).""" real = pytester.makepyfile( @@ -1260,6 +1341,7 @@ def test_collect_symlink_out_of_tree(pytester: Pytester) -> None: assert request.node.nodeid == "test_real.py::test_nodeid" """ ), + encoding="utf-8", ) out_of_tree = pytester.mkdir("out_of_tree") @@ -1288,12 +1370,16 @@ def test_collect_symlink_dir(pytester: Pytester) -> None: def test_collectignore_via_conftest(pytester: Pytester) -> None: """collect_ignore in parent conftest skips importing child (issue #4592).""" tests = pytester.mkpydir("tests") - tests.joinpath("conftest.py").write_text("collect_ignore = ['ignore_me']") + tests.joinpath("conftest.py").write_text( + "collect_ignore = ['ignore_me']", encoding="utf-8" + ) ignore_me = tests.joinpath("ignore_me") ignore_me.mkdir() ignore_me.joinpath("__init__.py").touch() - ignore_me.joinpath("conftest.py").write_text("assert 0, 'should_not_be_called'") + ignore_me.joinpath("conftest.py").write_text( + "assert 0, 'should_not_be_called'", encoding="utf-8" + ) result = pytester.runpytest() assert result.ret == ExitCode.NO_TESTS_COLLECTED @@ -1302,23 +1388,31 @@ def test_collectignore_via_conftest(pytester: Pytester) -> None: def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None: subdir = pytester.mkdir("sub") init = subdir.joinpath("__init__.py") - init.write_text("def test_init(): pass") + init.write_text("def test_init(): pass", encoding="utf-8") p = subdir.joinpath("test_file.py") - p.write_text("def test_file(): pass") + p.write_text("def test_file(): pass", encoding="utf-8") - # NOTE: without "-o python_files=*.py" this collects test_file.py twice. - # This changed/broke with "Add package scoped fixtures #2283" (2b1410895) - # initially (causing a RecursionError). - result = pytester.runpytest("-v", str(init), str(p)) + # Just the package directory, the __init__.py module is filtered out. + result = pytester.runpytest("-v", subdir) result.stdout.fnmatch_lines( [ "sub/test_file.py::test_file PASSED*", + "*1 passed in*", + ] + ) + + # But it's included if specified directly. + result = pytester.runpytest("-v", init, p) + result.stdout.fnmatch_lines( + [ + "sub/__init__.py::test_init PASSED*", "sub/test_file.py::test_file PASSED*", "*2 passed in*", ] ) - result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init), str(p)) + # Or if the pattern allows it. + result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir) result.stdout.fnmatch_lines( [ "sub/__init__.py::test_init PASSED*", @@ -1331,12 +1425,15 @@ def test_collect_pkg_init_and_file_in_args(pytester: Pytester) -> None: def test_collect_pkg_init_only(pytester: Pytester) -> None: subdir = pytester.mkdir("sub") init = subdir.joinpath("__init__.py") - init.write_text("def test_init(): pass") + init.write_text("def test_init(): pass", encoding="utf-8") - result = pytester.runpytest(str(init)) + result = pytester.runpytest(subdir) result.stdout.fnmatch_lines(["*no tests ran in*"]) - result = pytester.runpytest("-v", "-o", "python_files=*.py", str(init)) + result = pytester.runpytest("-v", init) + result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) + + result = pytester.runpytest("-v", "-o", "python_files=*.py", subdir) result.stdout.fnmatch_lines(["sub/__init__.py::test_init PASSED*", "*1 passed in*"]) @@ -1346,7 +1443,7 @@ def test_collect_sub_with_symlinks(use_pkg: bool, pytester: Pytester) -> None: sub = pytester.mkdir("sub") if use_pkg: sub.joinpath("__init__.py").touch() - sub.joinpath("test_file.py").write_text("def test_file(): pass") + sub.joinpath("test_file.py").write_text("def test_file(): pass", encoding="utf-8") # Create a broken symlink. symlink_or_skip("test_doesnotexist.py", sub.joinpath("test_broken.py")) @@ -1384,7 +1481,7 @@ def test_collector_respects_tbstyle(pytester: Pytester) -> None: def test_does_not_eagerly_collect_packages(pytester: Pytester) -> None: pytester.makepyfile("def test(): pass") pydir = pytester.mkpydir("foopkg") - pydir.joinpath("__init__.py").write_text("assert False") + pydir.joinpath("__init__.py").write_text("assert False", encoding="utf-8") result = pytester.runpytest() assert result.ret == ExitCode.OK diff --git a/testing/test_config.py b/testing/test_config.py index f5b6d7f98..257d696fa 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1,3 +1,4 @@ +import dataclasses import os import re import sys @@ -10,8 +11,6 @@ from typing import Tuple from typing import Type from typing import Union -import attr - import _pytest._code import pytest from _pytest.compat import importlib_metadata @@ -75,7 +74,7 @@ class TestParseIni: % p1.name, ) result = pytester.runpytest() - result.stdout.fnmatch_lines(["*, configfile: setup.cfg, *", "* 1 passed in *"]) + result.stdout.fnmatch_lines(["configfile: setup.cfg", "* 1 passed in *"]) assert result.ret == 0 def test_append_parse_args( @@ -88,7 +87,8 @@ class TestParseIni: [pytest] addopts = --verbose """ - ) + ), + encoding="utf-8", ) config = pytester.parseconfig(tmp_path) assert config.option.color == "no" @@ -128,7 +128,8 @@ class TestParseIni: """.format( section=section ) - ) + ), + encoding="utf-8", ) config = pytester.parseconfig() assert config.getini("minversion") == "3.36" @@ -151,7 +152,8 @@ class TestParseIni: [pytest] minversion = 2.0 """ - ) + ), + encoding="utf-8", ) pytester.path.joinpath("pytest.ini").write_text( textwrap.dedent( @@ -159,13 +161,16 @@ class TestParseIni: [pytest] minversion = 1.5 """ - ) + ), + encoding="utf-8", ) config = pytester.parseconfigure(sub) assert config.getini("minversion") == "2.0" def test_ini_parse_error(self, pytester: Pytester) -> None: - pytester.path.joinpath("pytest.ini").write_text("addopts = -x") + pytester.path.joinpath("pytest.ini").write_text( + "addopts = -x", encoding="utf-8" + ) result = pytester.runpytest() assert result.ret != 0 result.stderr.fnmatch_lines("ERROR: *pytest.ini:1: no section header defined") @@ -180,6 +185,23 @@ class TestParseIni: assert result.ret != 0 result.stderr.fnmatch_lines("ERROR: *pyproject.toml: Invalid statement*") + def test_confcutdir_default_without_configfile(self, pytester: Pytester) -> None: + # If --confcutdir is not specified, and there is no configfile, default + # to the roothpath. + sub = pytester.mkdir("sub") + os.chdir(sub) + config = pytester.parseconfigure() + assert config.pluginmanager._confcutdir == sub + + def test_confcutdir_default_with_configfile(self, pytester: Pytester) -> None: + # If --confcutdir is not specified, and there is a configfile, default + # to the configfile's directory. + pytester.makeini("[pytest]") + sub = pytester.mkdir("sub") + os.chdir(sub) + config = pytester.parseconfigure() + assert config.pluginmanager._confcutdir == pytester.path + @pytest.mark.xfail(reason="probably not needed") def test_confcutdir(self, pytester: Pytester) -> None: sub = pytester.mkdir("sub") @@ -423,11 +445,11 @@ class TestParseIni: This test installs a mock "myplugin-1.5" which is used in the parametrized test cases. """ - @attr.s + @dataclasses.dataclass class DummyEntryPoint: - name = attr.ib() - module = attr.ib() - group = "pytest11" + name: str + module: str + group: str = "pytest11" def load(self): __import__(self.module) @@ -437,11 +459,11 @@ class TestParseIni: DummyEntryPoint("myplugin1", "myplugin1_module"), ] - @attr.s + @dataclasses.dataclass class DummyDist: - entry_points = attr.ib() - files = () - version = plugin_version + entry_points: object + files: object = () + version: str = plugin_version @property def metadata(self): @@ -515,6 +537,8 @@ class TestConfigCmdlineParsing: ) config = pytester.parseconfig("-c", "custom.ini") assert config.getini("custom") == "1" + config = pytester.parseconfig("--config-file", "custom.ini") + assert config.getini("custom") == "1" pytester.makefile( ".cfg", @@ -525,6 +549,8 @@ class TestConfigCmdlineParsing: ) config = pytester.parseconfig("-c", "custom_tool_pytest_section.cfg") assert config.getini("custom") == "1" + config = pytester.parseconfig("--config-file", "custom_tool_pytest_section.cfg") + assert config.getini("custom") == "1" pytester.makefile( ".toml", @@ -537,6 +563,8 @@ class TestConfigCmdlineParsing: ) config = pytester.parseconfig("-c", "custom.toml") assert config.getini("custom") == "1" + config = pytester.parseconfig("--config-file", "custom.toml") + assert config.getini("custom") == "1" def test_absolute_win32_path(self, pytester: Pytester) -> None: temp_ini_file = pytester.makefile( @@ -551,6 +579,8 @@ class TestConfigCmdlineParsing: temp_ini_file_norm = normpath(str(temp_ini_file)) ret = pytest.main(["-c", temp_ini_file_norm]) assert ret == ExitCode.OK + ret = pytest.main(["--config-file", temp_ini_file_norm]) + assert ret == ExitCode.OK class TestConfigAPI: @@ -610,7 +640,7 @@ class TestConfigAPI: def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None: somepath = tmp_path.joinpath("x", "y", "z") p = tmp_path.joinpath("conftest.py") - p.write_text(f"mylist = {['.', str(somepath)]}") + p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8") config = pytester.parseconfigure(p) assert ( config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path) @@ -886,7 +916,8 @@ class TestConfigFromdictargs: [pytest] name = value """ - ) + ), + encoding="utf-8", ) inifilename = "../../foo/bar.ini" @@ -903,7 +934,8 @@ class TestConfigFromdictargs: name = wrong-value should_not_be_set = true """ - ) + ), + encoding="utf-8", ) with MonkeyPatch.context() as mp: mp.chdir(cwd) @@ -1152,7 +1184,7 @@ def test_cmdline_processargs_simple(pytester: Pytester) -> None: args.append("-h") """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wignore::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines(["*pytest*", "*-h*"]) @@ -1363,7 +1395,7 @@ class TestRootdir: ) def test_with_ini(self, tmp_path: Path, name: str, contents: str) -> None: inipath = tmp_path / name - inipath.write_text(contents, "utf-8") + inipath.write_text(contents, encoding="utf-8") a = tmp_path / "a" a.mkdir() @@ -1422,7 +1454,7 @@ class TestRootdir: ) -> None: p = tmp_path / name p.touch() - p.write_text(contents, "utf-8") + p.write_text(contents, encoding="utf-8") rootpath, inipath, ini_config = determine_setup(str(p), [str(tmp_path)]) assert rootpath == tmp_path assert inipath == p @@ -1518,7 +1550,8 @@ class TestOverrideIniArgs: custom = 1.0""".format( section=section ) - ) + ), + encoding="utf-8", ) pytester.makeconftest( """ @@ -1809,6 +1842,10 @@ def test_config_does_not_load_blocked_plugin_from_args(pytester: Pytester) -> No result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) assert result.ret == ExitCode.USAGE_ERROR + result = pytester.runpytest(str(p), "-p no:capture", "-s") + result.stderr.fnmatch_lines(["*: error: unrecognized arguments: -s"]) + assert result.ret == ExitCode.USAGE_ERROR + def test_invocation_args(pytester: Pytester) -> None: """Ensure that Config.invocation_* arguments are correctly defined""" @@ -1904,6 +1941,9 @@ class TestSetupCfg: with pytest.raises(pytest.fail.Exception): pytester.runpytest("-c", "custom.cfg") + with pytest.raises(pytest.fail.Exception): + pytester.runpytest("--config-file", "custom.cfg") + class TestPytestPluginsVariable: def test_pytest_plugins_in_non_top_level_conftest_unsupported( diff --git a/testing/test_conftest.py b/testing/test_conftest.py index d2bf860c6..427831507 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,4 +1,3 @@ -import argparse import os import textwrap from pathlib import Path @@ -7,6 +6,8 @@ from typing import Dict from typing import Generator from typing import List from typing import Optional +from typing import Sequence +from typing import Union import pytest from _pytest.config import ExitCode @@ -24,18 +25,18 @@ def ConftestWithSetinitial(path) -> PytestPluginManager: def conftest_setinitial( - conftest: PytestPluginManager, args, confcutdir: Optional["os.PathLike[str]"] = None + conftest: PytestPluginManager, + args: Sequence[Union[str, Path]], + confcutdir: Optional[Path] = None, ) -> None: - class Namespace: - def __init__(self) -> None: - self.file_or_dir = args - self.confcutdir = os.fspath(confcutdir) if confcutdir is not None else None - self.noconftest = False - self.pyargs = False - self.importmode = "prepend" - - namespace = cast(argparse.Namespace, Namespace()) - conftest._set_initial_conftests(namespace, rootpath=Path(args[0])) + conftest._set_initial_conftests( + args=args, + pyargs=False, + noconftest=False, + rootpath=Path(args[0]), + confcutdir=confcutdir, + importmode="prepend", + ) @pytest.mark.usefixtures("_sys_snapshot") @@ -46,8 +47,12 @@ class TestConftestValueAccessGlobal: ) -> Generator[Path, None, None]: tmp_path = tmp_path_factory.mktemp("basedir", numbered=True) tmp_path.joinpath("adir/b").mkdir(parents=True) - tmp_path.joinpath("adir/conftest.py").write_text("a=1 ; Directory = 3") - tmp_path.joinpath("adir/b/conftest.py").write_text("b=2 ; a = 1.5") + tmp_path.joinpath("adir/conftest.py").write_text( + "a=1 ; Directory = 3", encoding="utf-8" + ) + tmp_path.joinpath("adir/b/conftest.py").write_text( + "b=2 ; a = 1.5", encoding="utf-8" + ) if request.param == "inpackage": tmp_path.joinpath("adir/__init__.py").touch() tmp_path.joinpath("adir/b/__init__.py").touch() @@ -122,8 +127,12 @@ class TestConftestValueAccessGlobal: def test_conftest_in_nonpkg_with_init(tmp_path: Path, _sys_snapshot) -> None: tmp_path.joinpath("adir-1.0/b").mkdir(parents=True) - tmp_path.joinpath("adir-1.0/conftest.py").write_text("a=1 ; Directory = 3") - tmp_path.joinpath("adir-1.0/b/conftest.py").write_text("b=2 ; a = 1.5") + tmp_path.joinpath("adir-1.0/conftest.py").write_text( + "a=1 ; Directory = 3", encoding="utf-8" + ) + tmp_path.joinpath("adir-1.0/b/conftest.py").write_text( + "b=2 ; a = 1.5", encoding="utf-8" + ) tmp_path.joinpath("adir-1.0/b/__init__.py").touch() tmp_path.joinpath("adir-1.0/__init__.py").touch() ConftestWithSetinitial(tmp_path.joinpath("adir-1.0", "b")) @@ -166,7 +175,7 @@ def test_conftest_global_import(pytester: Pytester) -> None: sub = Path("sub") sub.mkdir() subconf = sub / "conftest.py" - subconf.write_text("y=4") + subconf.write_text("y=4", encoding="utf-8") mod2 = conf._importconftest(subconf, importmode="prepend", rootpath=Path.cwd()) assert mod != mod2 assert mod2.y == 4 @@ -245,7 +254,8 @@ def test_conftest_confcutdir(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--xyz", action="store_true") """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("-h", "--confcutdir=%s" % x, x) result.stdout.fnmatch_lines(["*--xyz*"]) @@ -273,9 +283,12 @@ def test_installed_conftest_is_picked_up(pytester: Pytester, tmp_path: Path) -> @pytest.fixture def fix(): return None """ - ) + ), + encoding="utf-8", + ) + tmp_path.joinpath("foo", "test_it.py").write_text( + "def test_it(fix): pass", encoding="utf-8" ) - tmp_path.joinpath("foo", "test_it.py").write_text("def test_it(fix): pass") result = pytester.runpytest("--pyargs", "foo") assert result.ret == 0 @@ -400,7 +413,8 @@ def test_conftest_existing_junitxml(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--xyz", action="store_true") """ - ) + ), + encoding="utf-8", ) pytester.makefile(ext=".xml", junit="") # Writes junit.xml result = pytester.runpytest("-h", "--junitxml", "junit.xml") @@ -411,7 +425,7 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) -> ct1 = pytester.makeconftest("") sub = pytester.mkdir("sub") ct2 = sub / "conftest.py" - ct2.write_text("") + ct2.write_text("", encoding="utf-8") def impct(p, importmode, root): return p @@ -449,7 +463,8 @@ def test_fixture_dependency(pytester: Pytester) -> None: def bar(foo): return 'bar' """ - ) + ), + encoding="utf-8", ) subsub = sub.joinpath("subsub") subsub.mkdir() @@ -466,7 +481,8 @@ def test_fixture_dependency(pytester: Pytester) -> None: def test_event_fixture(bar): assert bar == 'sub bar' """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("sub") result.stdout.fnmatch_lines(["*1 passed*"]) @@ -480,10 +496,11 @@ def test_conftest_found_with_double_dash(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--hello-world", action="store_true") """ - ) + ), + encoding="utf-8", ) p = sub.joinpath("test_hello.py") - p.write_text("def test_hello(): pass") + p.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest(str(p) + "::test_hello", "-h") result.stdout.fnmatch_lines( """ @@ -507,7 +524,8 @@ class TestConftestVisibility: def fxtr(): return "from-package" """ - ) + ), + encoding="utf-8", ) package.joinpath("test_pkgroot.py").write_text( textwrap.dedent( @@ -515,7 +533,8 @@ class TestConftestVisibility: def test_pkgroot(fxtr): assert fxtr == "from-package" """ - ) + ), + encoding="utf-8", ) swc = package.joinpath("swc") @@ -529,7 +548,8 @@ class TestConftestVisibility: def fxtr(): return "from-swc" """ - ) + ), + encoding="utf-8", ) swc.joinpath("test_with_conftest.py").write_text( textwrap.dedent( @@ -537,7 +557,8 @@ class TestConftestVisibility: def test_with_conftest(fxtr): assert fxtr == "from-swc" """ - ) + ), + encoding="utf-8", ) snc = package.joinpath("snc") @@ -550,7 +571,8 @@ class TestConftestVisibility: assert fxtr == "from-package" # No local conftest.py, so should # use value from parent dir's """ - ) + ), + encoding="utf-8", ) print("created directory structure:") for x in pytester.path.glob("**/"): @@ -594,7 +616,13 @@ class TestConftestVisibility: print("pytestarg : %s" % testarg) print("expected pass : %s" % expect_ntests_passed) os.chdir(dirs[chdir]) - reprec = pytester.inline_run(testarg, "-q", "--traceconfig") + reprec = pytester.inline_run( + testarg, + "-q", + "--traceconfig", + "--confcutdir", + pytester.path, + ) reprec.assertoutcome(passed=expect_ntests_passed) @@ -610,7 +638,7 @@ def test_search_conftest_up_to_inifile( root = pytester.path src = root.joinpath("src") src.mkdir() - src.joinpath("pytest.ini").write_text("[pytest]") + src.joinpath("pytest.ini").write_text("[pytest]", encoding="utf-8") src.joinpath("conftest.py").write_text( textwrap.dedent( """\ @@ -618,7 +646,8 @@ def test_search_conftest_up_to_inifile( @pytest.fixture def fix1(): pass """ - ) + ), + encoding="utf-8", ) src.joinpath("test_foo.py").write_text( textwrap.dedent( @@ -628,7 +657,8 @@ def test_search_conftest_up_to_inifile( def test_2(out_of_reach): pass """ - ) + ), + encoding="utf-8", ) root.joinpath("conftest.py").write_text( textwrap.dedent( @@ -637,7 +667,8 @@ def test_search_conftest_up_to_inifile( @pytest.fixture def out_of_reach(): pass """ - ) + ), + encoding="utf-8", ) args = [str(src)] @@ -720,7 +751,8 @@ def test_required_option_help(pytester: Pytester) -> None: def pytest_addoption(parser): parser.addoption("--xyz", action="store_true", required=True) """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("-h", x) result.stdout.no_fnmatch_line("*argument --xyz is required*") diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 08ae09658..eecc1e39f 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -20,9 +20,18 @@ def pdb_env(request): pytester._monkeypatch.setenv("PDBPP_HIJACK_PDB", "0") -def runpdb_and_get_report(pytester: Pytester, source: str): +def runpdb(pytester: Pytester, source: str): p = pytester.makepyfile(source) - result = pytester.runpytest_inprocess("--pdb", p) + return pytester.runpytest_inprocess("--pdb", p) + + +def runpdb_and_get_stdout(pytester: Pytester, source: str): + result = runpdb(pytester, source) + return result.stdout.str() + + +def runpdb_and_get_report(pytester: Pytester, source: str): + result = runpdb(pytester, source) reports = result.reprec.getreports("pytest_runtest_logreport") # type: ignore[attr-defined] assert len(reports) == 3, reports # setup/call/teardown return reports[1] @@ -124,6 +133,16 @@ class TestPDB: assert rep.skipped assert len(pdblist) == 0 + def test_pdb_on_top_level_raise_skiptest(self, pytester, pdblist) -> None: + stdout = runpdb_and_get_stdout( + pytester, + """ + import unittest + raise unittest.SkipTest("This is a common way to skip an entire file.") + """, + ) + assert "entering PDB" not in stdout, stdout + def test_pdb_on_BdbQuit(self, pytester, pdblist) -> None: rep = runpdb_and_get_report( pytester, diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 2f73feb8c..f189e8645 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -114,7 +114,7 @@ class TestDoctests: reprec.assertoutcome(failed=1) def test_importmode(self, pytester: Pytester): - p = pytester.makepyfile( + pytester.makepyfile( **{ "namespacepkg/innerpkg/__init__.py": "", "namespacepkg/innerpkg/a.py": """ @@ -132,7 +132,7 @@ class TestDoctests: """, } ) - reprec = pytester.inline_run(p, "--doctest-modules", "--import-mode=importlib") + reprec = pytester.inline_run("--doctest-modules", "--import-mode=importlib") reprec.assertoutcome(passed=1) def test_new_pattern(self, pytester: Pytester): @@ -357,7 +357,8 @@ class TestDoctests: >>> 1/0 ''' """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("--doctest-modules") result.stdout.fnmatch_lines( @@ -448,7 +449,8 @@ class TestDoctests: """\ import asdalsdkjaslkdjasd """ - ) + ), + encoding="utf-8", ) pytester.maketxtfile( """ @@ -492,7 +494,8 @@ class TestDoctests: 2 ''' """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest(p, "--doctest-modules") result.stdout.fnmatch_lines( @@ -1236,7 +1239,6 @@ class TestDoctestSkips: class TestDoctestAutoUseFixtures: - SCOPES = ["module", "session", "class", "function"] def test_doctest_module_session_fixture(self, pytester: Pytester): @@ -1379,7 +1381,6 @@ class TestDoctestAutoUseFixtures: class TestDoctestNamespaceFixture: - SCOPES = ["module", "session", "class", "function"] @pytest.mark.parametrize("scope", SCOPES) @@ -1568,7 +1569,9 @@ def test_warning_on_unwrap_of_broken_object( def test_is_setup_py_not_named_setup_py(tmp_path: Path) -> None: not_setup_py = tmp_path.joinpath("not_setup.py") - not_setup_py.write_text('from setuptools import setup; setup(name="foo")') + not_setup_py.write_text( + 'from setuptools import setup; setup(name="foo")', encoding="utf-8" + ) assert not _is_setup_py(not_setup_py) diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 3a2917261..8287de603 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from textwrap import dedent @@ -5,6 +6,7 @@ import pytest from _pytest.config import UsageError from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_dirs_from_args +from _pytest.config.findpaths import is_fs_root from _pytest.config.findpaths import load_config_dict_from_file @@ -133,3 +135,18 @@ def test_get_dirs_from_args(tmp_path): assert get_dirs_from_args( [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option] ) == [fn.parent, d] + + +@pytest.mark.parametrize( + "path, expected", + [ + pytest.param( + f"e:{os.sep}", True, marks=pytest.mark.skipif("sys.platform != 'win32'") + ), + (f"{os.sep}", True), + (f"e:{os.sep}projects", False), + (f"{os.sep}projects", False), + ], +) +def test_is_fs_root(path: Path, expected: bool) -> None: + assert is_fs_root(Path(path)) is expected diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index b266c76d9..690830329 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -28,7 +28,7 @@ from _pytest.stash import Stash def schema() -> xmlschema.XMLSchema: """Return an xmlschema.XMLSchema object for the junit-10.xsd file.""" fn = Path(__file__).parent / "example_scripts/junit-10.xsd" - with fn.open() as f: + with fn.open(encoding="utf-8") as f: return xmlschema.XMLSchema(f) @@ -45,7 +45,7 @@ class RunAndParse: xml_path = self.pytester.path.joinpath("junit.xml") result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args) if family == "xunit2": - with xml_path.open() as f: + with xml_path.open(encoding="utf-8") as f: self.schema.validate(f) xmldoc = minidom.parse(str(xml_path)) return result, DomNode(xmldoc) @@ -253,7 +253,6 @@ class TestPython: duration_report: str, run_and_parse: RunAndParse, ) -> None: - # mock LogXML.node_reporter so it always sets a known duration to each test report object original_node_reporter = LogXML.node_reporter @@ -470,7 +469,7 @@ class TestPython: self, pytester: Pytester, run_and_parse: RunAndParse, xunit_family: str ) -> None: p = pytester.mkdir("sub").joinpath("test_hello.py") - p.write_text("def test_func(): 0/0") + p.write_text("def test_func(): 0/0", encoding="utf-8") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -603,7 +602,6 @@ class TestPython: node.assert_attr(failures=3, tests=3) for index, char in enumerate("<&'"): - tnode = node.find_nth_by_tag("testcase", index) tnode.assert_attr( classname="test_failure_escape", name="test_func[%s]" % char @@ -989,7 +987,7 @@ class TestNonPython: return "custom item runtest failed" """ ) - pytester.path.joinpath("myfile.xyz").write_text("hello") + pytester.path.joinpath("myfile.xyz").write_text("hello", encoding="utf-8") result, dom = run_and_parse(family=xunit_family) assert result.ret node = dom.find_first_by_tag("testsuite") @@ -1015,7 +1013,7 @@ def test_nullbyte(pytester: Pytester, junit_logging: str) -> None: ) xmlf = pytester.path.joinpath("junit.xml") pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) - text = xmlf.read_text() + text = xmlf.read_text(encoding="utf-8") assert "\x00" not in text if junit_logging == "system-out": assert "#x00" in text @@ -1037,7 +1035,7 @@ def test_nullbyte_replace(pytester: Pytester, junit_logging: str) -> None: ) xmlf = pytester.path.joinpath("junit.xml") pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) - text = xmlf.read_text() + text = xmlf.read_text(encoding="utf-8") if junit_logging == "system-out": assert "#x0" in text if junit_logging == "no": diff --git a/testing/test_link_resolve.py b/testing/test_link_resolve.py index 60a86ada3..1ac3afd09 100644 --- a/testing/test_link_resolve.py +++ b/testing/test_link_resolve.py @@ -59,7 +59,8 @@ def test_link_resolve(pytester: Pytester) -> None: def test_foo(): raise AssertionError() """ - ) + ), + encoding="utf-8", ) subst = subst_path_linux diff --git a/testing/test_mark.py b/testing/test_mark.py index 65f2581bd..e2d1a40c3 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1109,3 +1109,27 @@ def test_marker_expr_eval_failure_handling(pytester: Pytester, expr) -> None: result = pytester.runpytest(foo, "-m", expr) result.stderr.fnmatch_lines([expected]) assert result.ret == ExitCode.USAGE_ERROR + + +def test_mark_mro() -> None: + xfail = pytest.mark.xfail + + @xfail("a") + class A: + pass + + @xfail("b") + class B: + pass + + @xfail("c") + class C(A, B): + pass + + from _pytest.mark.structures import get_unpacked_marks + + all_marks = get_unpacked_marks(C) + + assert all_marks == [xfail("c").mark, xfail("a").mark, xfail("b").mark] + + assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark] diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 49635f95e..8175b5f0f 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -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: @@ -324,7 +324,8 @@ def test_importerror(pytester: Pytester) -> None: x = 1 """ - ) + ), + encoding="utf-8", ) pytester.path.joinpath("test_importerror.py").write_text( textwrap.dedent( @@ -332,7 +333,8 @@ def test_importerror(pytester: Pytester) -> None: def test_importerror(monkeypatch): monkeypatch.setattr('package.a.x', 2) """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest() result.stdout.fnmatch_lines( @@ -425,6 +427,7 @@ def test_context_classmethod() -> None: assert A.x == 1 +@pytest.mark.filterwarnings(r"ignore:.*\bpkg_resources\b:DeprecationWarning") def test_syspath_prepend_with_namespace_packages( pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: @@ -433,11 +436,13 @@ def test_syspath_prepend_with_namespace_packages( ns = d.joinpath("ns_pkg") ns.mkdir() ns.joinpath("__init__.py").write_text( - "__import__('pkg_resources').declare_namespace(__name__)" + "__import__('pkg_resources').declare_namespace(__name__)", encoding="utf-8" ) lib = ns.joinpath(dirname) lib.mkdir() - lib.joinpath("__init__.py").write_text("def check(): return %r" % dirname) + lib.joinpath("__init__.py").write_text( + "def check(): return %r" % dirname, encoding="utf-8" + ) monkeypatch.syspath_prepend("hello") import ns_pkg.hello @@ -456,5 +461,5 @@ def test_syspath_prepend_with_namespace_packages( # Should invalidate caches via importlib.invalidate_caches. modules_tmpdir = pytester.mkdir("modules_tmpdir") monkeypatch.syspath_prepend(str(modules_tmpdir)) - modules_tmpdir.joinpath("main_app.py").write_text("app = True") + modules_tmpdir.joinpath("main_app.py").write_text("app = True", encoding="utf-8") from main_app import app # noqa: F401 diff --git a/testing/test_nose.py b/testing/test_nose.py index 92d6b95fd..7ec4026f2 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -23,7 +23,9 @@ def test_nose_setup(pytester: Pytester) -> None: test_hello.teardown = lambda: values.append(2) """ ) - result = pytester.runpytest(p, "-p", "nose") + result = pytester.runpytest( + p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" + ) result.assert_outcomes(passed=2) @@ -76,7 +78,9 @@ def test_nose_setup_func(pytester: Pytester) -> None: """ ) - result = pytester.runpytest(p, "-p", "nose") + result = pytester.runpytest( + p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" + ) result.assert_outcomes(passed=2) @@ -100,7 +104,9 @@ def test_nose_setup_func_failure(pytester: Pytester) -> None: """ ) - result = pytester.runpytest(p, "-p", "nose") + result = pytester.runpytest( + p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" + ) result.stdout.fnmatch_lines(["*TypeError: ()*"]) @@ -154,7 +160,9 @@ def test_nose_setup_partial(pytester: Pytester) -> None: test_hello.teardown = my_teardown_partial """ ) - result = pytester.runpytest(p, "-p", "nose") + result = pytester.runpytest( + p, "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" + ) result.stdout.fnmatch_lines(["*2 passed*"]) @@ -193,7 +201,9 @@ def test_module_level_setup(pytester: Pytester) -> None: assert items["setup2"] == ["up", "down", "up"] """ ) - result = pytester.runpytest("-p", "nose") + result = pytester.runpytest( + "-p", "nose", "-Wignore::pytest.PytestRemovedIn8Warning" + ) result.stdout.fnmatch_lines(["*4 passed*"]) @@ -278,7 +288,7 @@ def test_nose_setup_ordering(pytester: Pytester) -> None: assert self.visited_cls """ ) - result = pytester.runpytest() + result = pytester.runpytest("-Wignore::pytest.PytestRemovedIn8Warning") result.stdout.fnmatch_lines(["*1 passed*"]) @@ -494,5 +504,26 @@ def test_nose_setup_skipped_if_non_callable(pytester: Pytester) -> None: pass """, ) + result = pytester.runpytest(p.parent, "-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 diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index 992f49bc5..1899abe15 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,4 +1,5 @@ import argparse +import locale import os import shlex import subprocess @@ -289,6 +290,10 @@ class TestParser: def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: + try: + encoding = locale.getencoding() # New in Python 3.11, ignores utf-8 mode + except AttributeError: + encoding = locale.getpreferredencoding(False) try: bash_version = subprocess.run( ["bash", "--version"], @@ -296,6 +301,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: stderr=subprocess.DEVNULL, check=True, text=True, + encoding=encoding, ).stdout except (OSError, subprocess.CalledProcessError): pytest.skip("bash is not available") @@ -305,7 +311,7 @@ def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: script = str(pytester.path.joinpath("test_argcomplete")) - with open(str(script), "w") as fp: + with open(str(script), "w", encoding="utf-8") as fp: # redirect output from argcomplete to stdin and stderr is not trivial # http://stackoverflow.com/q/12589419/1307905 # so we use bash diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index c901dc6f4..56c54e484 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -91,16 +91,22 @@ class TestImportPath: yield path assert path.joinpath("samplefile").exists() + @pytest.fixture(autouse=True) + def preserve_sys(self): + with unittest.mock.patch.dict(sys.modules): + with unittest.mock.patch.object(sys, "path", list(sys.path)): + yield + def setuptestfs(self, path: Path) -> None: # print "setting up test fs for", repr(path) samplefile = path / "samplefile" - samplefile.write_text("samplefile\n") + samplefile.write_text("samplefile\n", encoding="utf-8") execfile = path / "execfile" - execfile.write_text("x=42") + execfile.write_text("x=42", encoding="utf-8") execfilepy = path / "execfile.py" - execfilepy.write_text("x=42") + execfilepy.write_text("x=42", encoding="utf-8") d = {1: 2, "hello": "world", "answer": 42} path.joinpath("samplepickle").write_bytes(pickle.dumps(d, 1)) @@ -114,9 +120,9 @@ class TestImportPath: otherdir.joinpath("__init__.py").touch() module_a = otherdir / "a.py" - module_a.write_text("from .b import stuff as result\n") + module_a.write_text("from .b import stuff as result\n", encoding="utf-8") module_b = otherdir / "b.py" - module_b.write_text('stuff="got it"\n') + module_b.write_text('stuff="got it"\n', encoding="utf-8") module_c = otherdir / "c.py" module_c.write_text( dedent( @@ -125,7 +131,8 @@ class TestImportPath: import otherdir.a value = otherdir.a.result """ - ) + ), + encoding="utf-8", ) module_d = otherdir / "d.py" module_d.write_text( @@ -135,7 +142,8 @@ class TestImportPath: from otherdir import a value2 = a.result """ - ) + ), + encoding="utf-8", ) def test_smoke_test(self, path1: Path) -> None: @@ -277,7 +285,7 @@ class TestImportPath: def simple_module(self, tmp_path: Path) -> Path: fn = tmp_path / "_src/tests/mymod.py" fn.parent.mkdir(parents=True) - fn.write_text("def foo(x): return 40 + x") + fn.write_text("def foo(x): return 40 + x", encoding="utf-8") return fn def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None: @@ -441,7 +449,7 @@ def test_samefile_false_negatives(tmp_path: Path, monkeypatch: MonkeyPatch) -> N return False, even when they are clearly equal. """ module_path = tmp_path.joinpath("my_module.py") - module_path.write_text("def foo(): return 42") + module_path.write_text("def foo(): return 42", encoding="utf-8") monkeypatch.syspath_prepend(tmp_path) with monkeypatch.context() as mp: @@ -467,7 +475,8 @@ class TestImportLibMode: class Data: value: str """ - ) + ), + encoding="utf-8", ) module = import_path(fn, mode="importlib", root=tmp_path) @@ -492,7 +501,8 @@ class TestImportLibMode: s = pickle.dumps(_action) return pickle.loads(s) """ - ) + ), + encoding="utf-8", ) module = import_path(fn, mode="importlib", root=tmp_path) @@ -512,14 +522,15 @@ class TestImportLibMode: fn1.write_text( dedent( """ - import attr + import dataclasses import pickle - @attr.s(auto_attribs=True) + @dataclasses.dataclass class Data: x: int = 42 """ - ) + ), + encoding="utf-8", ) fn2 = tmp_path.joinpath("_src/m2/tests/test.py") @@ -527,14 +538,15 @@ class TestImportLibMode: fn2.write_text( dedent( """ - import attr + import dataclasses import pickle - @attr.s(auto_attribs=True) + @dataclasses.dataclass class Data: x: str = "" """ - ) + ), + encoding="utf-8", ) import pickle diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 9fe23d177..c6f518b1d 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -347,7 +347,7 @@ class TestPytestPluginManager: pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y") pytester.syspathinsert() - pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3") + pytester.mkpydir("pkg").joinpath("plug.py").write_text("x=3", encoding="utf-8") pluginname = "pkg.plug" pytestpm.import_plugin(pluginname) mod = pytestpm.get_plugin("pkg.plug") diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 62dad9858..8f8b4d291 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -222,7 +222,7 @@ class TestInlineRunModulesCleanup: result = pytester.inline_run(str(test_mod)) assert result.ret == ExitCode.OK # rewrite module, now test should fail if module was re-imported - test_mod.write_text("def test_foo(): assert False") + test_mod.write_text("def test_foo(): assert False", encoding="utf-8") result2 = pytester.inline_run(str(test_mod)) assert result2.ret == ExitCode.TESTS_FAILED diff --git a/testing/test_python_path.py b/testing/test_python_path.py index 5ee0f55e3..e1628feb1 100644 --- a/testing/test_python_path.py +++ b/testing/test_python_path.py @@ -82,7 +82,7 @@ def test_no_ini(pytester: Pytester, file_structure) -> None: def test_clean_up(pytester: Pytester) -> None: """Test that the plugin cleans up after itself.""" - # This is tough to test behaviorly because the cleanup really runs last. + # This is tough to test behaviorally because the cleanup really runs last. # So the test make several implementation assumptions: # - Cleanup is done in pytest_unconfigure(). # - Not a hookwrapper. diff --git a/testing/test_reports.py b/testing/test_reports.py index 31b6cf1af..387d2e807 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -6,6 +6,7 @@ from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr from _pytest.config import Config from _pytest.pytester import Pytester +from _pytest.python_api import approx from _pytest.reports import CollectReport from _pytest.reports import TestReport @@ -409,12 +410,32 @@ class TestReportSerialization: ) -> None: sub_dir = pytester.path.joinpath("ns") sub_dir.mkdir() - sub_dir.joinpath("conftest.py").write_text("import unknown") + sub_dir.joinpath("conftest.py").write_text("import unknown", encoding="utf-8") result = pytester.runpytest_subprocess(".") result.stdout.fnmatch_lines(["E *Error: No module named 'unknown'"]) result.stdout.no_fnmatch_line("ERROR - *ConftestImportFailure*") + def test_report_timestamps_match_duration(self, pytester: Pytester, mock_timing): + reprec = pytester.inline_runsource( + """ + import pytest + from _pytest import timing + @pytest.fixture + def fixture_(): + timing.sleep(5) + yield + timing.sleep(5) + def test_1(fixture_): timing.sleep(10) + """ + ) + reports = reprec.getreports("pytest_runtest_logreport") + assert len(reports) == 3 + for report in reports: + data = report._to_json() + loaded_report = TestReport._from_json(data) + assert loaded_report.stop - loaded_report.start == approx(report.duration) + class TestHooks: """Test that the hooks are working correctly for plugins""" diff --git a/testing/test_runner.py b/testing/test_runner.py index 2e2c462d9..de3e18184 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -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: @@ -447,6 +473,7 @@ class TestSessionReports: assert not rep.skipped assert rep.passed locinfo = rep.location + assert locinfo is not None assert locinfo[0] == col.path.name assert not locinfo[1] assert locinfo[2] == col.path.name @@ -880,6 +907,7 @@ def test_makereport_getsource_dynamic_code( def test_store_except_info_on_error() -> None: """Test that upon test failure, the exception info is stored on sys.last_traceback and friends.""" + # Simulate item that might raise a specific exception, depending on `raise_error` class var class ItemMightRaise: nodeid = "item_that_raises" diff --git a/testing/test_session.py b/testing/test_session.py index f73dc89ef..48dc08e8c 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -265,9 +265,9 @@ def test_plugin_already_exists(pytester: Pytester) -> None: def test_exclude(pytester: Pytester) -> None: hellodir = pytester.mkdir("hello") - hellodir.joinpath("test_hello.py").write_text("x y syntaxerror") + hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8") hello2dir = pytester.mkdir("hello2") - hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror") + hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8") pytester.makepyfile(test_ok="def test_pass(): pass") result = pytester.runpytest("--ignore=hello", "--ignore=hello2") assert result.ret == 0 @@ -276,13 +276,13 @@ def test_exclude(pytester: Pytester) -> None: def test_exclude_glob(pytester: Pytester) -> None: hellodir = pytester.mkdir("hello") - hellodir.joinpath("test_hello.py").write_text("x y syntaxerror") + hellodir.joinpath("test_hello.py").write_text("x y syntaxerror", encoding="utf-8") hello2dir = pytester.mkdir("hello2") - hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror") + hello2dir.joinpath("test_hello2.py").write_text("x y syntaxerror", encoding="utf-8") hello3dir = pytester.mkdir("hallo3") - hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror") + hello3dir.joinpath("test_hello3.py").write_text("x y syntaxerror", encoding="utf-8") subdir = pytester.mkdir("sub") - subdir.joinpath("test_hello4.py").write_text("x y syntaxerror") + subdir.joinpath("test_hello4.py").write_text("x y syntaxerror", encoding="utf-8") pytester.makepyfile(test_ok="def test_pass(): pass") result = pytester.runpytest("--ignore-glob=*h[ea]llo*") assert result.ret == 0 diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 6415480ef..6b8034610 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -195,7 +195,8 @@ class TestEvaluation: def pytest_markeval_namespace(): return {"arg": "root"} """ - ) + ), + encoding="utf-8", ) root.joinpath("test_root.py").write_text( textwrap.dedent( @@ -206,7 +207,8 @@ class TestEvaluation: def test_root(): assert False """ - ) + ), + encoding="utf-8", ) foo = root.joinpath("foo") foo.mkdir() @@ -219,7 +221,8 @@ class TestEvaluation: def pytest_markeval_namespace(): return {"arg": "foo"} """ - ) + ), + encoding="utf-8", ) foo.joinpath("test_foo.py").write_text( textwrap.dedent( @@ -230,7 +233,8 @@ class TestEvaluation: def test_foo(): assert False """ - ) + ), + encoding="utf-8", ) bar = root.joinpath("bar") bar.mkdir() @@ -243,7 +247,8 @@ class TestEvaluation: def pytest_markeval_namespace(): return {"arg": "bar"} """ - ) + ), + encoding="utf-8", ) bar.joinpath("test_bar.py").write_text( textwrap.dedent( @@ -254,7 +259,8 @@ class TestEvaluation: def test_bar(): assert False """ - ) + ), + encoding="utf-8", ) reprec = pytester.inline_run("-vs", "--capture=no") @@ -629,7 +635,8 @@ class TestXFail: @pytest.mark.xfail(reason='unsupported feature', strict=%s) def test_foo(): - with open('foo_executed', 'w'): pass # make sure test executes + with open('foo_executed', 'w', encoding='utf-8'): + pass # make sure test executes """ % strict ) @@ -1439,6 +1446,27 @@ def test_relpath_rootdir(pytester: Pytester) -> None: ) +def test_skip_from_fixture(pytester: Pytester) -> None: + pytester.makepyfile( + **{ + "tests/test_1.py": """ + import pytest + def test_pass(arg): + pass + @pytest.fixture + def arg(): + condition = True + if condition: + pytest.skip("Fixture conditional skip") + """, + } + ) + result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") + result.stdout.fnmatch_lines( + ["SKIPPED [[]1[]] tests/test_1.py:2: Fixture conditional skip"] + ) + + def test_skip_using_reason_works_ok(pytester: Pytester) -> None: p = pytester.makepyfile( """ diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 20781d42c..85e38c7d5 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -1,6 +1,10 @@ +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 @@ -278,3 +282,76 @@ 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(encoding="utf-8") as file_handle: + observed_value = file_handle.readlines() + assert [expected_value] == observed_value diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 9a8dd4d9a..7c2f7c94a 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -244,7 +244,8 @@ class TestTerminal: def test_method(self): pass """ - ) + ), + encoding="utf-8", ) result = pytester.runpytest("-vv") assert result.ret == 0 @@ -387,13 +388,13 @@ class TestTerminal: pytest.xfail("It's 🕙 o'clock") @pytest.mark.skip( - reason="cannot do foobar because baz is missing due to I don't know what" + reason="1 cannot do foobar because baz is missing due to I don't know what" ) def test_long_skip(): pass @pytest.mark.xfail( - reason="cannot do foobar because baz is missing due to I don't know what" + reason="2 cannot do foobar because baz is missing due to I don't know what" ) def test_long_xfail(): print(1 / 0) @@ -417,8 +418,8 @@ class TestTerminal: result.stdout.fnmatch_lines( common_output + [ - "test_verbose_skip_reason.py::test_long_skip SKIPPED (cannot *...) *", - "test_verbose_skip_reason.py::test_long_xfail XFAIL (cannot *...) *", + "test_verbose_skip_reason.py::test_long_skip SKIPPED (1 cannot *...) *", + "test_verbose_skip_reason.py::test_long_xfail XFAIL (2 cannot *...) *", ] ) @@ -428,12 +429,14 @@ class TestTerminal: + [ ( "test_verbose_skip_reason.py::test_long_skip SKIPPED" - " (cannot do foobar because baz is missing due to I don't know what) *" + " (1 cannot do foobar" ), + "because baz is missing due to I don't know what) *", ( "test_verbose_skip_reason.py::test_long_xfail XFAIL" - " (cannot do foobar because baz is missing due to I don't know what) *" + " (2 cannot do foobar" ), + "because baz is missing due to I don't know what) *", ] ) @@ -909,7 +912,7 @@ class TestTerminalFunctional: # with configfile pytester.makeini("""[pytest]""") result = pytester.runpytest() - result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"]) # with testpaths option, and not passing anything in the command-line pytester.makeini( @@ -920,12 +923,12 @@ class TestTerminalFunctional: ) result = pytester.runpytest() result.stdout.fnmatch_lines( - ["rootdir: *test_header0, configfile: tox.ini, testpaths: tests, gui"] + ["rootdir: *test_header0", "configfile: tox.ini", "testpaths: tests, gui"] ) # with testpaths option, passing directory in command-line: do not show testpaths then result = pytester.runpytest("tests") - result.stdout.fnmatch_lines(["rootdir: *test_header0, configfile: tox.ini"]) + result.stdout.fnmatch_lines(["rootdir: *test_header0", "configfile: tox.ini"]) def test_header_absolute_testpath( self, pytester: Pytester, monkeypatch: MonkeyPatch @@ -944,9 +947,9 @@ class TestTerminalFunctional: result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "rootdir: *absolute_testpath0, configfile: pyproject.toml, testpaths: {}".format( - tests - ) + "rootdir: *absolute_testpath0", + "configfile: pyproject.toml", + f"testpaths: {tests}", ] ) @@ -998,6 +1001,22 @@ class TestTerminalFunctional: ] ) + def test_noshowlocals_addopts_override(self, pytester: Pytester) -> None: + pytester.makeini("[pytest]\naddopts=--showlocals") + p1 = pytester.makepyfile( + """ + def test_noshowlocals(): + x = 3 + y = "x" * 5000 + assert 0 + """ + ) + + # Override global --showlocals for py.test via arg + result = pytester.runpytest(p1, "--no-showlocals") + result.stdout.no_fnmatch_line("x* = 3") + result.stdout.no_fnmatch_line("y* = 'xxxxxx*") + def test_showlocals_short(self, pytester: Pytester) -> None: p1 = pytester.makepyfile( """ @@ -1249,14 +1268,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}():", - "> fail()", + " {kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", + "> fail(){endline}", "", "{bold}{red}test_color_yes.py{reset}:5: ", "_ _ * _ _*", "", - " {kw}def{hl-reset} {function}fail{hl-reset}():", - "> {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}def{hl-reset} {function}fail{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "", "{bold}{red}test_color_yes.py{reset}:2: AssertionError", @@ -1276,9 +1295,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()", + " fail(){endline}", "{bold}{red}test_color_yes.py{reset}:2: in fail", - " {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", ] @@ -1523,6 +1542,19 @@ class TestGenericReporting: s = result.stdout.str() assert "def test_func2" not in s + def test_tb_crashline_pytrace_false(self, pytester: Pytester, option) -> None: + p = pytester.makepyfile( + """ + import pytest + def test_func1(): + pytest.fail('test_func1', pytrace=False) + """ + ) + result = pytester.runpytest("--tb=line") + result.stdout.str() + bn = p.name + result.stdout.fnmatch_lines(["*%s:3: Failed: test_func1" % bn]) + def test_pytest_report_header(self, pytester: Pytester, option) -> None: pytester.makeconftest( """ @@ -1536,7 +1568,8 @@ class TestGenericReporting: """ def pytest_report_header(config, start_path): return ["line1", str(start_path)] -""" +""", + encoding="utf-8", ) result = pytester.runpytest("a") result.stdout.fnmatch_lines(["*hello: 42*", "line1", str(pytester.path)]) @@ -1640,7 +1673,7 @@ def test_fdopen_kept_alive_issue124(pytester: Pytester) -> None: import os, sys k = [] def test_open_file_and_keep_alive(capfd): - stdout = os.fdopen(1, 'w', 1) + stdout = os.fdopen(1, 'w', buffering=1, encoding='utf-8') k.append(stdout) def test_close_kept_alive_file(): @@ -2197,6 +2230,24 @@ class TestProgressOutputStyle: output = pytester.runpytest("--capture=no") output.stdout.no_fnmatch_line("*%]*") + def test_capture_no_progress_enabled( + self, many_tests_files, pytester: Pytester + ) -> None: + pytester.makeini( + """ + [pytest] + console_output_style = progress-even-when-capture-no + """ + ) + output = pytester.runpytest("-s") + output.stdout.re_match_lines( + [ + r"test_bar.py \.{10} \s+ \[ 50%\]", + r"test_foo.py \.{5} \s+ \[ 75%\]", + r"test_foobar.py \.{5} \s+ \[100%\]", + ] + ) + class TestProgressWithTeardown: """Ensure we show the correct percentages for tests that fail during teardown (#3088)""" @@ -2456,8 +2507,8 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) @@ -2478,9 +2529,9 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", - "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}", + "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", ] ) @@ -2501,8 +2552,8 @@ class TestCodeHighlight: result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 4f7c53847..1e1446af1 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,3 +1,4 @@ +import dataclasses import os import stat import sys @@ -6,8 +7,7 @@ from pathlib import Path from typing import Callable from typing import cast from typing import List - -import attr +from typing import Union import pytest from _pytest import pathlib @@ -31,9 +31,9 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: results.stdout.fnmatch_lines(["*1 passed*"]) -@attr.s +@dataclasses.dataclass class FakeConfig: - basetemp = attr.ib() + basetemp: Union[str, Path] @property def trace(self): @@ -42,13 +42,21 @@ 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 "all" + else: + assert False + @property def option(self): return self class TestTmpPathHandler: - def test_mktemp(self, tmp_path): + def test_mktemp(self, tmp_path: Path) -> None: config = cast(Config, FakeConfig(tmp_path)) t = TempPathFactory.from_config(config, _ispytest=True) tmp = t.mktemp("world") @@ -59,7 +67,9 @@ class TestTmpPathHandler: assert str(tmp2.relative_to(t.getbasetemp())).startswith("this") assert tmp2 != tmp - def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): + def test_tmppath_relative_basetemp_absolute( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: """#4425""" monkeypatch.chdir(tmp_path) config = cast(Config, FakeConfig("hello")) @@ -84,6 +94,136 @@ 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.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + 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.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + 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.makepyprojecttoml( + """ + [tool.pytest.ini_options] + tmp_path_retention_policy = "failed" + """ + ) + + 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 +415,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 +429,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) @@ -367,20 +512,20 @@ class TestRmRf: # unknown exception with pytest.warns(pytest.PytestWarning): - exc_info1 = (None, RuntimeError(), None) + exc_info1 = (RuntimeError, RuntimeError(), None) on_rm_rf_error(os.unlink, str(fn), exc_info1, start_path=tmp_path) assert fn.is_file() # we ignore FileNotFoundError - exc_info2 = (None, FileNotFoundError(), None) + exc_info2 = (FileNotFoundError, FileNotFoundError(), None) assert not on_rm_rf_error(None, str(fn), exc_info2, start_path=tmp_path) # unknown function with pytest.warns( pytest.PytestWarning, - match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\nNone: ", + match=r"^\(rm_rf\) unknown function None when removing .*foo.txt:\n: ", ): - exc_info3 = (None, PermissionError(), None) + exc_info3 = (PermissionError, PermissionError(), None) on_rm_rf_error(None, str(fn), exc_info3, start_path=tmp_path) assert fn.is_file() @@ -388,12 +533,12 @@ class TestRmRf: with warnings.catch_warnings(): warnings.simplefilter("ignore") with pytest.warns(None) as warninfo: # type: ignore[call-overload] - exc_info4 = (None, PermissionError(), None) + exc_info4 = PermissionError() on_rm_rf_error(os.open, str(fn), exc_info4, start_path=tmp_path) assert fn.is_file() assert not [x.message for x in warninfo] - exc_info5 = (None, PermissionError(), None) + exc_info5 = PermissionError() on_rm_rf_error(os.unlink, str(fn), exc_info5, start_path=tmp_path) assert not fn.is_file() @@ -416,7 +561,7 @@ def test_basetemp_with_read_only_files(pytester: Pytester) -> None: def test(tmp_path): fn = tmp_path / 'foo.txt' - fn.write_text('hello') + fn.write_text('hello', encoding='utf-8') mode = os.stat(str(fn)).st_mode os.chmod(str(fn), mode & ~stat.S_IREAD) """ @@ -446,7 +591,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, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # No world-readable permissions. @@ -466,14 +611,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, "all", 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, "all", lambda *args: None, _ispytest=True) basetemp = tmp_factory.getbasetemp() # After - fixed. diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 7b716bb45..96ecad6f6 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -518,7 +518,8 @@ class TestDeprecationWarningsByDefault: assert WARNINGS_SUMMARY_HEADER not in result.stdout.str() -@pytest.mark.skip("not relevant until pytest 8.0") +# In 8.1, uncomment below and change RemovedIn8 -> RemovedIn9. +# @pytest.mark.skip("not relevant until pytest 9.0") @pytest.mark.parametrize("change_default", [None, "ini", "cmdline"]) def test_removed_in_x_warning_as_error(pytester: Pytester, change_default) -> None: """This ensures that PytestRemovedInXWarnings raised by pytest are turned into errors. @@ -777,6 +778,20 @@ class TestStackLevel: ) +def test_warning_on_testpaths_not_found(pytester: Pytester) -> None: + # Check for warning when testpaths set, but not found by glob + pytester.makeini( + """ + [pytest] + testpaths = absent + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + ["*ConfigWarning: No files were found in testpaths*", "*1 warning*"] + ) + + def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) -> None: # Some platforms (notably PyPy) don't have tracemalloc. # We choose to explicitly not skip this in case tracemalloc is not @@ -796,12 +811,12 @@ def test_resource_warning(pytester: Pytester, monkeypatch: pytest.MonkeyPatch) - pytester.makepyfile( """ def open_file(p): - f = p.open("r") + f = p.open("r", encoding="utf-8") assert p.read_text() == "hello" def test_resource_warning(tmp_path): p = tmp_path.joinpath("foo.txt") - p.write_text("hello") + p.write_text("hello", encoding="utf-8") open_file(p) """ ) diff --git a/testing/typing_checks.py b/testing/typing_checks.py index 0a6b5ad28..57f2bae47 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -3,7 +3,13 @@ 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 +from pytest import MonkeyPatch # Issue #7488. @@ -22,3 +28,22 @@ 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 + + +# Issue #10999. +def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None: + from typing import TypedDict + + class Foo(TypedDict): + x: int + y: float + + a: Foo = {"x": 1, "y": 3.14} + monkeypatch.setitem(a, "x", 2) + monkeypatch.delitem(a, "y") + + +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]]) diff --git a/tox.ini b/tox.ini index f1ab4b815..05c1842af 100644 --- a/tox.ini +++ b/tox.ini @@ -9,8 +9,9 @@ envlist = py39 py310 py311 + py312 pypy3 - py37-{pexpect,xdist,unittestextras,numpy,pluggymain} + py37-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib} doctesting plugins py37-freeze @@ -29,10 +30,18 @@ commands = doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest coverage: coverage combine coverage: coverage report -m -passenv = USER USERNAME COVERAGE_* PYTEST_ADDOPTS TERM SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + COVERAGE_* + PYTEST_ADDOPTS + TERM + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} + # See https://docs.python.org/3/library/io.html#io-encoding-warning + # If we don't enable this, neither can any of our downstream users! + PYTHONWARNDEFAULTENCODING=1 + # Configuration to run with coverage similar to CI, e.g. # "tox -e py37-coverage". coverage: _PYTEST_TOX_COVERAGE_RUN=coverage run -m @@ -54,6 +63,7 @@ deps = numpy: numpy>=1.19.4 pexpect: pexpect>=4.8.0 pluggymain: pluggy @ git+https://github.com/pytest-dev/pluggy.git + pylib: py>=1.8.2 unittestextras: twisted unittestextras: asynctest xdist: pytest-xdist>=2.1.0 @@ -91,13 +101,13 @@ commands = [testenv:regen] changedir = doc/en basepython = python3 -passenv = SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST deps = - dataclasses PyYAML regendoc>=0.8.1 sphinx -whitelist_externals = +allowlist_externals = make commands = make regen @@ -159,7 +169,10 @@ commands = python scripts/prepare-release-pr.py {posargs} description = create GitHub release after deployment basepython = python3 usedevelop = True -passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY +passenv = + GH_RELEASE_NOTES_TOKEN + GITHUB_REF + GITHUB_REPOSITORY deps = github3.py pypandoc