Merge branch 'main' of https://github.com/JiajunXu2/pytest
This commit is contained in:
commit
b356bd52dd
|
@ -31,7 +31,7 @@ jobs:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Build and Check Package
|
- name: Build and Check Package
|
||||||
uses: hynek/build-and-inspect-python-package@v2.5.0
|
uses: hynek/build-and-inspect-python-package@v2.6.0
|
||||||
with:
|
with:
|
||||||
attest-build-provenance-github: 'true'
|
attest-build-provenance-github: 'true'
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ jobs:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Build and Check Package
|
- name: Build and Check Package
|
||||||
uses: hynek/build-and-inspect-python-package@v2.5.0
|
uses: hynek/build-and-inspect-python-package@v2.6.0
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: [package]
|
needs: [package]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: "v0.4.4"
|
rev: "v0.4.7"
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: ["--fix"]
|
args: ["--fix"]
|
||||||
|
@ -43,6 +43,11 @@ repos:
|
||||||
- id: pyproject-fmt
|
- id: pyproject-fmt
|
||||||
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
|
# https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version
|
||||||
additional_dependencies: ["tox>=4.9"]
|
additional_dependencies: ["tox>=4.9"]
|
||||||
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
|
rev: v3.15.2
|
||||||
|
hooks:
|
||||||
|
- id: pyupgrade
|
||||||
|
stages: [manual]
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: pylint
|
- id: pylint
|
||||||
|
|
3
AUTHORS
3
AUTHORS
|
@ -192,6 +192,7 @@ Jake VanderPlas
|
||||||
Jakob van Santen
|
Jakob van Santen
|
||||||
Jakub Mitoraj
|
Jakub Mitoraj
|
||||||
James Bourbeau
|
James Bourbeau
|
||||||
|
James Frost
|
||||||
Jan Balster
|
Jan Balster
|
||||||
Janne Vanhala
|
Janne Vanhala
|
||||||
Jason R. Coombs
|
Jason R. Coombs
|
||||||
|
@ -278,6 +279,7 @@ Michael Droettboom
|
||||||
Michael Goerz
|
Michael Goerz
|
||||||
Michael Krebs
|
Michael Krebs
|
||||||
Michael Seifert
|
Michael Seifert
|
||||||
|
Michael Vogt
|
||||||
Michal Wajszczuk
|
Michal Wajszczuk
|
||||||
Michał Górny
|
Michał Górny
|
||||||
Michał Zięba
|
Michał Zięba
|
||||||
|
@ -289,6 +291,7 @@ Mike Lundy
|
||||||
Milan Lesnek
|
Milan Lesnek
|
||||||
Miro Hrončok
|
Miro Hrončok
|
||||||
mrbean-bremen
|
mrbean-bremen
|
||||||
|
Nathan Goldbaum
|
||||||
Nathaniel Compton
|
Nathaniel Compton
|
||||||
Nathaniel Waisbrot
|
Nathaniel Waisbrot
|
||||||
Ned Batchelder
|
Ned Batchelder
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
Updated Sphinx theme to use Furo instead of Flask, enabling Dark mode theme.
|
|
|
@ -0,0 +1 @@
|
||||||
|
Fix crash with `assert testcase is not None` assertion failure when re-running unittest tests using plugins like pytest-rerunfailures. Regressed in 8.2.2.
|
|
@ -0,0 +1 @@
|
||||||
|
Do not truncate arguments to functions in output when running with `-vvv`.
|
|
@ -6,6 +6,7 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-8.2.2
|
||||||
release-8.2.1
|
release-8.2.1
|
||||||
release-8.2.0
|
release-8.2.0
|
||||||
release-8.1.2
|
release-8.1.2
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
pytest-8.2.2
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
pytest 8.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
|
||||||
|
* Ran Benita
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The pytest Development Team
|
|
@ -22,7 +22,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
cachedir: .pytest_cache
|
cachedir: .pytest_cache
|
||||||
rootdir: /home/sweet/project
|
rootdir: /home/sweet/project
|
||||||
collected 0 items
|
collected 0 items
|
||||||
cache -- .../_pytest/cacheprovider.py:549
|
cache -- .../_pytest/cacheprovider.py:560
|
||||||
Return a cache object that can persist state between testing sessions.
|
Return a cache object that can persist state between testing sessions.
|
||||||
|
|
||||||
cache.get(key, default)
|
cache.get(key, default)
|
||||||
|
@ -115,7 +115,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
||||||
|
|
||||||
For more details: :ref:`doctest_namespace`.
|
For more details: :ref:`doctest_namespace`.
|
||||||
|
|
||||||
pytestconfig [session scope] -- .../_pytest/fixtures.py:1335
|
pytestconfig [session scope] -- .../_pytest/fixtures.py:1338
|
||||||
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
Session-scoped fixture that returns the session's :class:`pytest.Config`
|
||||||
object.
|
object.
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,35 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 8.2.2 (2024-06-04)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#12355 <https://github.com/pytest-dev/pytest/issues/12355>`_: Fix possible catastrophic performance slowdown on a certain parametrization pattern involving many higher-scoped parameters.
|
||||||
|
|
||||||
|
|
||||||
|
- `#12367 <https://github.com/pytest-dev/pytest/issues/12367>`_: Fix a regression in pytest 8.2.0 where unittest class instances (a fresh one is created for each test) were not released promptly on test teardown but only on session teardown.
|
||||||
|
|
||||||
|
|
||||||
|
- `#12381 <https://github.com/pytest-dev/pytest/issues/12381>`_: Fix possible "Directory not empty" crashes arising from concurent cache dir (``.pytest_cache``) creation. Regressed in pytest 8.2.0.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#12290 <https://github.com/pytest-dev/pytest/issues/12290>`_: Updated Sphinx theme to use Furo instead of Flask, enabling Dark mode theme.
|
||||||
|
|
||||||
|
|
||||||
|
- `#12356 <https://github.com/pytest-dev/pytest/issues/12356>`_: Added a subsection to the documentation for debugging flaky tests to mention
|
||||||
|
lack of thread safety in pytest as a possible source of flakyness.
|
||||||
|
|
||||||
|
|
||||||
|
- `#12363 <https://github.com/pytest-dev/pytest/issues/12363>`_: The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results.
|
||||||
|
|
||||||
|
|
||||||
pytest 8.2.1 (2024-05-19)
|
pytest 8.2.1 (2024-05-19)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -312,6 +312,9 @@ html_show_sourcelink = False
|
||||||
# Output file base name for HTML help builder.
|
# Output file base name for HTML help builder.
|
||||||
htmlhelp_basename = "pytestdoc"
|
htmlhelp_basename = "pytestdoc"
|
||||||
|
|
||||||
|
# The base URL which points to the root of the HTML documentation. It is used
|
||||||
|
# to indicate the location of document using the canonical link relation (#12363).
|
||||||
|
html_baseurl = "https://docs.pytest.org/en/stable/"
|
||||||
|
|
||||||
# -- Options for LaTeX output --------------------------------------------------
|
# -- Options for LaTeX output --------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -26,19 +26,12 @@ Contact channels
|
||||||
<https://web.libera.chat/#pytest>`_, or `via Matrix
|
<https://web.libera.chat/#pytest>`_, or `via Matrix
|
||||||
<https://matrix.to/#/%23pytest:libera.chat>`_).
|
<https://matrix.to/#/%23pytest:libera.chat>`_).
|
||||||
|
|
||||||
- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
|
|
||||||
|
|
||||||
|
|
||||||
- `merlinux.eu`_ offers pytest and tox-related professional teaching and
|
|
||||||
consulting.
|
|
||||||
|
|
||||||
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues
|
||||||
.. _`old issue tracker`: https://bitbucket.org/hpk42/py-trunk/issues/
|
.. _`old issue tracker`: https://bitbucket.org/hpk42/py-trunk/issues/
|
||||||
|
|
||||||
.. _`pytest discussions`: https://github.com/pytest-dev/pytest/discussions
|
.. _`pytest discussions`: https://github.com/pytest-dev/pytest/discussions
|
||||||
|
|
||||||
.. _`merlinux.eu`: https://merlinux.eu/
|
|
||||||
|
|
||||||
.. _`get an account`:
|
.. _`get an account`:
|
||||||
|
|
||||||
.. _tetamap: https://tetamap.wordpress.com/
|
.. _tetamap: https://tetamap.wordpress.com/
|
||||||
|
|
|
@ -162,7 +162,7 @@ objects, they are still using the default pytest representation:
|
||||||
rootdir: /home/sweet/project
|
rootdir: /home/sweet/project
|
||||||
collected 8 items
|
collected 8 items
|
||||||
|
|
||||||
<Dir parametrize.rst-199>
|
<Dir parametrize.rst-200>
|
||||||
<Module test_time.py>
|
<Module test_time.py>
|
||||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||||
<Function test_timedistance_v0[a1-b1-expected1]>
|
<Function test_timedistance_v0[a1-b1-expected1]>
|
||||||
|
@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
||||||
rootdir: /home/sweet/project
|
rootdir: /home/sweet/project
|
||||||
collected 4 items
|
collected 4 items
|
||||||
|
|
||||||
<Dir parametrize.rst-199>
|
<Dir parametrize.rst-200>
|
||||||
<Module test_scenarios.py>
|
<Module test_scenarios.py>
|
||||||
<Class TestSampleWithScenarios>
|
<Class TestSampleWithScenarios>
|
||||||
<Function test_demo1[basic]>
|
<Function test_demo1[basic]>
|
||||||
|
@ -318,7 +318,7 @@ Let's first see how it looks like at collection time:
|
||||||
rootdir: /home/sweet/project
|
rootdir: /home/sweet/project
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
<Dir parametrize.rst-199>
|
<Dir parametrize.rst-200>
|
||||||
<Module test_backends.py>
|
<Module test_backends.py>
|
||||||
<Function test_db_initialized[d1]>
|
<Function test_db_initialized[d1]>
|
||||||
<Function test_db_initialized[d2]>
|
<Function test_db_initialized[d2]>
|
||||||
|
|
|
@ -152,7 +152,7 @@ The test collection would look like this:
|
||||||
configfile: pytest.ini
|
configfile: pytest.ini
|
||||||
collected 2 items
|
collected 2 items
|
||||||
|
|
||||||
<Dir pythoncollection.rst-200>
|
<Dir pythoncollection.rst-201>
|
||||||
<Module check_myapp.py>
|
<Module check_myapp.py>
|
||||||
<Class CheckMyApp>
|
<Class CheckMyApp>
|
||||||
<Function simple_check>
|
<Function simple_check>
|
||||||
|
@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this:
|
||||||
configfile: pytest.ini
|
configfile: pytest.ini
|
||||||
collected 3 items
|
collected 3 items
|
||||||
|
|
||||||
<Dir pythoncollection.rst-200>
|
<Dir pythoncollection.rst-201>
|
||||||
<Dir CWD>
|
<Dir CWD>
|
||||||
<Module pythoncollection.py>
|
<Module pythoncollection.py>
|
||||||
<Function test_function>
|
<Function test_function>
|
||||||
|
|
|
@ -18,7 +18,7 @@ System state
|
||||||
|
|
||||||
Broadly speaking, a flaky test indicates that the test relies on some system state that is not being appropriately controlled - the test environment is not sufficiently isolated. Higher level tests are more likely to be flaky as they rely on more state.
|
Broadly speaking, a flaky test indicates that the test relies on some system state that is not being appropriately controlled - the test environment is not sufficiently isolated. Higher level tests are more likely to be flaky as they rely on more state.
|
||||||
|
|
||||||
Flaky tests sometimes appear when a test suite is run in parallel (such as use of pytest-xdist). This can indicate a test is reliant on test ordering.
|
Flaky tests sometimes appear when a test suite is run in parallel (such as use of `pytest-xdist`_). This can indicate a test is reliant on test ordering.
|
||||||
|
|
||||||
- Perhaps a different test is failing to clean up after itself and leaving behind data which causes the flaky test to fail.
|
- Perhaps a different test is failing to clean up after itself and leaving behind data which causes the flaky test to fail.
|
||||||
- The flaky test is reliant on data from a previous test that doesn't clean up after itself, and in parallel runs that previous test is not always present
|
- The flaky test is reliant on data from a previous test that doesn't clean up after itself, and in parallel runs that previous test is not always present
|
||||||
|
@ -30,9 +30,22 @@ Overly strict assertion
|
||||||
|
|
||||||
Overly strict assertions can cause problems with floating point comparison as well as timing issues. :func:`pytest.approx` is useful here.
|
Overly strict assertions can cause problems with floating point comparison as well as timing issues. :func:`pytest.approx` is useful here.
|
||||||
|
|
||||||
|
Thread safety
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
Pytest features
|
pytest is single-threaded, executing its tests always in the same thread, sequentially, never spawning any threads itself.
|
||||||
^^^^^^^^^^^^^^^
|
|
||||||
|
Even in case of plugins which run tests in parallel, for example `pytest-xdist`_, usually work by spawning multiple *processes* and running tests in batches, without using multiple threads.
|
||||||
|
|
||||||
|
It is of course possible (and common) for tests and fixtures to spawn threads themselves as part of their testing workflow (for example, a fixture that starts a server thread in the background, or a test which executes production code that spawns threads), but some care must be taken:
|
||||||
|
|
||||||
|
* Make sure to eventually wait on any spawned threads -- for example at the end of a test, or during the teardown of a fixture.
|
||||||
|
* Avoid using primitives provided by pytest (:func:`pytest.warns`, :func:`pytest.raises`, etc) from multiple threads, as they are not thread-safe.
|
||||||
|
|
||||||
|
If your test suite uses threads and your are seeing flaky test results, do not discount the possibility that the test is implicitly using global state in pytest itself.
|
||||||
|
|
||||||
|
Related features
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
Xfail strict
|
Xfail strict
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
@ -123,3 +136,6 @@ Resources
|
||||||
|
|
||||||
* `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016
|
* `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016
|
||||||
* `Where do Google's flaky tests come from? <https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html>`_ by Jeff Listfield, 2017
|
* `Where do Google's flaky tests come from? <https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html>`_ by Jeff Listfield, 2017
|
||||||
|
|
||||||
|
|
||||||
|
.. _pytest-xdist: https://github.com/pytest-dev/pytest-xdist
|
||||||
|
|
|
@ -22,7 +22,7 @@ Install ``pytest``
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
$ pytest --version
|
$ pytest --version
|
||||||
pytest 8.2.1
|
pytest 8.2.2
|
||||||
|
|
||||||
.. _`simpletest`:
|
.. _`simpletest`:
|
||||||
|
|
||||||
|
|
|
@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used:
|
||||||
rootdir: /home/sweet/project
|
rootdir: /home/sweet/project
|
||||||
collected 12 items
|
collected 12 items
|
||||||
|
|
||||||
<Dir fixtures.rst-218>
|
<Dir fixtures.rst-219>
|
||||||
<Module test_anothersmtp.py>
|
<Module test_anothersmtp.py>
|
||||||
<Function test_showhelo[smtp.gmail.com]>
|
<Function test_showhelo[smtp.gmail.com]>
|
||||||
<Function test_showhelo[mail.python.org]>
|
<Function test_showhelo[mail.python.org]>
|
||||||
|
|
|
@ -4,11 +4,10 @@
|
||||||
|
|
||||||
.. sidebar:: **Next Open Trainings and Events**
|
.. sidebar:: **Next Open Trainings and Events**
|
||||||
|
|
||||||
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_ (3 day in-depth training):
|
- `pytest development sprint <https://github.com/pytest-dev/sprint>`_, **June 17th -- 22nd 2024**, Klaus (AT) / Remote
|
||||||
* **June 11th to 13th 2024**, Remote
|
- `pytest tips and tricks for a better testsuite <https://ep2024.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2024 <https://ep2024.europython.eu/>`_, **July 8th -- 14th 2024** (3h), Prague (CZ)
|
||||||
* **March 4th to 6th 2025**, Leipzig, Germany / Remote
|
- `pytest: Professionelles Testen (nicht nur) für Python <https://pretalx.com/workshoptage-2024/talk/9VUHYB/>`_, at `CH Open Workshoptage <https://workshoptage.ch/>`_, **September 2nd 2024**, HSLU Rotkreuz (CH)
|
||||||
- `pytest development sprint <https://github.com/pytest-dev/sprint>`_, **June 17th -- 22nd 2024**
|
- `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_ (3 day in-depth training), **March 4th -- 6th 2025**, Leipzig (DE) / Remote
|
||||||
- pytest tips and tricks for a better testsuite, `Europython 2024 <https://ep2024.europython.eu/>`_, **July 8th -- 14th 2024** (3h), Prague
|
|
||||||
|
|
||||||
Also see :doc:`previous talks and blogposts <talks>`
|
Also see :doc:`previous talks and blogposts <talks>`
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
@ -17,7 +18,7 @@ def get_issues():
|
||||||
if r.status_code == 403:
|
if r.status_code == 403:
|
||||||
# API request limit exceeded
|
# API request limit exceeded
|
||||||
print(data["message"])
|
print(data["message"])
|
||||||
exit(1)
|
sys.exit(1)
|
||||||
issues.extend(data)
|
issues.extend(data)
|
||||||
|
|
||||||
# Look for next page
|
# Look for next page
|
||||||
|
|
|
@ -181,28 +181,25 @@ disable = [
|
||||||
"bad-mcs-method-argument",
|
"bad-mcs-method-argument",
|
||||||
"broad-exception-caught",
|
"broad-exception-caught",
|
||||||
"broad-exception-raised",
|
"broad-exception-raised",
|
||||||
"cell-var-from-loop",
|
"cell-var-from-loop", # B023 from ruff / flake8-bugbear
|
||||||
"comparison-of-constants",
|
"comparison-of-constants",
|
||||||
"comparison-with-callable",
|
"comparison-with-callable",
|
||||||
"comparison-with-itself",
|
"comparison-with-itself",
|
||||||
"condition-evals-to-constant",
|
"condition-evals-to-constant",
|
||||||
"consider-using-dict-items",
|
"consider-using-dict-items",
|
||||||
"consider-using-enumerate",
|
|
||||||
"consider-using-from-import",
|
"consider-using-from-import",
|
||||||
"consider-using-f-string",
|
"consider-using-f-string",
|
||||||
"consider-using-in",
|
"consider-using-in",
|
||||||
"consider-using-sys-exit",
|
|
||||||
"consider-using-ternary",
|
"consider-using-ternary",
|
||||||
"consider-using-with",
|
"consider-using-with",
|
||||||
"cyclic-import",
|
"cyclic-import",
|
||||||
"disallowed-name",
|
"disallowed-name", # foo / bar are used often in tests
|
||||||
"duplicate-code",
|
"duplicate-code",
|
||||||
"eval-used",
|
"eval-used",
|
||||||
"exec-used",
|
"exec-used",
|
||||||
"expression-not-assigned",
|
"expression-not-assigned",
|
||||||
"fixme",
|
"fixme",
|
||||||
"global-statement",
|
"global-statement",
|
||||||
"implicit-str-concat",
|
|
||||||
"import-error",
|
"import-error",
|
||||||
"import-outside-toplevel",
|
"import-outside-toplevel",
|
||||||
"inconsistent-return-statements",
|
"inconsistent-return-statements",
|
||||||
|
@ -213,10 +210,9 @@ disable = [
|
||||||
"keyword-arg-before-vararg",
|
"keyword-arg-before-vararg",
|
||||||
"line-too-long",
|
"line-too-long",
|
||||||
"method-hidden",
|
"method-hidden",
|
||||||
"misplaced-bare-raise",
|
|
||||||
"missing-docstring",
|
"missing-docstring",
|
||||||
"missing-timeout",
|
"missing-timeout",
|
||||||
"multiple-statements",
|
"multiple-statements", # multiple-statements-on-one-line-colon (E701) from ruff
|
||||||
"no-else-break",
|
"no-else-break",
|
||||||
"no-else-continue",
|
"no-else-continue",
|
||||||
"no-else-raise",
|
"no-else-raise",
|
||||||
|
@ -229,6 +225,7 @@ disable = [
|
||||||
"pointless-exception-statement",
|
"pointless-exception-statement",
|
||||||
"pointless-statement",
|
"pointless-statement",
|
||||||
"pointless-string-statement",
|
"pointless-string-statement",
|
||||||
|
"possibly-used-before-assignment",
|
||||||
"protected-access",
|
"protected-access",
|
||||||
"raise-missing-from",
|
"raise-missing-from",
|
||||||
"redefined-argument-from-local",
|
"redefined-argument-from-local",
|
||||||
|
@ -276,7 +273,6 @@ disable = [
|
||||||
"useless-else-on-loop",
|
"useless-else-on-loop",
|
||||||
"useless-import-alias",
|
"useless-import-alias",
|
||||||
"useless-return",
|
"useless-return",
|
||||||
"use-maxsplit-arg",
|
|
||||||
"using-constant-test",
|
"using-constant-test",
|
||||||
"wrong-import-order",
|
"wrong-import-order",
|
||||||
]
|
]
|
||||||
|
|
|
@ -55,7 +55,7 @@ from _pytest.pathlib import bestrelpath
|
||||||
if sys.version_info < (3, 11):
|
if sys.version_info < (3, 11):
|
||||||
from exceptiongroup import BaseExceptionGroup
|
from exceptiongroup import BaseExceptionGroup
|
||||||
|
|
||||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||||
|
|
||||||
|
|
||||||
class Code:
|
class Code:
|
||||||
|
@ -199,8 +199,8 @@ class TracebackEntry:
|
||||||
rawentry: TracebackType,
|
rawentry: TracebackType,
|
||||||
repr_style: Optional['Literal["short", "long"]'] = None,
|
repr_style: Optional['Literal["short", "long"]'] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._rawentry: "Final" = rawentry
|
self._rawentry: Final = rawentry
|
||||||
self._repr_style: "Final" = repr_style
|
self._repr_style: Final = repr_style
|
||||||
|
|
||||||
def with_repr_style(
|
def with_repr_style(
|
||||||
self, repr_style: Optional['Literal["short", "long"]']
|
self, repr_style: Optional['Literal["short", "long"]']
|
||||||
|
@ -628,13 +628,14 @@ class ExceptionInfo(Generic[E]):
|
||||||
def getrepr(
|
def getrepr(
|
||||||
self,
|
self,
|
||||||
showlocals: bool = False,
|
showlocals: bool = False,
|
||||||
style: _TracebackStyle = "long",
|
style: TracebackStyle = "long",
|
||||||
abspath: bool = False,
|
abspath: bool = False,
|
||||||
tbfilter: Union[
|
tbfilter: Union[
|
||||||
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
||||||
] = True,
|
] = True,
|
||||||
funcargs: bool = False,
|
funcargs: bool = False,
|
||||||
truncate_locals: bool = True,
|
truncate_locals: bool = True,
|
||||||
|
truncate_args: bool = True,
|
||||||
chain: bool = True,
|
chain: bool = True,
|
||||||
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
|
) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]:
|
||||||
"""Return str()able representation of this exception info.
|
"""Return str()able representation of this exception info.
|
||||||
|
@ -665,6 +666,9 @@ class ExceptionInfo(Generic[E]):
|
||||||
:param bool truncate_locals:
|
:param bool truncate_locals:
|
||||||
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
With ``showlocals==True``, make sure locals can be safely represented as strings.
|
||||||
|
|
||||||
|
:param bool truncate_args:
|
||||||
|
With ``showargs==True``, make sure args can be safely represented as strings.
|
||||||
|
|
||||||
:param bool chain:
|
:param bool chain:
|
||||||
If chained exceptions in Python 3 should be shown.
|
If chained exceptions in Python 3 should be shown.
|
||||||
|
|
||||||
|
@ -691,6 +695,7 @@ class ExceptionInfo(Generic[E]):
|
||||||
tbfilter=tbfilter,
|
tbfilter=tbfilter,
|
||||||
funcargs=funcargs,
|
funcargs=funcargs,
|
||||||
truncate_locals=truncate_locals,
|
truncate_locals=truncate_locals,
|
||||||
|
truncate_args=truncate_args,
|
||||||
chain=chain,
|
chain=chain,
|
||||||
)
|
)
|
||||||
return fmt.repr_excinfo(self)
|
return fmt.repr_excinfo(self)
|
||||||
|
@ -804,11 +809,12 @@ class FormattedExcinfo:
|
||||||
fail_marker: ClassVar = "E"
|
fail_marker: ClassVar = "E"
|
||||||
|
|
||||||
showlocals: bool = False
|
showlocals: bool = False
|
||||||
style: _TracebackStyle = "long"
|
style: TracebackStyle = "long"
|
||||||
abspath: bool = True
|
abspath: bool = True
|
||||||
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
||||||
funcargs: bool = False
|
funcargs: bool = False
|
||||||
truncate_locals: bool = True
|
truncate_locals: bool = True
|
||||||
|
truncate_args: bool = True
|
||||||
chain: bool = True
|
chain: bool = True
|
||||||
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
|
astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field(
|
||||||
default_factory=dict, init=False, repr=False
|
default_factory=dict, init=False, repr=False
|
||||||
|
@ -839,7 +845,11 @@ class FormattedExcinfo:
|
||||||
if self.funcargs:
|
if self.funcargs:
|
||||||
args = []
|
args = []
|
||||||
for argname, argvalue in entry.frame.getargs(var=True):
|
for argname, argvalue in entry.frame.getargs(var=True):
|
||||||
args.append((argname, saferepr(argvalue)))
|
if self.truncate_args:
|
||||||
|
str_repr = saferepr(argvalue)
|
||||||
|
else:
|
||||||
|
str_repr = saferepr(argvalue, maxsize=None)
|
||||||
|
args.append((argname, str_repr))
|
||||||
return ReprFuncArgs(args)
|
return ReprFuncArgs(args)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1164,7 +1174,7 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]]
|
||||||
extraline: Optional[str]
|
extraline: Optional[str]
|
||||||
style: _TracebackStyle
|
style: TracebackStyle
|
||||||
|
|
||||||
entrysep: ClassVar = "_ "
|
entrysep: ClassVar = "_ "
|
||||||
|
|
||||||
|
@ -1198,7 +1208,7 @@ class ReprTracebackNative(ReprTraceback):
|
||||||
class ReprEntryNative(TerminalRepr):
|
class ReprEntryNative(TerminalRepr):
|
||||||
lines: Sequence[str]
|
lines: Sequence[str]
|
||||||
|
|
||||||
style: ClassVar[_TracebackStyle] = "native"
|
style: ClassVar[TracebackStyle] = "native"
|
||||||
|
|
||||||
def toterminal(self, tw: TerminalWriter) -> None:
|
def toterminal(self, tw: TerminalWriter) -> None:
|
||||||
tw.write("".join(self.lines))
|
tw.write("".join(self.lines))
|
||||||
|
@ -1210,7 +1220,7 @@ class ReprEntry(TerminalRepr):
|
||||||
reprfuncargs: Optional["ReprFuncArgs"]
|
reprfuncargs: Optional["ReprFuncArgs"]
|
||||||
reprlocals: Optional["ReprLocals"]
|
reprlocals: Optional["ReprLocals"]
|
||||||
reprfileloc: Optional["ReprFileLocation"]
|
reprfileloc: Optional["ReprFileLocation"]
|
||||||
style: _TracebackStyle
|
style: TracebackStyle
|
||||||
|
|
||||||
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
def _write_entry_lines(self, tw: TerminalWriter) -> None:
|
||||||
"""Write the source code portions of a list of traceback entries with syntax highlighting.
|
"""Write the source code portions of a list of traceback entries with syntax highlighting.
|
||||||
|
|
|
@ -161,15 +161,13 @@ class Visitor:
|
||||||
)
|
)
|
||||||
if not self.breadthfirst:
|
if not self.breadthfirst:
|
||||||
for subdir in dirs:
|
for subdir in dirs:
|
||||||
for p in self.gen(subdir):
|
yield from self.gen(subdir)
|
||||||
yield p
|
|
||||||
for p in self.optsort(entries):
|
for p in self.optsort(entries):
|
||||||
if self.fil is None or self.fil(p):
|
if self.fil is None or self.fil(p):
|
||||||
yield p
|
yield p
|
||||||
if self.breadthfirst:
|
if self.breadthfirst:
|
||||||
for subdir in dirs:
|
for subdir in dirs:
|
||||||
for p in self.gen(subdir):
|
yield from self.gen(subdir)
|
||||||
yield p
|
|
||||||
|
|
||||||
|
|
||||||
class FNMatcher:
|
class FNMatcher:
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# This plugin was not named "cache" to avoid conflicts with the external
|
# This plugin was not named "cache" to avoid conflicts with the external
|
||||||
# pytest-cache version.
|
# pytest-cache version.
|
||||||
import dataclasses
|
import dataclasses
|
||||||
|
import errno
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
@ -227,13 +228,23 @@ class Cache:
|
||||||
with open(path.joinpath("CACHEDIR.TAG"), "xb") as f:
|
with open(path.joinpath("CACHEDIR.TAG"), "xb") as f:
|
||||||
f.write(CACHEDIR_TAG_CONTENT)
|
f.write(CACHEDIR_TAG_CONTENT)
|
||||||
|
|
||||||
|
try:
|
||||||
path.rename(self._cachedir)
|
path.rename(self._cachedir)
|
||||||
# Create a directory in place of the one we just moved so that `TemporaryDirectory`'s
|
except OSError as e:
|
||||||
# cleanup doesn't complain.
|
# If 2 concurrent pytests both race to the rename, the loser
|
||||||
|
# gets "Directory not empty" from the rename. In this case,
|
||||||
|
# everything is handled so just continue (while letting the
|
||||||
|
# temporary directory be cleaned up).
|
||||||
|
if e.errno != errno.ENOTEMPTY:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
# Create a directory in place of the one we just moved so that
|
||||||
|
# `TemporaryDirectory`'s cleanup doesn't complain.
|
||||||
#
|
#
|
||||||
# TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10. See
|
# TODO: pass ignore_cleanup_errors=True when we no longer support python < 3.10.
|
||||||
# https://github.com/python/cpython/issues/74168. Note that passing delete=False would
|
# See https://github.com/python/cpython/issues/74168. Note that passing
|
||||||
# do the wrong thing in case of errors and isn't supported until python 3.12.
|
# delete=False would do the wrong thing in case of errors and isn't supported
|
||||||
|
# until python 3.12.
|
||||||
path.mkdir()
|
path.mkdir()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,10 @@ from _pytest import __version__
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
from _pytest._code import filter_traceback
|
from _pytest._code import filter_traceback
|
||||||
|
from _pytest._code.code import TracebackStyle
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
|
from _pytest.config.argparsing import Argument
|
||||||
|
from _pytest.config.argparsing import Parser
|
||||||
import _pytest.deprecated
|
import _pytest.deprecated
|
||||||
import _pytest.hookspec
|
import _pytest.hookspec
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
@ -71,9 +74,7 @@ from _pytest.warning_types import warn_explicit_for
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .argparsing import Argument
|
from _pytest.cacheprovider import Cache
|
||||||
from .argparsing import Parser
|
|
||||||
from _pytest._code.code import _TracebackStyle
|
|
||||||
from _pytest.terminal import TerminalReporter
|
from _pytest.terminal import TerminalReporter
|
||||||
|
|
||||||
|
|
||||||
|
@ -1030,6 +1031,9 @@ class Config:
|
||||||
#: 'testpaths' configuration value.
|
#: 'testpaths' configuration value.
|
||||||
TESTPATHS = enum.auto()
|
TESTPATHS = enum.auto()
|
||||||
|
|
||||||
|
# Set by cacheprovider plugin.
|
||||||
|
cache: Optional["Cache"]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
pluginmanager: PytestPluginManager,
|
pluginmanager: PytestPluginManager,
|
||||||
|
@ -1091,11 +1095,6 @@ class Config:
|
||||||
self.args_source = Config.ArgsSource.ARGS
|
self.args_source = Config.ArgsSource.ARGS
|
||||||
self.args: List[str] = []
|
self.args: List[str] = []
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from _pytest.cacheprovider import Cache
|
|
||||||
|
|
||||||
self.cache: Optional[Cache] = None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rootpath(self) -> Path:
|
def rootpath(self) -> Path:
|
||||||
"""The path to the :ref:`rootdir <rootdir>`.
|
"""The path to the :ref:`rootdir <rootdir>`.
|
||||||
|
@ -1175,7 +1174,7 @@ class Config:
|
||||||
option: Optional[argparse.Namespace] = None,
|
option: Optional[argparse.Namespace] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
if option and getattr(option, "fulltrace", False):
|
if option and getattr(option, "fulltrace", False):
|
||||||
style: _TracebackStyle = "long"
|
style: TracebackStyle = "long"
|
||||||
else:
|
else:
|
||||||
style = "native"
|
style = "native"
|
||||||
excrepr = excinfo.getrepr(
|
excrepr = excinfo.getrepr(
|
||||||
|
@ -1913,7 +1912,7 @@ def parse_warning_filter(
|
||||||
parts.append("")
|
parts.append("")
|
||||||
action_, message, category_, module, lineno_ = (s.strip() for s in parts)
|
action_, message, category_, module, lineno_ = (s.strip() for s in parts)
|
||||||
try:
|
try:
|
||||||
action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined]
|
action: warnings._ActionKind = warnings._getaction(action_) # type: ignore[attr-defined]
|
||||||
except warnings._OptionError as e:
|
except warnings._OptionError as e:
|
||||||
raise UsageError(error_template.format(error=str(e))) from None
|
raise UsageError(error_template.format(error=str(e))) from None
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -13,12 +13,12 @@ from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
from typing import Type
|
from typing import Type
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
from typing import Union
|
from typing import Union
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from _pytest import outcomes
|
from _pytest import outcomes
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
|
from _pytest.capture import CaptureManager
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import ConftestImportFailure
|
from _pytest.config import ConftestImportFailure
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
|
@ -27,10 +27,6 @@ from _pytest.config.argparsing import Parser
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
from _pytest.nodes import Node
|
from _pytest.nodes import Node
|
||||||
from _pytest.reports import BaseReport
|
from _pytest.reports import BaseReport
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from _pytest.capture import CaptureManager
|
|
||||||
from _pytest.runner import CallInfo
|
from _pytest.runner import CallInfo
|
||||||
|
|
||||||
|
|
||||||
|
@ -310,7 +306,7 @@ class PdbTrace:
|
||||||
return (yield)
|
return (yield)
|
||||||
|
|
||||||
|
|
||||||
def wrap_pytest_function_for_tracing(pyfuncitem):
|
def wrap_pytest_function_for_tracing(pyfuncitem) -> None:
|
||||||
"""Change the Python function object of the given Function item by a
|
"""Change the Python function object of the given Function item by a
|
||||||
wrapper which actually enters pdb before calling the python function
|
wrapper which actually enters pdb before calling the python function
|
||||||
itself, effectively leaving the user in the pdb prompt in the first
|
itself, effectively leaving the user in the pdb prompt in the first
|
||||||
|
@ -322,14 +318,14 @@ def wrap_pytest_function_for_tracing(pyfuncitem):
|
||||||
# python < 3.7.4) runcall's first param is `func`, which means we'd get
|
# python < 3.7.4) runcall's first param is `func`, which means we'd get
|
||||||
# an exception if one of the kwargs to testfunction was called `func`.
|
# an exception if one of the kwargs to testfunction was called `func`.
|
||||||
@functools.wraps(testfunction)
|
@functools.wraps(testfunction)
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs) -> None:
|
||||||
func = functools.partial(testfunction, *args, **kwargs)
|
func = functools.partial(testfunction, *args, **kwargs)
|
||||||
_pdb.runcall(func)
|
_pdb.runcall(func)
|
||||||
|
|
||||||
pyfuncitem.obj = wrapper
|
pyfuncitem.obj = wrapper
|
||||||
|
|
||||||
|
|
||||||
def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
|
def maybe_wrap_pytest_function_for_tracing(pyfuncitem) -> None:
|
||||||
"""Wrap the given pytestfunct item for tracing support if --trace was given in
|
"""Wrap the given pytestfunct item for tracing support if --trace was given in
|
||||||
the command line."""
|
the command line."""
|
||||||
if pyfuncitem.config.getvalue("trace"):
|
if pyfuncitem.config.getvalue("trace"):
|
||||||
|
|
|
@ -298,7 +298,7 @@ class DoctestItem(Item):
|
||||||
def runtest(self) -> None:
|
def runtest(self) -> None:
|
||||||
_check_all_skipped(self.dtest)
|
_check_all_skipped(self.dtest)
|
||||||
self._disable_output_capturing_for_darwin()
|
self._disable_output_capturing_for_darwin()
|
||||||
failures: List["doctest.DocTestFailure"] = []
|
failures: List[doctest.DocTestFailure] = []
|
||||||
# Type ignored because we change the type of `out` from what
|
# Type ignored because we change the type of `out` from what
|
||||||
# doctest expects.
|
# doctest expects.
|
||||||
self.runner.run(self.dtest, out=failures) # type: ignore[arg-type]
|
self.runner.run(self.dtest, out=failures) # type: ignore[arg-type]
|
||||||
|
@ -505,18 +505,14 @@ class DoctestModule(Module):
|
||||||
import doctest
|
import doctest
|
||||||
|
|
||||||
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
class MockAwareDocTestFinder(doctest.DocTestFinder):
|
||||||
"""A hackish doctest finder that overrides stdlib internals to fix a stdlib bug.
|
if sys.version_info < (3, 11):
|
||||||
|
|
||||||
https://github.com/pytest-dev/pytest/issues/3456
|
|
||||||
https://bugs.python.org/issue25532
|
|
||||||
"""
|
|
||||||
|
|
||||||
def _find_lineno(self, obj, source_lines):
|
def _find_lineno(self, obj, source_lines):
|
||||||
"""Doctest code does not take into account `@property`, this
|
"""On older Pythons, doctest code does not take into account
|
||||||
is a hackish way to fix it. https://bugs.python.org/issue17446
|
`@property`. https://github.com/python/cpython/issues/61648
|
||||||
|
|
||||||
Wrapped Doctests will need to be unwrapped so the correct
|
Moreover, wrapped Doctests need to be unwrapped so the correct
|
||||||
line number is returned. This will be reported upstream. #8796
|
line number is returned. #8796
|
||||||
"""
|
"""
|
||||||
if isinstance(obj, property):
|
if isinstance(obj, property):
|
||||||
obj = getattr(obj, "fget", obj)
|
obj = getattr(obj, "fget", obj)
|
||||||
|
@ -531,11 +527,18 @@ class DoctestModule(Module):
|
||||||
source_lines,
|
source_lines,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sys.version_info < (3, 10):
|
||||||
|
|
||||||
def _find(
|
def _find(
|
||||||
self, tests, obj, name, module, source_lines, globs, seen
|
self, tests, obj, name, module, source_lines, globs, seen
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""Override _find to work around issue in stdlib.
|
||||||
|
|
||||||
|
https://github.com/pytest-dev/pytest/issues/3456
|
||||||
|
https://github.com/python/cpython/issues/69718
|
||||||
|
"""
|
||||||
if _is_mocked(obj):
|
if _is_mocked(obj):
|
||||||
return
|
return # pragma: no cover
|
||||||
with _patch_unwrap_mock_aware():
|
with _patch_unwrap_mock_aware():
|
||||||
# Type ignored because this is a private function.
|
# Type ignored because this is a private function.
|
||||||
super()._find( # type:ignore[misc]
|
super()._find( # type:ignore[misc]
|
||||||
|
@ -556,9 +559,6 @@ class DoctestModule(Module):
|
||||||
# Type ignored because this is a private function.
|
# Type ignored because this is a private function.
|
||||||
return super()._from_module(module, object) # type: ignore[misc]
|
return super()._from_module(module, object) # type: ignore[misc]
|
||||||
|
|
||||||
else: # pragma: no cover
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
module = self.obj
|
module = self.obj
|
||||||
except Collector.CollectError:
|
except Collector.CollectError:
|
||||||
|
|
|
@ -21,9 +21,11 @@ from typing import Generic
|
||||||
from typing import Iterable
|
from typing import Iterable
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from typing import Mapping
|
||||||
from typing import MutableMapping
|
from typing import MutableMapping
|
||||||
from typing import NoReturn
|
from typing import NoReturn
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
from typing import OrderedDict
|
||||||
from typing import overload
|
from typing import overload
|
||||||
from typing import Sequence
|
from typing import Sequence
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
@ -58,6 +60,7 @@ from _pytest.config.argparsing import Parser
|
||||||
from _pytest.deprecated import check_ispytest
|
from _pytest.deprecated import check_ispytest
|
||||||
from _pytest.deprecated import MARKED_FIXTURE
|
from _pytest.deprecated import MARKED_FIXTURE
|
||||||
from _pytest.deprecated import YIELD_FIXTURE
|
from _pytest.deprecated import YIELD_FIXTURE
|
||||||
|
from _pytest.main import Session
|
||||||
from _pytest.mark import Mark
|
from _pytest.mark import Mark
|
||||||
from _pytest.mark import ParameterSet
|
from _pytest.mark import ParameterSet
|
||||||
from _pytest.mark.structures import MarkDecorator
|
from _pytest.mark.structures import MarkDecorator
|
||||||
|
@ -76,9 +79,6 @@ if sys.version_info < (3, 11):
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Deque
|
|
||||||
|
|
||||||
from _pytest.main import Session
|
|
||||||
from _pytest.python import CallSpec2
|
from _pytest.python import CallSpec2
|
||||||
from _pytest.python import Function
|
from _pytest.python import Function
|
||||||
from _pytest.python import Metafunc
|
from _pytest.python import Metafunc
|
||||||
|
@ -161,6 +161,12 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# Algorithm for sorting on a per-parametrized resource setup basis.
|
||||||
|
# It is called for Session scope first and performs sorting
|
||||||
|
# down to the lower scopes such as to minimize number of "high scope"
|
||||||
|
# setups and teardowns.
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class FixtureArgKey:
|
class FixtureArgKey:
|
||||||
argname: str
|
argname: str
|
||||||
|
@ -169,19 +175,21 @@ class FixtureArgKey:
|
||||||
item_cls: Optional[type]
|
item_cls: Optional[type]
|
||||||
|
|
||||||
|
|
||||||
def get_parametrized_fixture_keys(
|
_V = TypeVar("_V")
|
||||||
|
OrderedSet = Dict[_V, None]
|
||||||
|
|
||||||
|
|
||||||
|
def get_parametrized_fixture_argkeys(
|
||||||
item: nodes.Item, scope: Scope
|
item: nodes.Item, scope: Scope
|
||||||
) -> Iterator[FixtureArgKey]:
|
) -> Iterator[FixtureArgKey]:
|
||||||
"""Return list of keys for all parametrized arguments which match
|
"""Return list of keys for all parametrized arguments which match
|
||||||
the specified scope."""
|
the specified scope."""
|
||||||
assert scope is not Scope.Function
|
assert scope is not Scope.Function
|
||||||
|
|
||||||
try:
|
try:
|
||||||
callspec: CallSpec2 = item.callspec # type: ignore[attr-defined]
|
callspec: CallSpec2 = item.callspec # type: ignore[attr-defined]
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return
|
return
|
||||||
for argname in callspec.indices:
|
|
||||||
if callspec._arg2scope[argname] != scope:
|
|
||||||
continue
|
|
||||||
|
|
||||||
item_cls = None
|
item_cls = None
|
||||||
if scope is Scope.Session:
|
if scope is Scope.Session:
|
||||||
|
@ -197,69 +205,65 @@ def get_parametrized_fixture_keys(
|
||||||
else:
|
else:
|
||||||
assert_never(scope)
|
assert_never(scope)
|
||||||
|
|
||||||
|
for argname in callspec.indices:
|
||||||
|
if callspec._arg2scope[argname] != scope:
|
||||||
|
continue
|
||||||
param_index = callspec.indices[argname]
|
param_index = callspec.indices[argname]
|
||||||
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
|
yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls)
|
||||||
|
|
||||||
|
|
||||||
# Algorithm for sorting on a per-parametrized resource setup basis.
|
|
||||||
# It is called for Session scope first and performs sorting
|
|
||||||
# down to the lower scopes such as to minimize number of "high scope"
|
|
||||||
# setups and teardowns.
|
|
||||||
|
|
||||||
|
|
||||||
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]:
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]] = {}
|
argkeys_by_item: Dict[Scope, Dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {}
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, Deque[nodes.Item]]] = {}
|
items_by_argkey: Dict[
|
||||||
|
Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]]
|
||||||
|
] = {}
|
||||||
for scope in HIGH_SCOPES:
|
for scope in HIGH_SCOPES:
|
||||||
scoped_argkeys_cache = argkeys_cache[scope] = {}
|
scoped_argkeys_by_item = argkeys_by_item[scope] = {}
|
||||||
scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(deque)
|
scoped_items_by_argkey = items_by_argkey[scope] = defaultdict(OrderedDict)
|
||||||
for item in items:
|
for item in items:
|
||||||
keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None)
|
argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope))
|
||||||
if keys:
|
if argkeys:
|
||||||
scoped_argkeys_cache[item] = keys
|
scoped_argkeys_by_item[item] = argkeys
|
||||||
for key in keys:
|
for argkey in argkeys:
|
||||||
scoped_items_by_argkey[key].append(item)
|
scoped_items_by_argkey[argkey][item] = None
|
||||||
items_dict = dict.fromkeys(items, None)
|
|
||||||
|
items_set = dict.fromkeys(items)
|
||||||
return list(
|
return list(
|
||||||
reorder_items_atscope(items_dict, argkeys_cache, items_by_argkey, Scope.Session)
|
reorder_items_atscope(
|
||||||
|
items_set, argkeys_by_item, items_by_argkey, Scope.Session
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def fix_cache_order(
|
|
||||||
item: nodes.Item,
|
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
|
||||||
) -> None:
|
|
||||||
for scope in HIGH_SCOPES:
|
|
||||||
for key in argkeys_cache[scope].get(item, []):
|
|
||||||
items_by_argkey[scope][key].appendleft(item)
|
|
||||||
|
|
||||||
|
|
||||||
def reorder_items_atscope(
|
def reorder_items_atscope(
|
||||||
items: Dict[nodes.Item, None],
|
items: OrderedSet[nodes.Item],
|
||||||
argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]],
|
argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]],
|
||||||
items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]],
|
items_by_argkey: Mapping[
|
||||||
|
Scope, Mapping[FixtureArgKey, OrderedDict[nodes.Item, None]]
|
||||||
|
],
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
) -> Dict[nodes.Item, None]:
|
) -> OrderedSet[nodes.Item]:
|
||||||
if scope is Scope.Function or len(items) < 3:
|
if scope is Scope.Function or len(items) < 3:
|
||||||
return items
|
return items
|
||||||
ignore: Set[Optional[FixtureArgKey]] = set()
|
|
||||||
items_deque = deque(items)
|
|
||||||
items_done: Dict[nodes.Item, None] = {}
|
|
||||||
scoped_items_by_argkey = items_by_argkey[scope]
|
scoped_items_by_argkey = items_by_argkey[scope]
|
||||||
scoped_argkeys_cache = argkeys_cache[scope]
|
scoped_argkeys_by_item = argkeys_by_item[scope]
|
||||||
|
|
||||||
|
ignore: Set[FixtureArgKey] = set()
|
||||||
|
items_deque = deque(items)
|
||||||
|
items_done: OrderedSet[nodes.Item] = {}
|
||||||
while items_deque:
|
while items_deque:
|
||||||
no_argkey_group: Dict[nodes.Item, None] = {}
|
no_argkey_items: OrderedSet[nodes.Item] = {}
|
||||||
slicing_argkey = None
|
slicing_argkey = None
|
||||||
while items_deque:
|
while items_deque:
|
||||||
item = items_deque.popleft()
|
item = items_deque.popleft()
|
||||||
if item in items_done or item in no_argkey_group:
|
if item in items_done or item in no_argkey_items:
|
||||||
continue
|
continue
|
||||||
argkeys = dict.fromkeys(
|
argkeys = dict.fromkeys(
|
||||||
(k for k in scoped_argkeys_cache.get(item, []) if k not in ignore), None
|
k for k in scoped_argkeys_by_item.get(item, ()) if k not in ignore
|
||||||
)
|
)
|
||||||
if not argkeys:
|
if not argkeys:
|
||||||
no_argkey_group[item] = None
|
no_argkey_items[item] = None
|
||||||
else:
|
else:
|
||||||
slicing_argkey, _ = argkeys.popitem()
|
slicing_argkey, _ = argkeys.popitem()
|
||||||
# We don't have to remove relevant items from later in the
|
# We don't have to remove relevant items from later in the
|
||||||
|
@ -268,15 +272,22 @@ def reorder_items_atscope(
|
||||||
i for i in scoped_items_by_argkey[slicing_argkey] if i in items
|
i for i in scoped_items_by_argkey[slicing_argkey] if i in items
|
||||||
]
|
]
|
||||||
for i in reversed(matching_items):
|
for i in reversed(matching_items):
|
||||||
fix_cache_order(i, argkeys_cache, items_by_argkey)
|
|
||||||
items_deque.appendleft(i)
|
items_deque.appendleft(i)
|
||||||
break
|
# Fix items_by_argkey order.
|
||||||
if no_argkey_group:
|
for other_scope in HIGH_SCOPES:
|
||||||
no_argkey_group = reorder_items_atscope(
|
other_scoped_items_by_argkey = items_by_argkey[other_scope]
|
||||||
no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower()
|
for argkey in argkeys_by_item[other_scope].get(i, ()):
|
||||||
|
other_scoped_items_by_argkey[argkey][i] = None
|
||||||
|
other_scoped_items_by_argkey[argkey].move_to_end(
|
||||||
|
i, last=False
|
||||||
)
|
)
|
||||||
for item in no_argkey_group:
|
break
|
||||||
items_done[item] = None
|
if no_argkey_items:
|
||||||
|
reordered_no_argkey_items = reorder_items_atscope(
|
||||||
|
no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower()
|
||||||
|
)
|
||||||
|
items_done.update(reordered_no_argkey_items)
|
||||||
|
if slicing_argkey is not None:
|
||||||
ignore.add(slicing_argkey)
|
ignore.add(slicing_argkey)
|
||||||
return items_done
|
return items_done
|
||||||
|
|
||||||
|
@ -1627,7 +1638,7 @@ class FixtureManager:
|
||||||
func: "_FixtureFunc[object]",
|
func: "_FixtureFunc[object]",
|
||||||
nodeid: Optional[str],
|
nodeid: Optional[str],
|
||||||
scope: Union[
|
scope: Union[
|
||||||
Scope, _ScopeName, Callable[[str, Config], _ScopeName], None
|
Scope, _ScopeName, Callable[[str, Config], _ScopeName]
|
||||||
] = "function",
|
] = "function",
|
||||||
params: Optional[Sequence[object]] = None,
|
params: Optional[Sequence[object]] = None,
|
||||||
ids: Optional[
|
ids: Optional[
|
||||||
|
@ -1823,7 +1834,10 @@ def _show_fixtures_per_test(config: Config, session: "Session") -> None:
|
||||||
fixture_doc = inspect.getdoc(fixture_def.func)
|
fixture_doc = inspect.getdoc(fixture_def.func)
|
||||||
if fixture_doc:
|
if fixture_doc:
|
||||||
write_docstring(
|
write_docstring(
|
||||||
tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
|
tw,
|
||||||
|
fixture_doc.split("\n\n", maxsplit=1)[0]
|
||||||
|
if verbose <= 0
|
||||||
|
else fixture_doc,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
tw.line(" no docstring available", red=True)
|
tw.line(" no docstring available", red=True)
|
||||||
|
@ -1905,7 +1919,9 @@ def _showfixtures_main(config: Config, session: "Session") -> None:
|
||||||
tw.write("\n")
|
tw.write("\n")
|
||||||
doc = inspect.getdoc(fixturedef.func)
|
doc = inspect.getdoc(fixturedef.func)
|
||||||
if doc:
|
if doc:
|
||||||
write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
|
write_docstring(
|
||||||
|
tw, doc.split("\n\n", maxsplit=1)[0] if verbose <= 0 else doc
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
tw.line(" no docstring available", red=True)
|
tw.line(" no docstring available", red=True)
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
|
@ -311,7 +311,12 @@ def pytest_collection_finish(session: "Session") -> None:
|
||||||
def pytest_ignore_collect(
|
def pytest_ignore_collect(
|
||||||
collection_path: Path, path: "LEGACY_PATH", config: "Config"
|
collection_path: Path, path: "LEGACY_PATH", config: "Config"
|
||||||
) -> Optional[bool]:
|
) -> Optional[bool]:
|
||||||
"""Return True to prevent considering this path for collection.
|
"""Return ``True`` to ignore this path for collection.
|
||||||
|
|
||||||
|
Return ``None`` to let other plugins ignore the path for collection.
|
||||||
|
|
||||||
|
Returning ``False`` will forcefully *not* ignore this path for collection,
|
||||||
|
without giving a chance for other plugins to ignore this path.
|
||||||
|
|
||||||
This hook is consulted for all files and directories prior to calling
|
This hook is consulted for all files and directories prior to calling
|
||||||
more specific hooks.
|
more specific hooks.
|
||||||
|
|
|
@ -401,7 +401,7 @@ class LogCaptureHandler(logging_StreamHandler):
|
||||||
# The default behavior of logging is to print "Logging error"
|
# The default behavior of logging is to print "Logging error"
|
||||||
# to stderr with the call stack and some extra details.
|
# to stderr with the call stack and some extra details.
|
||||||
# pytest wants to make such mistakes visible during testing.
|
# pytest wants to make such mistakes visible during testing.
|
||||||
raise
|
raise # pylint: disable=misplaced-bare-raise
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
|
|
|
@ -38,7 +38,6 @@ from _pytest.config import PytestPluginManager
|
||||||
from _pytest.config import UsageError
|
from _pytest.config import UsageError
|
||||||
from _pytest.config.argparsing import Parser
|
from _pytest.config.argparsing import Parser
|
||||||
from _pytest.config.compat import PathAwareHookProxy
|
from _pytest.config.compat import PathAwareHookProxy
|
||||||
from _pytest.fixtures import FixtureManager
|
|
||||||
from _pytest.outcomes import exit
|
from _pytest.outcomes import exit
|
||||||
from _pytest.pathlib import absolutepath
|
from _pytest.pathlib import absolutepath
|
||||||
from _pytest.pathlib import bestrelpath
|
from _pytest.pathlib import bestrelpath
|
||||||
|
@ -55,6 +54,8 @@ from _pytest.warning_types import PytestWarning
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
|
from _pytest.fixtures import FixtureManager
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(parser: Parser) -> None:
|
def pytest_addoption(parser: Parser) -> None:
|
||||||
parser.addini(
|
parser.addini(
|
||||||
|
@ -551,7 +552,7 @@ class Session(nodes.Collector):
|
||||||
# Set on the session by runner.pytest_sessionstart.
|
# Set on the session by runner.pytest_sessionstart.
|
||||||
_setupstate: SetupState
|
_setupstate: SetupState
|
||||||
# Set on the session by fixtures.pytest_sessionstart.
|
# Set on the session by fixtures.pytest_sessionstart.
|
||||||
_fixturemanager: FixtureManager
|
_fixturemanager: "FixtureManager"
|
||||||
exitstatus: Union[int, ExitCode]
|
exitstatus: Union[int, ExitCode]
|
||||||
|
|
||||||
def __init__(self, config: Config) -> None:
|
def __init__(self, config: Config) -> None:
|
||||||
|
|
|
@ -31,6 +31,7 @@ from _pytest.config import Config
|
||||||
from _pytest.deprecated import check_ispytest
|
from _pytest.deprecated import check_ispytest
|
||||||
from _pytest.deprecated import MARKED_FIXTURE
|
from _pytest.deprecated import MARKED_FIXTURE
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
|
from _pytest.scope import _ScopeName
|
||||||
from _pytest.warning_types import PytestUnknownMarkWarning
|
from _pytest.warning_types import PytestUnknownMarkWarning
|
||||||
|
|
||||||
|
|
||||||
|
@ -430,7 +431,6 @@ def store_mark(obj, mark: Mark, *, stacklevel: int = 2) -> None:
|
||||||
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
|
# Typing for builtin pytest marks. This is cheating; it gives builtin marks
|
||||||
# special privilege, and breaks modularity. But practicality beats purity...
|
# special privilege, and breaks modularity. But practicality beats purity...
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest.scope import _ScopeName
|
|
||||||
|
|
||||||
class _SkipMarkDecorator(MarkDecorator):
|
class _SkipMarkDecorator(MarkDecorator):
|
||||||
@overload # type: ignore[override,no-overload-impl]
|
@overload # type: ignore[override,no-overload-impl]
|
||||||
|
|
|
@ -30,6 +30,7 @@ from _pytest._code import getfslineno
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
from _pytest._code.code import Traceback
|
from _pytest._code.code import Traceback
|
||||||
|
from _pytest._code.code import TracebackStyle
|
||||||
from _pytest.compat import LEGACY_PATH
|
from _pytest.compat import LEGACY_PATH
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import ConftestImportFailure
|
from _pytest.config import ConftestImportFailure
|
||||||
|
@ -49,7 +50,6 @@ if TYPE_CHECKING:
|
||||||
from typing import Self
|
from typing import Self
|
||||||
|
|
||||||
# Imported here due to circular import.
|
# Imported here due to circular import.
|
||||||
from _pytest._code.code import _TracebackStyle
|
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
|
|
||||||
|
|
||||||
|
@ -416,7 +416,7 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||||
def _repr_failure_py(
|
def _repr_failure_py(
|
||||||
self,
|
self,
|
||||||
excinfo: ExceptionInfo[BaseException],
|
excinfo: ExceptionInfo[BaseException],
|
||||||
style: "Optional[_TracebackStyle]" = None,
|
style: "Optional[TracebackStyle]" = None,
|
||||||
) -> TerminalRepr:
|
) -> TerminalRepr:
|
||||||
from _pytest.fixtures import FixtureLookupError
|
from _pytest.fixtures import FixtureLookupError
|
||||||
|
|
||||||
|
@ -448,6 +448,8 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||||
else:
|
else:
|
||||||
truncate_locals = True
|
truncate_locals = True
|
||||||
|
|
||||||
|
truncate_args = False if self.config.getoption("verbose", 0) > 2 else True
|
||||||
|
|
||||||
# excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.
|
# excinfo.getrepr() formats paths relative to the CWD if `abspath` is False.
|
||||||
# It is possible for a fixture/test to change the CWD while this code runs, which
|
# It is possible for a fixture/test to change the CWD while this code runs, which
|
||||||
# would then result in the user seeing confusing paths in the failure message.
|
# would then result in the user seeing confusing paths in the failure message.
|
||||||
|
@ -466,12 +468,13 @@ class Node(abc.ABC, metaclass=NodeMeta):
|
||||||
style=style,
|
style=style,
|
||||||
tbfilter=tbfilter,
|
tbfilter=tbfilter,
|
||||||
truncate_locals=truncate_locals,
|
truncate_locals=truncate_locals,
|
||||||
|
truncate_args=truncate_args,
|
||||||
)
|
)
|
||||||
|
|
||||||
def repr_failure(
|
def repr_failure(
|
||||||
self,
|
self,
|
||||||
excinfo: ExceptionInfo[BaseException],
|
excinfo: ExceptionInfo[BaseException],
|
||||||
style: "Optional[_TracebackStyle]" = None,
|
style: "Optional[TracebackStyle]" = None,
|
||||||
) -> Union[str, TerminalRepr]:
|
) -> Union[str, TerminalRepr]:
|
||||||
"""Return a representation of a collection or test failure.
|
"""Return a representation of a collection or test failure.
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import TYPE_CHECKING
|
|
||||||
|
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
|
from _pytest.cacheprovider import Cache
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config.argparsing import Parser
|
from _pytest.config.argparsing import Parser
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.reports import TestReport
|
from _pytest.reports import TestReport
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from _pytest.cacheprovider import Cache
|
|
||||||
|
|
||||||
STEPWISE_CACHE_DIR = "cache/stepwise"
|
STEPWISE_CACHE_DIR = "cache/stepwise"
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,7 +33,6 @@ def pytest_addoption(parser: Parser) -> None:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.hookimpl
|
|
||||||
def pytest_configure(config: Config) -> None:
|
def pytest_configure(config: Config) -> None:
|
||||||
if config.option.stepwise_skip:
|
if config.option.stepwise_skip:
|
||||||
# allow --stepwise-skip to work on its own merits.
|
# allow --stepwise-skip to work on its own merits.
|
||||||
|
|
|
@ -34,8 +34,8 @@ class catch_threading_exception:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.args: Optional["threading.ExceptHookArgs"] = None
|
self.args: Optional[threading.ExceptHookArgs] = None
|
||||||
self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None
|
self._old_hook: Optional[Callable[[threading.ExceptHookArgs], Any]] = None
|
||||||
|
|
||||||
def _hook(self, args: "threading.ExceptHookArgs") -> None:
|
def _hook(self, args: "threading.ExceptHookArgs") -> None:
|
||||||
self.args = args
|
self.args = args
|
||||||
|
|
|
@ -41,6 +41,7 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
import twisted.trial.unittest
|
import twisted.trial.unittest
|
||||||
|
|
||||||
|
|
||||||
_SysExcInfoType = Union[
|
_SysExcInfoType = Union[
|
||||||
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
Tuple[Type[BaseException], BaseException, types.TracebackType],
|
||||||
Tuple[None, None, None],
|
Tuple[None, None, None],
|
||||||
|
@ -218,16 +219,17 @@ class TestCaseFunction(Function):
|
||||||
super().setup()
|
super().setup()
|
||||||
|
|
||||||
def teardown(self) -> None:
|
def teardown(self) -> None:
|
||||||
super().teardown()
|
|
||||||
if self._explicit_tearDown is not None:
|
if self._explicit_tearDown is not None:
|
||||||
self._explicit_tearDown()
|
self._explicit_tearDown()
|
||||||
self._explicit_tearDown = None
|
self._explicit_tearDown = None
|
||||||
self._obj = None
|
self._obj = None
|
||||||
|
del self._instance
|
||||||
|
super().teardown()
|
||||||
|
|
||||||
def startTest(self, testcase: "unittest.TestCase") -> None:
|
def startTest(self, testcase: "unittest.TestCase") -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None:
|
def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None:
|
||||||
# Unwrap potential exception info (see twisted trial support below).
|
# Unwrap potential exception info (see twisted trial support below).
|
||||||
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
|
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
|
||||||
try:
|
try:
|
||||||
|
@ -263,7 +265,7 @@ class TestCaseFunction(Function):
|
||||||
self.__dict__.setdefault("_excinfo", []).append(excinfo)
|
self.__dict__.setdefault("_excinfo", []).append(excinfo)
|
||||||
|
|
||||||
def addError(
|
def addError(
|
||||||
self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
|
self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
if isinstance(rawexcinfo[1], exit.Exception):
|
if isinstance(rawexcinfo[1], exit.Exception):
|
||||||
|
@ -273,7 +275,7 @@ class TestCaseFunction(Function):
|
||||||
self._addexcinfo(rawexcinfo)
|
self._addexcinfo(rawexcinfo)
|
||||||
|
|
||||||
def addFailure(
|
def addFailure(
|
||||||
self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType"
|
self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType
|
||||||
) -> None:
|
) -> None:
|
||||||
self._addexcinfo(rawexcinfo)
|
self._addexcinfo(rawexcinfo)
|
||||||
|
|
||||||
|
@ -286,7 +288,7 @@ class TestCaseFunction(Function):
|
||||||
def addExpectedFailure(
|
def addExpectedFailure(
|
||||||
self,
|
self,
|
||||||
testcase: "unittest.TestCase",
|
testcase: "unittest.TestCase",
|
||||||
rawexcinfo: "_SysExcInfoType",
|
rawexcinfo: _SysExcInfoType,
|
||||||
reason: str = "",
|
reason: str = "",
|
||||||
) -> None:
|
) -> None:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -34,8 +34,8 @@ class catch_unraisable_exception:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.unraisable: Optional["sys.UnraisableHookArgs"] = None
|
self.unraisable: Optional[sys.UnraisableHookArgs] = None
|
||||||
self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None
|
self._old_hook: Optional[Callable[[sys.UnraisableHookArgs], Any]] = None
|
||||||
|
|
||||||
def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None:
|
def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None:
|
||||||
# Storing unraisable.object can resurrect an object which is being
|
# Storing unraisable.object can resurrect an object which is being
|
||||||
|
|
|
@ -207,7 +207,7 @@ class CommonFSTests:
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"fil",
|
"fil",
|
||||||
["*dir", "*dir", pytest.mark.skip("sys.version_info <" " (3,6)")(b"*dir")],
|
["*dir", "*dir", pytest.mark.skip("sys.version_info < (3,6)")(b"*dir")],
|
||||||
)
|
)
|
||||||
def test_visit_filterfunc_is_string(self, path1, fil):
|
def test_visit_filterfunc_is_string(self, path1, fil):
|
||||||
lst = []
|
lst = []
|
||||||
|
|
|
@ -11,6 +11,7 @@ import re
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from typing import cast
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
|
@ -27,7 +28,7 @@ import pytest
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest._code.code import _TracebackStyle
|
from _pytest._code.code import TracebackStyle
|
||||||
|
|
||||||
if sys.version_info < (3, 11):
|
if sys.version_info < (3, 11):
|
||||||
from exceptiongroup import ExceptionGroup
|
from exceptiongroup import ExceptionGroup
|
||||||
|
@ -712,6 +713,29 @@ raise ValueError()
|
||||||
assert full_reprlocals.lines
|
assert full_reprlocals.lines
|
||||||
assert full_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
|
assert full_reprlocals.lines[0] == "l = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
|
||||||
|
|
||||||
|
def test_repr_args_not_truncated(self, importasmod) -> None:
|
||||||
|
mod = importasmod(
|
||||||
|
"""
|
||||||
|
def func1(m):
|
||||||
|
raise ValueError("hello\\nworld")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
excinfo = pytest.raises(ValueError, mod.func1, "m" * 500)
|
||||||
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
|
entry = excinfo.traceback[-1]
|
||||||
|
p = FormattedExcinfo(funcargs=True, truncate_args=True)
|
||||||
|
reprfuncargs = p.repr_args(entry)
|
||||||
|
assert reprfuncargs is not None
|
||||||
|
arg1 = cast(str, reprfuncargs.args[0][1])
|
||||||
|
assert len(arg1) < 500
|
||||||
|
assert "..." in arg1
|
||||||
|
# again without truncate
|
||||||
|
p = FormattedExcinfo(funcargs=True, truncate_args=False)
|
||||||
|
reprfuncargs = p.repr_args(entry)
|
||||||
|
assert reprfuncargs is not None
|
||||||
|
assert reprfuncargs.args[0] == ("m", repr("m" * 500))
|
||||||
|
assert "..." not in cast(str, reprfuncargs.args[0][1])
|
||||||
|
|
||||||
def test_repr_tracebackentry_lines(self, importasmod) -> None:
|
def test_repr_tracebackentry_lines(self, importasmod) -> None:
|
||||||
mod = importasmod(
|
mod = importasmod(
|
||||||
"""
|
"""
|
||||||
|
@ -901,7 +925,7 @@ raise ValueError()
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.entry)
|
excinfo = pytest.raises(ValueError, mod.entry)
|
||||||
|
|
||||||
styles: tuple[_TracebackStyle, ...] = ("long", "short")
|
styles: tuple[TracebackStyle, ...] = ("long", "short")
|
||||||
for style in styles:
|
for style in styles:
|
||||||
p = FormattedExcinfo(style=style)
|
p = FormattedExcinfo(style=style)
|
||||||
reprtb = p.repr_traceback(excinfo)
|
reprtb = p.repr_traceback(excinfo)
|
||||||
|
@ -1028,7 +1052,7 @@ raise ValueError()
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.entry)
|
excinfo = pytest.raises(ValueError, mod.entry)
|
||||||
|
|
||||||
styles: tuple[_TracebackStyle, ...] = ("short", "long", "no")
|
styles: tuple[TracebackStyle, ...] = ("short", "long", "no")
|
||||||
for style in styles:
|
for style in styles:
|
||||||
for showlocals in (True, False):
|
for showlocals in (True, False):
|
||||||
repr = excinfo.getrepr(style=style, showlocals=showlocals)
|
repr = excinfo.getrepr(style=style, showlocals=showlocals)
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class MyTestCase(unittest.TestCase):
|
||||||
|
first_time = True
|
||||||
|
|
||||||
|
def test_fail_the_first_time(self) -> None:
|
||||||
|
"""Regression test for issue #12424."""
|
||||||
|
if self.first_time:
|
||||||
|
type(self).first_time = False
|
||||||
|
self.fail()
|
|
@ -1,4 +1,4 @@
|
||||||
anyio[curio,trio]==4.3.0
|
anyio[curio,trio]==4.4.0
|
||||||
django==5.0.6
|
django==5.0.6
|
||||||
pytest-asyncio==0.23.7
|
pytest-asyncio==0.23.7
|
||||||
pytest-bdd==7.1.2
|
pytest-bdd==7.1.2
|
||||||
|
@ -9,7 +9,7 @@ pytest-html==4.1.1
|
||||||
pytest-mock==3.14.0
|
pytest-mock==3.14.0
|
||||||
pytest-rerunfailures==14.0
|
pytest-rerunfailures==14.0
|
||||||
pytest-sugar==1.0.0
|
pytest-sugar==1.0.0
|
||||||
pytest-trio==0.7.0
|
pytest-trio==0.8.0
|
||||||
pytest-twisted==1.14.1
|
pytest-twisted==1.14.1
|
||||||
twisted==24.3.0
|
twisted==24.3.0
|
||||||
pytest-xvfb==3.0.0
|
pytest-xvfb==3.0.0
|
||||||
|
|
|
@ -2219,6 +2219,25 @@ class TestAutouseManagement:
|
||||||
reprec = pytester.inline_run("-s")
|
reprec = pytester.inline_run("-s")
|
||||||
reprec.assertoutcome(passed=2)
|
reprec.assertoutcome(passed=2)
|
||||||
|
|
||||||
|
def test_reordering_catastrophic_performance(self, pytester: Pytester) -> None:
|
||||||
|
"""Check that a certain high-scope parametrization pattern doesn't cause
|
||||||
|
a catasrophic slowdown.
|
||||||
|
|
||||||
|
Regression test for #12355.
|
||||||
|
"""
|
||||||
|
pytester.makepyfile("""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
params = tuple("abcdefghijklmnopqrstuvwxyz")
|
||||||
|
@pytest.mark.parametrize(params, [range(len(params))] * 3, scope="module")
|
||||||
|
def test_parametrize(a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z):
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
|
||||||
|
result = pytester.runpytest()
|
||||||
|
|
||||||
|
result.assert_outcomes(passed=3)
|
||||||
|
|
||||||
|
|
||||||
class TestFixtureMarker:
|
class TestFixtureMarker:
|
||||||
def test_parametrize(self, pytester: Pytester) -> None:
|
def test_parametrize(self, pytester: Pytester) -> None:
|
||||||
|
|
|
@ -2045,3 +2045,36 @@ def test_fine_grained_assertion_verbosity(pytester: Pytester):
|
||||||
f"E AssertionError: assert 'hello world' in '{long_text}'",
|
f"E AssertionError: assert 'hello world' in '{long_text}'",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_full_output_vvv(pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
r"""
|
||||||
|
def crash_helper(m):
|
||||||
|
assert 1 == 2
|
||||||
|
def test_vvv():
|
||||||
|
crash_helper(500 * "a")
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("")
|
||||||
|
# without -vvv, the passed args are truncated
|
||||||
|
expected_non_vvv_arg_line = "m = 'aaaaaaaaaaaaaaa*..aaaaaaaaaaaa*"
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
expected_non_vvv_arg_line,
|
||||||
|
"test_full_output_vvv.py:2: AssertionError",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
# double check that the untruncated part is not in the output
|
||||||
|
expected_vvv_arg_line = "m = '{}'".format(500 * "a")
|
||||||
|
result.stdout.no_fnmatch_line(expected_vvv_arg_line)
|
||||||
|
|
||||||
|
# but with "-vvv" the args are not truncated
|
||||||
|
result = pytester.runpytest("-vvv")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
expected_vvv_arg_line,
|
||||||
|
"test_full_output_vvv.py:2: AssertionError",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
result.stdout.no_fnmatch_line(expected_non_vvv_arg_line)
|
||||||
|
|
|
@ -1163,7 +1163,7 @@ class TestNewFirst:
|
||||||
)
|
)
|
||||||
|
|
||||||
p1.write_text(
|
p1.write_text(
|
||||||
"def test_1(): assert 1\n" "def test_2(): assert 1\n", encoding="utf-8"
|
"def test_1(): assert 1\ndef test_2(): assert 1\n", encoding="utf-8"
|
||||||
)
|
)
|
||||||
os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9)))
|
os.utime(p1, ns=(p1.stat().st_atime_ns, int(1e9)))
|
||||||
|
|
||||||
|
|
|
@ -100,14 +100,13 @@ class TestReportSerialization:
|
||||||
|
|
||||||
rep_entries = rep.longrepr.reprtraceback.reprentries
|
rep_entries = rep.longrepr.reprtraceback.reprentries
|
||||||
a_entries = a.longrepr.reprtraceback.reprentries
|
a_entries = a.longrepr.reprtraceback.reprentries
|
||||||
for i in range(len(a_entries)):
|
assert len(rep_entries) == len(a_entries) # python < 3.10 zip(strict=True)
|
||||||
rep_entry = rep_entries[i]
|
for a_entry, rep_entry in zip(a_entries, rep_entries):
|
||||||
assert isinstance(rep_entry, ReprEntry)
|
assert isinstance(rep_entry, ReprEntry)
|
||||||
assert rep_entry.reprfileloc is not None
|
assert rep_entry.reprfileloc is not None
|
||||||
assert rep_entry.reprfuncargs is not None
|
assert rep_entry.reprfuncargs is not None
|
||||||
assert rep_entry.reprlocals is not None
|
assert rep_entry.reprlocals is not None
|
||||||
|
|
||||||
a_entry = a_entries[i]
|
|
||||||
assert isinstance(a_entry, ReprEntry)
|
assert isinstance(a_entry, ReprEntry)
|
||||||
assert a_entry.reprfileloc is not None
|
assert a_entry.reprfileloc is not None
|
||||||
assert a_entry.reprfuncargs is not None
|
assert a_entry.reprfuncargs is not None
|
||||||
|
@ -146,9 +145,10 @@ class TestReportSerialization:
|
||||||
|
|
||||||
rep_entries = rep.longrepr.reprtraceback.reprentries
|
rep_entries = rep.longrepr.reprtraceback.reprentries
|
||||||
a_entries = a.longrepr.reprtraceback.reprentries
|
a_entries = a.longrepr.reprtraceback.reprentries
|
||||||
for i in range(len(a_entries)):
|
assert len(rep_entries) == len(a_entries) # python < 3.10 zip(strict=True)
|
||||||
assert isinstance(rep_entries[i], ReprEntryNative)
|
for rep_entry, a_entry in zip(rep_entries, a_entries):
|
||||||
assert rep_entries[i].lines == a_entries[i].lines
|
assert isinstance(rep_entry, ReprEntryNative)
|
||||||
|
assert rep_entry.lines == a_entry.lines
|
||||||
|
|
||||||
def test_itemreport_outcomes(self, pytester: Pytester) -> None:
|
def test_itemreport_outcomes(self, pytester: Pytester) -> None:
|
||||||
# This test came originally from test_remote.py in xdist (ca03269).
|
# This test came originally from test_remote.py in xdist (ca03269).
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
# mypy: allow-untyped-defs
|
# mypy: allow-untyped-defs
|
||||||
import gc
|
|
||||||
import sys
|
import sys
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
@ -192,30 +191,35 @@ def test_teardown(pytester: Pytester) -> None:
|
||||||
def test_teardown_issue1649(pytester: Pytester) -> None:
|
def test_teardown_issue1649(pytester: Pytester) -> None:
|
||||||
"""
|
"""
|
||||||
Are TestCase objects cleaned up? Often unittest TestCase objects set
|
Are TestCase objects cleaned up? Often unittest TestCase objects set
|
||||||
attributes that are large and expensive during setUp.
|
attributes that are large and expensive during test run or setUp.
|
||||||
|
|
||||||
The TestCase will not be cleaned up if the test fails, because it
|
The TestCase will not be cleaned up if the test fails, because it
|
||||||
would then exist in the stackframe.
|
would then exist in the stackframe.
|
||||||
|
|
||||||
|
Regression test for #1649 (see also #12367).
|
||||||
"""
|
"""
|
||||||
testpath = pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
import unittest
|
import unittest
|
||||||
|
import gc
|
||||||
|
|
||||||
class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase):
|
class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase):
|
||||||
def setUp(self):
|
def test_expensive(self):
|
||||||
self.an_expensive_object = 1
|
self.an_expensive_obj = object()
|
||||||
def test_demo(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
"""
|
def test_is_it_still_alive(self):
|
||||||
)
|
|
||||||
|
|
||||||
pytester.inline_run("-s", testpath)
|
|
||||||
gc.collect()
|
gc.collect()
|
||||||
|
|
||||||
# Either already destroyed, or didn't run setUp.
|
|
||||||
for obj in gc.get_objects():
|
for obj in gc.get_objects():
|
||||||
if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp":
|
if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp":
|
||||||
assert not hasattr(obj, "an_expensive_obj")
|
assert not hasattr(obj, "an_expensive_obj")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
assert False, "Could not find TestCaseObjectsShouldBeCleanedUp instance"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
result = pytester.runpytest()
|
||||||
|
assert result.ret == ExitCode.OK
|
||||||
|
|
||||||
|
|
||||||
def test_unittest_skip_issue148(pytester: Pytester) -> None:
|
def test_unittest_skip_issue148(pytester: Pytester) -> None:
|
||||||
|
@ -380,7 +384,7 @@ def test_testcase_adderrorandfailure_defers(pytester: Pytester, type: str) -> No
|
||||||
@pytest.mark.parametrize("type", ["Error", "Failure"])
|
@pytest.mark.parametrize("type", ["Error", "Failure"])
|
||||||
def test_testcase_custom_exception_info(pytester: Pytester, type: str) -> None:
|
def test_testcase_custom_exception_info(pytester: Pytester, type: str) -> None:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
f"""
|
||||||
from typing import Generic, TypeVar
|
from typing import Generic, TypeVar
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
import pytest, _pytest._code
|
import pytest, _pytest._code
|
||||||
|
@ -409,7 +413,7 @@ def test_testcase_custom_exception_info(pytester: Pytester, type: str) -> None:
|
||||||
|
|
||||||
def test_hello(self):
|
def test_hello(self):
|
||||||
pass
|
pass
|
||||||
""".format(**locals())
|
"""
|
||||||
)
|
)
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
|
|
|
@ -280,10 +280,8 @@ def test_warning_recorded_hook(pytester: Pytester) -> None:
|
||||||
("call warning", "runtest", "test_warning_recorded_hook.py::test_func"),
|
("call warning", "runtest", "test_warning_recorded_hook.py::test_func"),
|
||||||
("teardown warning", "runtest", "test_warning_recorded_hook.py::test_func"),
|
("teardown warning", "runtest", "test_warning_recorded_hook.py::test_func"),
|
||||||
]
|
]
|
||||||
for index in range(len(expected)):
|
assert len(collected) == len(expected) # python < 3.10 zip(strict=True)
|
||||||
collected_result = collected[index]
|
for collected_result, expected_result in zip(collected, expected):
|
||||||
expected_result = expected[index]
|
|
||||||
|
|
||||||
assert collected_result[0] == expected_result[0], str(collected)
|
assert collected_result[0] == expected_result[0], str(collected)
|
||||||
assert collected_result[1] == expected_result[1], str(collected)
|
assert collected_result[1] == expected_result[1], str(collected)
|
||||||
assert collected_result[2] == expected_result[2], str(collected)
|
assert collected_result[2] == expected_result[2], str(collected)
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -141,7 +141,7 @@ commands =
|
||||||
pytest --cov=. simple_integration.py
|
pytest --cov=. simple_integration.py
|
||||||
pytest --ds=django_settings simple_integration.py
|
pytest --ds=django_settings simple_integration.py
|
||||||
pytest --html=simple.html simple_integration.py
|
pytest --html=simple.html simple_integration.py
|
||||||
pytest --reruns 5 simple_integration.py
|
pytest --reruns 5 simple_integration.py pytest_rerunfailures_integration.py
|
||||||
pytest pytest_anyio_integration.py
|
pytest pytest_anyio_integration.py
|
||||||
pytest pytest_asyncio_integration.py
|
pytest pytest_asyncio_integration.py
|
||||||
pytest pytest_mock_integration.py
|
pytest pytest_mock_integration.py
|
||||||
|
|
Loading…
Reference in New Issue