From b92fde37cee08ac018f17f41913f2cf9d98d0fe4 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 24 Sep 2022 23:36:50 +0200 Subject: [PATCH 001/199] Introduce a gate/check GHA job MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds a GHA job that reliably determines if all the required dependencies have succeeded or not. It also allows to reduce the list of required branch protection CI statuses to just one — `check`. This reduces the maintenance burden by a lot and have been battle-tested across a small bunch of projects in its action form and in-house implementations of other people. I was curious about the spread of use. And I've just discovered that it is now in use in aiohttp (and other aio-libs projects), CherryPy, some of the Ansible repositories, all of the jaraco's projects (like `setuptools`, `importlib_metadata`), some PyCQA, PyCA and pytest projects, a few AWS Labs projects. Admittedly, I maintain a few of these but it seems to address some of the pain folks have: https://github.com/jaraco/skeleton/pull/55#issuecomment-1106638475. The story behind this is explained in more detail at https://github.com/marketplace/actions/alls-green#why. --- .github/workflows/test.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5f5832475d3..80e747400e3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -211,3 +211,17 @@ jobs: fail_ci_if_error: true files: ./coverage.xml verbose: true + + check: # This job does nothing and is only used for the branch protection + if: always() + + needs: + - build + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} From 30ce631fb9f306e81d70de03d2328162a8717933 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 24 Sep 2022 23:39:20 +0200 Subject: [PATCH 002/199] Securely pin re-actors/alls-green to v1.2 commit --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 80e747400e3..9e646e53ab4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -222,6 +222,6 @@ jobs: steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@release/v1 + uses: re-actors/alls-green@198badcb65a1a44528f27d5da555c4be9f12eac6 with: jobs: ${{ toJSON(needs) }} From fc5c9603041251225a2ddc7caf77e9fb3d013e68 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 01:39:36 +0300 Subject: [PATCH 003/199] runner: avoid adding uninteresting entry to tracebacks In these two cases which re-raise an immediately-caught exception, do the re-raising with `raise` instead of `raise exc`, because the `raise exc` adds an uninteresting entry to the exception's traceback (that of itself), while `raise` doesn't. --- src/_pytest/runner.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 3f706b927c8..6883a1a5895 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -180,7 +180,7 @@ def pytest_runtest_call(item: Item) -> None: assert e.__traceback__ is not None # Skip *this* frame sys.last_traceback = e.__traceback__.tb_next - raise e + raise def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: @@ -512,7 +512,7 @@ def setup(self, item: Item) -> None: col.setup() except TEST_OUTCOME as exc: self.stack[col] = (self.stack[col][0], exc) - raise exc + raise def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: """Attach a finalizer to the given node. From 79ca819e59948661c5403dd4aa77c248266a8c76 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 27 Apr 2024 20:35:29 -0300 Subject: [PATCH 004/199] Merge pull request #12257 from pytest-dev/release-8.2.0 Prepare release 8.2.0 (cherry picked from commit 69c3bcea36e1cf3468ac3cb92da9cf37038f959d) --- changelog/11523.improvement.rst | 5 -- changelog/11728.improvement.rst | 1 - changelog/11777.improvement.rst | 1 - changelog/11871.feature.rst | 1 - changelog/12065.bugfix.rst | 4 -- changelog/12069.deprecation.rst | 12 ----- changelog/12069.trivial.rst | 1 - changelog/12112.improvement.rst | 1 - changelog/12135.bugfix.rst | 1 - changelog/12167.trivial.rst | 1 - changelog/12194.bugfix.rst | 1 - changelog/1489.bugfix.rst | 1 - changelog/9502.improvement.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-8.2.0.rst | 43 +++++++++++++++ doc/en/builtin.rst | 30 +++++------ doc/en/changelog.rst | 81 +++++++++++++++++++++++++++++ doc/en/example/parametrize.rst | 6 +-- doc/en/example/pythoncollection.rst | 4 +- doc/en/getting-started.rst | 2 +- doc/en/how-to/fixtures.rst | 2 +- doc/en/how-to/output.rst | 44 ++++++++++++++-- 22 files changed, 188 insertions(+), 56 deletions(-) delete mode 100644 changelog/11523.improvement.rst delete mode 100644 changelog/11728.improvement.rst delete mode 100644 changelog/11777.improvement.rst delete mode 100644 changelog/11871.feature.rst delete mode 100644 changelog/12065.bugfix.rst delete mode 100644 changelog/12069.deprecation.rst delete mode 100644 changelog/12069.trivial.rst delete mode 100644 changelog/12112.improvement.rst delete mode 100644 changelog/12135.bugfix.rst delete mode 100644 changelog/12167.trivial.rst delete mode 100644 changelog/12194.bugfix.rst delete mode 100644 changelog/1489.bugfix.rst delete mode 100644 changelog/9502.improvement.rst create mode 100644 doc/en/announce/release-8.2.0.rst diff --git a/changelog/11523.improvement.rst b/changelog/11523.improvement.rst deleted file mode 100644 index f7d8ff89df6..00000000000 --- a/changelog/11523.improvement.rst +++ /dev/null @@ -1,5 +0,0 @@ -:func:`pytest.importorskip` will now issue a warning if the module could be found, but raised :class:`ImportError` instead of :class:`ModuleNotFoundError`. - -The warning can be suppressed by passing ``exc_type=ImportError`` to :func:`pytest.importorskip`. - -See :ref:`import-or-skip-import-error` for details. diff --git a/changelog/11728.improvement.rst b/changelog/11728.improvement.rst deleted file mode 100644 index 1e87fc5ed88..00000000000 --- a/changelog/11728.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -For ``unittest``-based tests, exceptions during class cleanup (as raised by functions registered with :meth:`TestCase.addClassCleanup `) are now reported instead of silently failing. diff --git a/changelog/11777.improvement.rst b/changelog/11777.improvement.rst deleted file mode 100644 index fb53c63c10a..00000000000 --- a/changelog/11777.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Text is no longer truncated in the ``short test summary info`` section when ``-vv`` is given. diff --git a/changelog/11871.feature.rst b/changelog/11871.feature.rst deleted file mode 100644 index 530db8c3c6f..00000000000 --- a/changelog/11871.feature.rst +++ /dev/null @@ -1 +0,0 @@ -Added support for reading command line arguments from a file using the prefix character ``@``, like e.g.: ``pytest @tests.txt``. The file must have one argument per line. diff --git a/changelog/12065.bugfix.rst b/changelog/12065.bugfix.rst deleted file mode 100644 index ca55b327e13..00000000000 --- a/changelog/12065.bugfix.rst +++ /dev/null @@ -1,4 +0,0 @@ -Fixed a regression in pytest 8.0.0 where test classes containing ``setup_method`` and tests using ``@staticmethod`` or ``@classmethod`` would crash with ``AttributeError: 'NoneType' object has no attribute 'setup_method'``. - -Now the :attr:`request.instance ` attribute of tests using ``@staticmethod`` and ``@classmethod`` is no longer ``None``, but a fresh instance of the class, like in non-static methods. -Previously it was ``None``, and all fixtures of such tests would share a single ``self``. diff --git a/changelog/12069.deprecation.rst b/changelog/12069.deprecation.rst deleted file mode 100644 index c8798b5ff25..00000000000 --- a/changelog/12069.deprecation.rst +++ /dev/null @@ -1,12 +0,0 @@ -A deprecation warning is now raised when implementations of one of the following hooks request a deprecated ``py.path.local`` parameter instead of the ``pathlib.Path`` parameter which replaced it: - -- :hook:`pytest_ignore_collect` - the ``path`` parameter - use ``collection_path`` instead. -- :hook:`pytest_collect_file` - the ``path`` parameter - use ``file_path`` instead. -- :hook:`pytest_pycollect_makemodule` - the ``path`` parameter - use ``module_path`` instead. -- :hook:`pytest_report_header` - the ``startdir`` parameter - use ``start_path`` instead. -- :hook:`pytest_report_collectionfinish` - the ``startdir`` parameter - use ``start_path`` instead. - -The replacement parameters are available since pytest 7.0.0. -The old parameters will be removed in pytest 9.0.0. - -See :ref:`legacy-path-hooks-deprecated` for more details. diff --git a/changelog/12069.trivial.rst b/changelog/12069.trivial.rst deleted file mode 100644 index 8eb9b0c464a..00000000000 --- a/changelog/12069.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -``pluggy>=1.5.0`` is now required. diff --git a/changelog/12112.improvement.rst b/changelog/12112.improvement.rst deleted file mode 100644 index 3f997b2af65..00000000000 --- a/changelog/12112.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Improve namespace packages detection when :confval:`consider_namespace_packages` is enabled, covering more situations (like editable installs). diff --git a/changelog/12135.bugfix.rst b/changelog/12135.bugfix.rst deleted file mode 100644 index 734733b100d..00000000000 --- a/changelog/12135.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix fixtures adding their finalizer multiple times to fixtures they request, causing unreliable and non-intuitive teardown ordering in some instances. diff --git a/changelog/12167.trivial.rst b/changelog/12167.trivial.rst deleted file mode 100644 index da9363420e6..00000000000 --- a/changelog/12167.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -cache: create cache directory supporting files (``CACHEDIR.TAG``, ``.gitignore``, etc.) in a temporary directory to provide atomic semantics. diff --git a/changelog/12194.bugfix.rst b/changelog/12194.bugfix.rst deleted file mode 100644 index 6983ba35a90..00000000000 --- a/changelog/12194.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed a bug with ``--importmode=importlib`` and ``--doctest-modules`` where child modules did not appear as attributes in parent modules. diff --git a/changelog/1489.bugfix.rst b/changelog/1489.bugfix.rst deleted file mode 100644 index 70c5dd1252e..00000000000 --- a/changelog/1489.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix some instances where teardown of higher-scoped fixtures was not happening in the reverse order they were initialized in. diff --git a/changelog/9502.improvement.rst b/changelog/9502.improvement.rst deleted file mode 100644 index 2eaf6a72747..00000000000 --- a/changelog/9502.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Added :envvar:`PYTEST_VERSION` environment variable which is defined at the start of the pytest session and undefined afterwards. It contains the value of ``pytest.__version__``, and among other things can be used to easily check if code is running from within a pytest run. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index bb39eb7e6a1..4d0a3ab558e 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.2.0 release-8.1.2 release-8.1.1 release-8.1.0 diff --git a/doc/en/announce/release-8.2.0.rst b/doc/en/announce/release-8.2.0.rst new file mode 100644 index 00000000000..2a63c8d8722 --- /dev/null +++ b/doc/en/announce/release-8.2.0.rst @@ -0,0 +1,43 @@ +pytest-8.2.0 +======================================= + +The pytest team is proud to announce the 8.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: + +* Bruno Oliveira +* Daniel Miller +* Florian Bruhin +* HolyMagician03-UMich +* John Litborn +* Levon Saldamli +* Linghao Zhang +* Manuel López-Ibáñez +* Pierre Sassoulas +* Ran Benita +* Ronny Pfannschmidt +* Sebastian Meyer +* Shekhar verma +* Tamir Duberstein +* Tobias Stoeckmann +* dj +* jakkdl +* poulami-sau +* tserg + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 9d49389f190..d5f2e9a1b0f 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:527 + cache -- .../_pytest/cacheprovider.py:542 Return a cache object that can persist state between testing sessions. cache.get(key, default) @@ -33,7 +33,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a Values can be any object handled by the json stdlib module. - capsysbinary -- .../_pytest/capture.py:1008 + capsysbinary -- .../_pytest/capture.py:1003 Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsysbinary.readouterr()`` @@ -50,7 +50,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capsysbinary.readouterr() assert captured.out == b"hello\n" - capfd -- .../_pytest/capture.py:1035 + capfd -- .../_pytest/capture.py:1030 Enable text capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -67,7 +67,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capfd.readouterr() assert captured.out == "hello\n" - capfdbinary -- .../_pytest/capture.py:1062 + capfdbinary -- .../_pytest/capture.py:1057 Enable bytes capturing of writes to file descriptors ``1`` and ``2``. The captured output is made available via ``capfd.readouterr()`` method @@ -84,7 +84,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capfdbinary.readouterr() assert captured.out == b"hello\n" - capsys -- .../_pytest/capture.py:981 + capsys -- .../_pytest/capture.py:976 Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. The captured output is made available via ``capsys.readouterr()`` method @@ -101,7 +101,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a captured = capsys.readouterr() assert captured.out == "hello\n" - doctest_namespace [session scope] -- .../_pytest/doctest.py:737 + doctest_namespace [session scope] -- .../_pytest/doctest.py:738 Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. @@ -115,7 +115,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a For more details: :ref:`doctest_namespace`. - pytestconfig [session scope] -- .../_pytest/fixtures.py:1346 + pytestconfig [session scope] -- .../_pytest/fixtures.py:1335 Session-scoped fixture that returns the session's :class:`pytest.Config` object. @@ -125,7 +125,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a if pytestconfig.getoption("verbose") > 0: ... - record_property -- .../_pytest/junitxml.py:283 + record_property -- .../_pytest/junitxml.py:284 Add extra properties to the calling test. User properties become part of the test report and are available to the @@ -139,13 +139,13 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a def test_function(record_property): record_property("example_key", 1) - record_xml_attribute -- .../_pytest/junitxml.py:306 + record_xml_attribute -- .../_pytest/junitxml.py:307 Add extra xml attributes to the tag for the calling test. The fixture is callable with ``name, value``. The value is automatically XML-encoded. - record_testsuite_property [session scope] -- .../_pytest/junitxml.py:344 + record_testsuite_property [session scope] -- .../_pytest/junitxml.py:345 Record a new ```` tag as child of the root ````. This is suitable to writing global information regarding the entire test @@ -192,7 +192,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a .. _legacy_path: https://py.readthedocs.io/en/latest/path.html - caplog -- .../_pytest/logging.py:601 + caplog -- .../_pytest/logging.py:602 Access and control log capturing. Captured logs are available through the following properties/methods:: @@ -203,7 +203,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string - monkeypatch -- .../_pytest/monkeypatch.py:32 + monkeypatch -- .../_pytest/monkeypatch.py:33 A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -227,16 +227,16 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a To undo modifications done by the fixture in a contained scope, use :meth:`context() `. - recwarn -- .../_pytest/recwarn.py:31 + recwarn -- .../_pytest/recwarn.py:32 Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information on warning categories. - tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:241 + tmp_path_factory [session scope] -- .../_pytest/tmpdir.py:242 Return a :class:`pytest.TempPathFactory` instance for the test session. - tmp_path -- .../_pytest/tmpdir.py:256 + tmp_path -- .../_pytest/tmpdir.py:257 Return a temporary directory path object which is unique to each test function invocation, created as a sub directory of the base temporary directory. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index fb316e706ba..2630d95cf28 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,87 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.2.0 (2024-04-27) +========================= + +Deprecations +------------ + +- `#12069 `_: A deprecation warning is now raised when implementations of one of the following hooks request a deprecated ``py.path.local`` parameter instead of the ``pathlib.Path`` parameter which replaced it: + + - :hook:`pytest_ignore_collect` - the ``path`` parameter - use ``collection_path`` instead. + - :hook:`pytest_collect_file` - the ``path`` parameter - use ``file_path`` instead. + - :hook:`pytest_pycollect_makemodule` - the ``path`` parameter - use ``module_path`` instead. + - :hook:`pytest_report_header` - the ``startdir`` parameter - use ``start_path`` instead. + - :hook:`pytest_report_collectionfinish` - the ``startdir`` parameter - use ``start_path`` instead. + + The replacement parameters are available since pytest 7.0.0. + The old parameters will be removed in pytest 9.0.0. + + See :ref:`legacy-path-hooks-deprecated` for more details. + + + +Features +-------- + +- `#11871 `_: Added support for reading command line arguments from a file using the prefix character ``@``, like e.g.: ``pytest @tests.txt``. The file must have one argument per line. + + See :ref:`Read arguments from file ` for details. + + + +Improvements +------------ + +- `#11523 `_: :func:`pytest.importorskip` will now issue a warning if the module could be found, but raised :class:`ImportError` instead of :class:`ModuleNotFoundError`. + + The warning can be suppressed by passing ``exc_type=ImportError`` to :func:`pytest.importorskip`. + + See :ref:`import-or-skip-import-error` for details. + + +- `#11728 `_: For ``unittest``-based tests, exceptions during class cleanup (as raised by functions registered with :meth:`TestCase.addClassCleanup `) are now reported instead of silently failing. + + +- `#11777 `_: Text is no longer truncated in the ``short test summary info`` section when ``-vv`` is given. + + +- `#12112 `_: Improved namespace packages detection when :confval:`consider_namespace_packages` is enabled, covering more situations (like editable installs). + + +- `#9502 `_: Added :envvar:`PYTEST_VERSION` environment variable which is defined at the start of the pytest session and undefined afterwards. It contains the value of ``pytest.__version__``, and among other things can be used to easily check if code is running from within a pytest run. + + + +Bug Fixes +--------- + +- `#12065 `_: Fixed a regression in pytest 8.0.0 where test classes containing ``setup_method`` and tests using ``@staticmethod`` or ``@classmethod`` would crash with ``AttributeError: 'NoneType' object has no attribute 'setup_method'``. + + Now the :attr:`request.instance ` attribute of tests using ``@staticmethod`` and ``@classmethod`` is no longer ``None``, but a fresh instance of the class, like in non-static methods. + Previously it was ``None``, and all fixtures of such tests would share a single ``self``. + + +- `#12135 `_: Fixed issue where fixtures adding their finalizer multiple times to fixtures they request would cause unreliable and non-intuitive teardown ordering in some instances. + + +- `#12194 `_: Fixed a bug with ``--importmode=importlib`` and ``--doctest-modules`` where child modules did not appear as attributes in parent modules. + + +- `#1489 `_: Fixed some instances where teardown of higher-scoped fixtures was not happening in the reverse order they were initialized in. + + + +Trivial/Internal Changes +------------------------ + +- `#12069 `_: ``pluggy>=1.5.0`` is now required. + + +- `#12167 `_: :ref:`cache `: create supporting files (``CACHEDIR.TAG``, ``.gitignore``, etc.) in a temporary directory to provide atomic semantics. + + pytest 8.1.2 (2024-04-26) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 672c7c4457d..1bbe2faaad0 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -162,7 +162,7 @@ objects, they are still using the default pytest representation: rootdir: /home/sweet/project collected 8 items - + @@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia rootdir: /home/sweet/project collected 4 items - + @@ -318,7 +318,7 @@ Let's first see how it looks like at collection time: rootdir: /home/sweet/project collected 2 items - + diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index c01e685f3f4..a383173d07e 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -152,7 +152,7 @@ The test collection would look like this: configfile: pytest.ini collected 2 items - + @@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this: configfile: pytest.ini collected 3 items - + diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index e96eabbc5e7..5b33e308d13 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 8.1.2 + pytest 8.2.0 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index ba416ccbc23..72b69a14681 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used: rootdir: /home/sweet/project collected 12 items - + diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 5b47a5c7776..7a4e32edc78 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -294,9 +294,47 @@ Now if we increase verbosity even more: test_verbosity_example.py:19: AssertionError ========================= short test summary info ========================== - FAILED test_verbosity_example.py::test_words_fail - AssertionError: asser... - FAILED test_verbosity_example.py::test_numbers_fail - AssertionError: ass... - FAILED test_verbosity_example.py::test_long_text_fail - AssertionError: a... + FAILED test_verbosity_example.py::test_words_fail - AssertionError: assert ['banana', 'apple', 'grapes', 'melon', 'kiwi'] == ['banana', 'apple', 'orange', 'melon', 'kiwi'] + + At index 2 diff: 'grapes' != 'orange' + + Full diff: + [ + 'banana', + 'apple', + - 'orange', + ? ^ ^^ + + 'grapes', + ? ^ ^ + + 'melon', + 'kiwi', + ] + FAILED test_verbosity_example.py::test_numbers_fail - AssertionError: assert {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4} == {'0': 0, '10': 10, '20': 20, '30': 30, '40': 40} + + Common items: + {'0': 0} + Left contains 4 more items: + {'1': 1, '2': 2, '3': 3, '4': 4} + Right contains 4 more items: + {'10': 10, '20': 20, '30': 30, '40': 40} + + Full diff: + { + '0': 0, + - '10': 10, + ? - - + + '1': 1, + - '20': 20, + ? - - + + '2': 2, + - '30': 30, + ? - - + + '3': 3, + - '40': 40, + ? - - + + '4': 4, + } + FAILED test_verbosity_example.py::test_long_text_fail - AssertionError: assert 'hello world' in 'Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet Lorem ipsum dolor sit amet ' ======================= 3 failed, 1 passed in 0.12s ======================== Notice now that: From 852df0dc3e384976924041de6273ef4f19a74cf4 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 28 Apr 2024 00:20:56 +0000 Subject: [PATCH 005/199] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 136 ++++++++++++++++++------------- 1 file changed, 80 insertions(+), 56 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index e1d1e3ec24a..68d017c31c4 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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 Jan 18, 2024 5 - Production/Stable pytest >=6 + :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures Apr 22, 2024 5 - Production/Stable pytest>=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 @@ -119,7 +119,7 @@ This list contains 1448 plugins. :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-automation` pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. Apr 24, 2024 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 @@ -145,7 +145,7 @@ This list contains 1448 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Apr 19, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Apr 26, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -267,6 +267,7 @@ This list contains 1448 plugins. :pypi:`pytest-container` Pytest fixtures for writing container based tests Apr 10, 2024 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-continuous` A pytest plugin to run tests continuously until failure or interruption. Apr 23, 2024 N/A N/A :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.0) :pypi:`pytest-copie` The pytest plugin for your copier templates 📒 Jan 27, 2024 3 - Alpha pytest :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Dec 11, 2023 4 - Beta pytest>=7.3.2 @@ -301,9 +302,9 @@ This list contains 1448 plugins. :pypi:`pytest-cython-collect` Jun 17, 2022 N/A pytest :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Feb 25, 2024 N/A pytest <7,>=6.0.1 :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A - :pypi:`pytest-dashboard` Apr 18, 2024 N/A pytest<8.0.0,>=7.4.3 + :pypi:`pytest-dashboard` Apr 22, 2024 N/A pytest<8.0.0,>=7.4.3 :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Apr 19, 2024 4 - Beta pytest + :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Apr 21, 2024 4 - Beta pytest :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 03, 2023 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) @@ -436,14 +437,14 @@ This list contains 1448 plugins. :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 Apr 04, 2024 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. Apr 09, 2024 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Apr 09, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Apr 09, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Apr 09, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Apr 09, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Apr 09, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Apr 09, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Apr 09, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Apr 26, 2024 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Apr 26, 2024 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) @@ -468,7 +469,7 @@ This list contains 1448 plugins. :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-evm` The testing package containing tools to test Web3-based projects Apr 20, 2024 4 - Beta pytest<9.0.0,>=8.1.1 + :pypi:`pytest-evm` The testing package containing tools to test Web3-based projects Apr 22, 2024 4 - Beta pytest<9.0.0,>=8.1.1 :pypi:`pytest_exact_fixtures` Parse queries in Lucene and Elasticsearch syntaxes Feb 04, 2019 N/A N/A :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7 :pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9) @@ -512,6 +513,7 @@ This list contains 1448 plugins. :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Oct 04, 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-fauna` A collection of helpful test fixtures for Fauna DB. Apr 22, 2024 N/A N/A :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-file` Pytest File Mar 18, 2024 1 - Planning N/A @@ -591,7 +593,7 @@ This list contains 1448 plugins. :pypi:`pytest-gitlab-code-quality` Collects warnings while testing and generates a GitLab Code Quality Report. Apr 03, 2024 N/A pytest>=8.1.1 :pypi:`pytest-gitlab-fold` Folds output sections in GitLab CI build log Dec 31, 2023 4 - Beta pytest >=2.6.0 :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-glamor-allure` Extends allure-pytest functionality Apr 22, 2024 4 - Beta pytest<=8.1.1 :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) @@ -621,7 +623,7 @@ This list contains 1448 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 Apr 13, 2024 3 - Alpha pytest==8.1.1 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Apr 25, 2024 3 - Alpha pytest==8.1.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` Apr 18, 2024 N/A N/A @@ -652,13 +654,13 @@ This list contains 1448 plugins. :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-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Apr 12, 2024 3 - Alpha pytest>=7.0.0 + :pypi:`pytest-iam` A fully functional OAUTH2 / OpenID Connect (OIDC) server to be used in your testsuite Apr 22, 2024 3 - Alpha pytest>=7.0.0 :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 Dec 05, 2023 4 - Beta pytest :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 Dec 13, 2023 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 08, 2024 5 - Production/Stable pytest>=6.0 + :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 20, 2024 5 - Production/Stable pytest>=6.0 :pypi:`pytest-ignore-test-results` A pytest plugin to ignore test results. Aug 17, 2023 2 - Pre-Alpha pytest>=7.0 :pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0 @@ -809,7 +811,7 @@ This list contains 1448 plugins. :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-minio-mock` A pytest plugin for mocking Minio S3 interactions Apr 15, 2024 N/A pytest>=5.0.0 + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions Apr 27, 2024 N/A pytest>=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-mitmproxy` pytest plugin for mitmproxy tests Mar 07, 2024 N/A pytest >=7.0 :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A @@ -853,6 +855,7 @@ This list contains 1448 plugins. :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 Mar 31, 2024 4 - Beta pytest>=7.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 pytest>=6.0.0 + :pypi:`pytest-mypy-runner` Run the mypy static type checker as a pytest test case Apr 23, 2024 N/A pytest>=8.0 :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Mar 04, 2024 N/A pytest>=7,<9 :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Oct 30, 2023 5 - Production/Stable pytest >=6.2 :pypi:`pytest-ndb` pytest notebook debugger Oct 15, 2023 N/A pytest @@ -1104,7 +1107,7 @@ This list contains 1448 plugins. :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-rerun-all` Rerun testsuite for a certain time or iterations Nov 16, 2023 3 - Alpha pytest (>=7.0.0) - :pypi:`pytest-rerunclassfailures` pytest rerun class failures plugin Mar 29, 2024 5 - Production/Stable pytest>=7.2 + :pypi:`pytest-rerunclassfailures` pytest rerun class failures plugin Apr 24, 2024 5 - Production/Stable pytest>=7.2 :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Mar 13, 2024 5 - Production/Stable pytest >=7.2 :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. Feb 08, 2024 4 - Beta pytest @@ -1156,7 +1159,7 @@ This list contains 1448 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Apr 14, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Apr 26, 2024 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 Feb 16, 2024 5 - Production/Stable pytest >=3.5.0 @@ -1165,12 +1168,12 @@ This list contains 1448 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Apr 14, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Apr 26, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 :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 05, 2024 N/A pytest + :pypi:`pytest-sentry` A pytest plugin to send testrun information to Sentry.io Apr 25, 2024 N/A pytest :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 Dec 19, 2023 5 - Production/Stable pytest :pypi:`pytest-serverless` Automatically mocks resources from serverless.yml in pytest using moto. May 09, 2022 4 - Beta N/A @@ -1294,7 +1297,7 @@ This list contains 1448 plugins. :pypi:`pytest-tcpclient` A pytest plugin for testing TCP clients Nov 16, 2022 N/A pytest (<8,>=7.1.3) :pypi:`pytest-tdd` run pytest on a python module Aug 18, 2023 4 - Beta N/A :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` Pytest to Telegram reporting plugin Apr 25, 2024 5 - Production/Stable N/A :pypi:`pytest-telegram-notifier` Telegram notification plugin for Pytest Jun 27, 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) @@ -1454,7 +1457,7 @@ This list contains 1448 plugins. :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-xiuyu` This is a pytest plugin Jul 25, 2023 5 - Production/Stable 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 22, 2024 N/A N/A + :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Apr 23, 2024 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. Mar 31, 2024 4 - Beta pytest>=2.8 :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A @@ -1464,7 +1467,7 @@ This list contains 1448 plugins. :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-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Oct 01, 2023 4 - Beta pytest >=7.1.0 :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 Apr 19, 2024 N/A pytest>=7.4.0 + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Apr 23, 2024 N/A pytest>=7.4.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 19, 2023 N/A pytest (>=7.2.0) @@ -1761,9 +1764,9 @@ This list contains 1448 plugins. pytest-annotate: Generate PyAnnotate annotations from your pytest tests. :pypi:`pytest-ansible` - *last release*: Jan 18, 2024, + *last release*: Apr 22, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=6 + *requires*: pytest>=6 Plugin for pytest to simplify calling ansible modules from tests or fixtures @@ -2083,9 +2086,9 @@ This list contains 1448 plugins. automatically check condition and log all the checks :pypi:`pytest-automation` - *last release*: May 20, 2022, + *last release*: Apr 24, 2024, *status*: N/A, - *requires*: pytest (>=7.0.0) + *requires*: pytest>=7.0.0 pytest plugin for building a test suite, using YAML files to extend pytest parameterize functionality. @@ -2265,7 +2268,7 @@ This list contains 1448 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Apr 19, 2024, + *last release*: Apr 26, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -3118,6 +3121,13 @@ This list contains 1448 plugins. A plugin to run tests written with the Contexts framework using pytest + :pypi:`pytest-continuous` + *last release*: Apr 23, 2024, + *status*: N/A, + *requires*: N/A + + A pytest plugin to run tests continuously until failure or interruption. + :pypi:`pytest-cookies` *last release*: Mar 22, 2023, *status*: 5 - Production/Stable, @@ -3357,7 +3367,7 @@ This list contains 1448 plugins. pytest fixtures to run dash applications. :pypi:`pytest-dashboard` - *last release*: Apr 18, 2024, + *last release*: Apr 22, 2024, *status*: N/A, *requires*: pytest<8.0.0,>=7.4.3 @@ -3371,7 +3381,7 @@ This list contains 1448 plugins. Useful functions for managing data for pytest fixtures :pypi:`pytest-databases` - *last release*: Apr 19, 2024, + *last release*: Apr 21, 2024, *status*: 4 - Beta, *requires*: pytest @@ -4302,56 +4312,56 @@ This list contains 1448 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Apr 09, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -4526,7 +4536,7 @@ This list contains 1448 plugins. Applies eventlet monkey-patch as a pytest plugin. :pypi:`pytest-evm` - *last release*: Apr 20, 2024, + *last release*: Apr 22, 2024, *status*: 4 - Beta, *requires*: pytest<9.0.0,>=8.1.1 @@ -4833,6 +4843,13 @@ This list contains 1448 plugins. py.test plugin that activates the fault handler module for tests (dummy package) + :pypi:`pytest-fauna` + *last release*: Apr 22, 2024, + *status*: N/A, + *requires*: N/A + + A collection of helpful test fixtures for Fauna DB. + :pypi:`pytest-fauxfactory` *last release*: Dec 06, 2017, *status*: 5 - Production/Stable, @@ -5387,9 +5404,9 @@ This list contains 1448 plugins. Utility to select tests that have had its dependencies modified (as identified by git diff) :pypi:`pytest-glamor-allure` - *last release*: Jul 22, 2022, + *last release*: Apr 22, 2024, *status*: 4 - Beta, - *requires*: pytest + *requires*: pytest<=8.1.1 Extends allure-pytest functionality @@ -5597,7 +5614,7 @@ This list contains 1448 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Apr 13, 2024, + *last release*: Apr 25, 2024, *status*: 3 - Alpha, *requires*: pytest==8.1.1 @@ -5814,7 +5831,7 @@ This list contains 1448 plugins. help hypo module for pytest :pypi:`pytest-iam` - *last release*: Apr 12, 2024, + *last release*: Apr 22, 2024, *status*: 3 - Alpha, *requires*: pytest>=7.0.0 @@ -5856,7 +5873,7 @@ This list contains 1448 plugins. Pytest plugin for testing function idempotence. :pypi:`pytest-ignore-flaky` - *last release*: Apr 08, 2024, + *last release*: Apr 20, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=6.0 @@ -6913,7 +6930,7 @@ This list contains 1448 plugins. A plugin to test mp :pypi:`pytest-minio-mock` - *last release*: Apr 15, 2024, + *last release*: Apr 27, 2024, *status*: N/A, *requires*: pytest>=5.0.0 @@ -7220,6 +7237,13 @@ This list contains 1448 plugins. Substitute for "pytest-mypy-plugins" for Python implementations which aren't supported by mypy. + :pypi:`pytest-mypy-runner` + *last release*: Apr 23, 2024, + *status*: N/A, + *requires*: pytest>=8.0 + + Run the mypy static type checker as a pytest test case + :pypi:`pytest-mypy-testing` *last release*: Mar 04, 2024, *status*: N/A, @@ -8978,7 +9002,7 @@ This list contains 1448 plugins. Rerun testsuite for a certain time or iterations :pypi:`pytest-rerunclassfailures` - *last release*: Mar 29, 2024, + *last release*: Apr 24, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=7.2 @@ -9342,7 +9366,7 @@ This list contains 1448 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: Apr 14, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9405,7 +9429,7 @@ This list contains 1448 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Apr 14, 2024, + *last release*: Apr 26, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9440,7 +9464,7 @@ This list contains 1448 plugins. Send pytest execution result email :pypi:`pytest-sentry` - *last release*: Apr 05, 2024, + *last release*: Apr 25, 2024, *status*: N/A, *requires*: pytest @@ -10308,7 +10332,7 @@ This list contains 1448 plugins. py.test plugin to introduce block structure in teamcity build log, if output is not captured :pypi:`pytest-telegram` - *last release*: Dec 10, 2020, + *last release*: Apr 25, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -11428,7 +11452,7 @@ This list contains 1448 plugins. Extended logging for test and decorators :pypi:`pytest-xlsx` - *last release*: Mar 22, 2024, + *last release*: Apr 23, 2024, *status*: N/A, *requires*: N/A @@ -11498,7 +11522,7 @@ This list contains 1448 plugins. This plugin is used to load yaml output to your test using pytest framework. :pypi:`pytest-yaml-sanmu` - *last release*: Apr 19, 2024, + *last release*: Apr 23, 2024, *status*: N/A, *requires*: pytest>=7.4.0 From 1a84d233f3a64cab72795b236523bdff95781c81 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 27 Apr 2024 01:41:45 +0300 Subject: [PATCH 006/199] terminal: some minor code cleanups No logical changed intended. --- src/_pytest/terminal.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 724d5c54d2f..c0ca97df6c1 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -432,7 +432,7 @@ def hasopt(self, char: str) -> bool: char = {"xfailed": "x", "skipped": "s"}.get(char, char) return char in self.reportchars - def write_fspath_result(self, nodeid: str, res, **markup: bool) -> None: + def write_fspath_result(self, nodeid: str, res: str, **markup: bool) -> None: fspath = self.config.rootpath / nodeid.split("::")[0] if self.currentfspath is None or fspath != self.currentfspath: if self.currentfspath is not None and self._show_progress_info: @@ -565,10 +565,11 @@ def pytest_deselected(self, items: Sequence[Item]) -> None: def pytest_runtest_logstart( self, nodeid: str, location: Tuple[str, Optional[int], str] ) -> None: + fspath, lineno, domain = location # Ensure that the path is printed before the # 1st test of a module starts running. if self.showlongtestinfo: - line = self._locationline(nodeid, *location) + line = self._locationline(nodeid, fspath, lineno, domain) self.write_ensure_prefix(line, "") self.flush() elif self.showfspath: @@ -591,7 +592,6 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: if not letter and not word: # Probably passed setup/teardown. return - running_xdist = hasattr(rep, "node") if markup is None: was_xfail = hasattr(report, "wasxfail") if rep.passed and not was_xfail: @@ -609,6 +609,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: else: self._progress_nodeids_reported.add(rep.nodeid) line = self._locationline(rep.nodeid, *rep.location) + running_xdist = hasattr(rep, "node") if not running_xdist: self.write_ensure_prefix(line, word, **markup) if rep.skipped or hasattr(report, "wasxfail"): @@ -649,38 +650,26 @@ def _is_last_item(self) -> bool: return len(self._progress_nodeids_reported) == self._session.testscollected def pytest_runtest_logfinish(self, nodeid: str) -> None: - assert self._session if ( self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 and self._show_progress_info ): - if self._show_progress_info == "count": - num_tests = self._session.testscollected - progress_length = len(f" [{num_tests}/{num_tests}]") - else: - progress_length = len(" [100%]") - self._progress_nodeids_reported.add(nodeid) if self._is_last_item: self._write_progress_information_filling_space() else: - main_color, _ = self._get_main_color() - w = self._width_of_current_line - past_edge = w + progress_length + 1 >= self._screen_width - if past_edge: - msg = self._get_progress_information_message() - self._tw.write(msg + "\n", **{main_color: True}) + self._write_progress_information_if_past_edge() def _get_progress_information_message(self) -> str: assert self._session collected = self._session.testscollected if self._show_progress_info == "count": if collected: - progress = self._progress_nodeids_reported + progress = len(self._progress_nodeids_reported) counter_format = f"{{:{len(str(collected))}d}}" format_string = f" [{counter_format}/{{}}]" - return format_string.format(len(progress), collected) + return format_string.format(progress, collected) return f" [ {collected} / {collected} ]" else: if collected: @@ -689,6 +678,20 @@ def _get_progress_information_message(self) -> str: ) return " [100%]" + def _write_progress_information_if_past_edge(self) -> None: + w = self._width_of_current_line + if self._show_progress_info == "count": + assert self._session + num_tests = self._session.testscollected + progress_length = len(f" [{num_tests}/{num_tests}]") + else: + progress_length = len(" [100%]") + past_edge = w + progress_length + 1 >= self._screen_width + if past_edge: + main_color, _ = self._get_main_color() + msg = self._get_progress_information_message() + self._tw.write(msg + "\n", **{main_color: True}) + def _write_progress_information_filling_space(self) -> None: color, _ = self._get_main_color() msg = self._get_progress_information_message() @@ -937,7 +940,7 @@ def mkrel(nodeid: str) -> str: line += "[".join(values) return line - # collect_fspath comes from testid which has a "/"-normalized path. + # fspath comes from testid which has a "/"-normalized path. if fspath: res = mkrel(nodeid) if self.verbosity >= 2 and nodeid.split("::")[0] != fspath.replace( From 50d1e81713af75ab6d3608d2920ddf21bf53d765 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 27 Apr 2024 13:50:31 +0300 Subject: [PATCH 007/199] terminal: fix progress percentages not aligning correctly in xdist Fix #7166 --- changelog/7166.bugfix.rst | 1 + src/_pytest/terminal.py | 24 +++++++++++++++++------- 2 files changed, 18 insertions(+), 7 deletions(-) create mode 100644 changelog/7166.bugfix.rst diff --git a/changelog/7166.bugfix.rst b/changelog/7166.bugfix.rst new file mode 100644 index 00000000000..98e6821f2ff --- /dev/null +++ b/changelog/7166.bugfix.rst @@ -0,0 +1 @@ +Fixed progress percentages (the ``[ 87%]`` at the edge of the screen) sometimes not aligning correctly when running with pytest-xdist ``-n``. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index c0ca97df6c1..f4b6e9b40ec 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -604,10 +604,18 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: markup = {"yellow": True} else: markup = {} + self._progress_nodeids_reported.add(rep.nodeid) if self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0: self._tw.write(letter, **markup) + # When running in xdist, the logreport and logfinish of multiple + # items are interspersed, e.g. `logreport`, `logreport`, + # `logfinish`, `logfinish`. To avoid the "past edge" calculation + # from getting confused and overflowing (#7166), do the past edge + # printing here and not in logfinish, except for the 100% which + # should only be printed after all teardowns are finished. + if self._show_progress_info and not self._is_last_item: + self._write_progress_information_if_past_edge() else: - self._progress_nodeids_reported.add(rep.nodeid) line = self._locationline(rep.nodeid, *rep.location) running_xdist = hasattr(rep, "node") if not running_xdist: @@ -649,17 +657,19 @@ def _is_last_item(self) -> bool: assert self._session is not None return len(self._progress_nodeids_reported) == self._session.testscollected - def pytest_runtest_logfinish(self, nodeid: str) -> None: + @hookimpl(wrapper=True) + def pytest_runtestloop(self) -> Generator[None, object, object]: + result = yield + + # Write the final/100% progress -- deferred until the loop is complete. if ( self.config.get_verbosity(Config.VERBOSITY_TEST_CASES) <= 0 and self._show_progress_info + and self._progress_nodeids_reported ): - self._progress_nodeids_reported.add(nodeid) + self._write_progress_information_filling_space() - if self._is_last_item: - self._write_progress_information_filling_space() - else: - self._write_progress_information_if_past_edge() + return result def _get_progress_information_message(self) -> str: assert self._session From 0b91d5e3e869d00c01c614d827745a9ed4cddb43 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 11:44:55 +0300 Subject: [PATCH 008/199] fixtures: fix tracebacks for higher-scoped failed fixtures getting longer and longer Fix #12204. --- changelog/12204.bugfix.rst | 4 ++++ src/_pytest/fixtures.py | 11 ++++++----- testing/python/fixtures.py | 22 ++++++++++++++++++++++ 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 changelog/12204.bugfix.rst diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst new file mode 100644 index 00000000000..b89a04827f0 --- /dev/null +++ b/changelog/12204.bugfix.rst @@ -0,0 +1,4 @@ +Fix a regression in pytest 8.0 where tracebacks get longer and longer when multiple tests fail due to a shared higher-scope fixture which raised. + +The fix necessitated internal changes which may affect some plugins: +- ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)`` instead of ``exc``. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 09fd07422fc..5f10d565f32 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -8,6 +8,7 @@ import os from pathlib import Path import sys +import types from typing import AbstractSet from typing import Any from typing import Callable @@ -104,8 +105,8 @@ None, # Cache key. object, - # Exception if raised. - BaseException, + # The exception and the original traceback. + Tuple[BaseException, Optional[types.TracebackType]], ], ] @@ -1049,8 +1050,8 @@ def execute(self, request: SubRequest) -> FixtureValue: # numpy arrays (#6497). if my_cache_key is cache_key: if self.cached_result[2] is not None: - exc = self.cached_result[2] - raise exc + exc, exc_tb = self.cached_result[2] + raise exc.with_traceback(exc_tb) else: result = self.cached_result[0] return result @@ -1126,7 +1127,7 @@ def pytest_fixture_setup( # Don't show the fixture as the skip location, as then the user # wouldn't know which test skipped. e._use_item_location = True - fixturedef.cached_result = (None, my_cache_key, e) + fixturedef.cached_result = (None, my_cache_key, (e, e.__traceback__)) raise fixturedef.cached_result = (result, my_cache_key, None) return result diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 12ca6e92630..77914fed75d 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3397,6 +3397,28 @@ def test_something(): ["*def gen(qwe123):*", "*fixture*qwe123*not found*", "*1 error*"] ) + def test_cached_exception_doesnt_get_longer(self, pytester: Pytester) -> None: + """Regression test for #12204.""" + pytester.makepyfile( + """ + import pytest + @pytest.fixture(scope="session") + def bad(): 1 / 0 + + def test_1(bad): pass + def test_2(bad): pass + def test_3(bad): pass + """ + ) + + result = pytester.runpytest_inprocess("--tb=native") + assert result.ret == ExitCode.TESTS_FAILED + failures = result.reprec.getfailures() # type: ignore[attr-defined] + assert len(failures) == 3 + lines1 = failures[1].longrepr.reprtraceback.reprentries[0].lines + lines2 = failures[2].longrepr.reprtraceback.reprentries[0].lines + assert len(lines1) == len(lines2) + class TestShowFixtures: def test_funcarg_compat(self, pytester: Pytester) -> None: From 3e81cb2f455fa091bc5a78742d190437f8281c68 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 11:47:37 +0300 Subject: [PATCH 009/199] runner: fix tracebacks for failed collectors getting longer and longer Refs https://github.com/pytest-dev/pytest/issues/12204#issuecomment-2081239376 --- changelog/12204.bugfix.rst | 3 +++ src/_pytest/runner.py | 14 ++++++++++---- testing/test_runner.py | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst index b89a04827f0..9690f513a38 100644 --- a/changelog/12204.bugfix.rst +++ b/changelog/12204.bugfix.rst @@ -1,4 +1,7 @@ Fix a regression in pytest 8.0 where tracebacks get longer and longer when multiple tests fail due to a shared higher-scope fixture which raised. +Also fix a similar regression in pytest 5.4 for collectors which raise during setup. + The fix necessitated internal changes which may affect some plugins: - ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)`` instead of ``exc``. +- ``SetupState.stack`` failures are now a tuple ``(exc, tb)`` instead of ``exc``. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index a551f715aa8..bf4d9a37f60 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -5,6 +5,7 @@ import dataclasses import os import sys +import types from typing import Callable from typing import cast from typing import Dict @@ -488,8 +489,13 @@ def __init__(self) -> None: Tuple[ # Node's finalizers. List[Callable[[], object]], - # Node's exception, if its setup raised. - Optional[Union[OutcomeException, Exception]], + # Node's exception and original traceback, if its setup raised. + Optional[ + Tuple[ + Union[OutcomeException, Exception], + Optional[types.TracebackType], + ] + ], ], ] = {} @@ -502,7 +508,7 @@ def setup(self, item: Item) -> None: for col, (finalizers, exc) in self.stack.items(): assert col in needed_collectors, "previous item was not torn down properly" if exc: - raise exc + raise exc[0].with_traceback(exc[1]) for col in needed_collectors[len(self.stack) :]: assert col not in self.stack @@ -511,7 +517,7 @@ def setup(self, item: Item) -> None: try: col.setup() except TEST_OUTCOME as exc: - self.stack[col] = (self.stack[col][0], exc) + self.stack[col] = (self.stack[col][0], (exc, exc.__traceback__)) raise def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: diff --git a/testing/test_runner.py b/testing/test_runner.py index 8b41ec28a38..ecb98f2ffc2 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -142,6 +142,43 @@ def raiser(exc): assert isinstance(func.exceptions[0], TypeError) # type: ignore assert isinstance(func.exceptions[1], ValueError) # type: ignore + def test_cached_exception_doesnt_get_longer(self, pytester: Pytester) -> None: + """Regression test for #12204 (the "BTW" case).""" + pytester.makepyfile(test="") + # If the collector.setup() raises, all collected items error with this + # exception. + pytester.makeconftest( + """ + import pytest + + class MyItem(pytest.Item): + def runtest(self) -> None: pass + + class MyBadCollector(pytest.Collector): + def collect(self): + return [ + MyItem.from_parent(self, name="one"), + MyItem.from_parent(self, name="two"), + MyItem.from_parent(self, name="three"), + ] + + def setup(self): + 1 / 0 + + def pytest_collect_file(file_path, parent): + if file_path.name == "test.py": + return MyBadCollector.from_parent(parent, name='bad') + """ + ) + + result = pytester.runpytest_inprocess("--tb=native") + assert result.ret == ExitCode.TESTS_FAILED + failures = result.reprec.getfailures() # type: ignore[attr-defined] + assert len(failures) == 3 + lines1 = failures[1].longrepr.reprtraceback.reprentries[0].lines + lines2 = failures[2].longrepr.reprtraceback.reprentries[0].lines + assert len(lines1) == len(lines2) + class BaseFunctionalTests: def test_passfunction(self, pytester: Pytester) -> None: From 85bc9ca9541e83ba6f171edc60daec9503054633 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 17:03:52 +0300 Subject: [PATCH 010/199] pre-commit: remove deprecated `fix-encoding-pragma` hook Deprecated by pre-commit. --- .pre-commit-config.yaml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a80edd28cdc..d54c08136f2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,8 +10,6 @@ repos: hooks: - id: trailing-whitespace - id: end-of-file-fixer - - id: fix-encoding-pragma - args: [--remove] - id: check-yaml - id: debug-statements exclude: _pytest/(debugging|hookspec).py From e847b2a5a99d8d9af77c23c625cdb6121c04ad31 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 17:04:27 +0300 Subject: [PATCH 011/199] pre-commit: replace `debug-statements` hook with ruff Ruff supports this functionality so we can use it. --- .pre-commit-config.yaml | 3 --- pyproject.toml | 1 + src/_pytest/debugging.py | 1 + src/_pytest/hookspec.py | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d54c08136f2..0225a6a2216 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,9 +11,6 @@ repos: - id: trailing-whitespace - id: end-of-file-fixer - id: check-yaml - - id: debug-statements - exclude: _pytest/(debugging|hookspec).py - language_version: python3 - repo: https://github.com/adamchainz/blacken-docs rev: 1.16.0 hooks: diff --git a/pyproject.toml b/pyproject.toml index 43efacf09f8..2be02ee7e23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -107,6 +107,7 @@ select = [ "PLE", # pylint error "PLW", # pylint warning "PLR1714", # Consider merging multiple comparisons + "T100", # flake8-debugger ] ignore = [ # bugbear ignore diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 6ed0c5c7aee..7beab563ef8 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -1,4 +1,5 @@ # mypy: allow-untyped-defs +# ruff: noqa: T100 """Interactive debugging with PDB, the Python Debugger.""" import argparse diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index acfe7eb9587..01d3b664080 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -1,4 +1,5 @@ # mypy: allow-untyped-defs +# ruff: noqa: T100 """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" From 940b78232e48c34501cfe6e0bfd0ea6d64f4521b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 28 Apr 2024 17:11:11 +0300 Subject: [PATCH 012/199] pre-commit: fix misleading indentation --- .pre-commit-config.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0225a6a2216..6cebeeb4617 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -45,13 +45,13 @@ repos: additional_dependencies: ["tox>=4.9"] - repo: local hooks: - - id: pylint - name: pylint - entry: pylint - language: system - types: [python] - args: ["-rn", "-sn", "--fail-on=I"] - stages: [manual] + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + args: ["-rn", "-sn", "--fail-on=I"] + stages: [manual] - id: rst name: rst entry: rst-lint --encoding utf-8 From 2ede8778d063756663c508f0a39c949d4943e03d Mon Sep 17 00:00:00 2001 From: dj <112573278+dheerajck@users.noreply.github.com> Date: Mon, 29 Apr 2024 00:22:29 +0530 Subject: [PATCH 013/199] Document using PYTEST_VERSION to detect if a code is running inside pytest (#12153) Related to #9502 --- changelog/12153.doc.rst | 1 + doc/en/example/simple.rst | 25 +++++-------------------- 2 files changed, 6 insertions(+), 20 deletions(-) create mode 100644 changelog/12153.doc.rst diff --git a/changelog/12153.doc.rst b/changelog/12153.doc.rst new file mode 100644 index 00000000000..ac36becf9a7 --- /dev/null +++ b/changelog/12153.doc.rst @@ -0,0 +1 @@ +Documented using :envvar:`PYTEST_VERSION` to detect if code is running from within a pytest run. diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index 7064f61f0e2..dec1bed5f80 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -405,35 +405,20 @@ Detect if running from within a pytest run Usually it is a bad idea to make application code behave differently if called from a test. But if you absolutely must find out if your application code is -running from a test you can do something like this: +running from a test you can do this: .. code-block:: python - # content of your_module.py + import os - _called_from_test = False - -.. code-block:: python - - # content of conftest.py - - - def pytest_configure(config): - your_module._called_from_test = True - -and then check for the ``your_module._called_from_test`` flag: - -.. code-block:: python - - if your_module._called_from_test: - # called from within a test run + if os.environ.get("PYTEST_VERSION") is not None: + # Things you want to to do if your code is called by pytest. ... else: - # called "normally" + # Things you want to to do if your code is not called by pytest. ... -accordingly in your application. Adding info to test report header -------------------------------------------------------------- From cf90008a1a3924f323543bfdfc2e86c65eeae941 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 08:38:05 -0300 Subject: [PATCH 014/199] build(deps): Bump peter-evans/create-pull-request from 6.0.4 to 6.0.5 (#12268) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6.0.4 to 6.0.5. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/9153d834b60caba6d51c9b9510b087acf9f33f83...6d6857d36972b65feb161a90e484f2984215f83e) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/update-plugin-list.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 6943e207608..1015d01c9c6 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -46,7 +46,7 @@ jobs: run: python scripts/update-plugin-list.py - name: Create Pull Request - uses: peter-evans/create-pull-request@9153d834b60caba6d51c9b9510b087acf9f33f83 + uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e with: commit-message: '[automated] Update plugin list' author: 'pytest bot ' From b660596f614f7a67dcf5f6f863345600840df18e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 29 Apr 2024 20:50:34 +0300 Subject: [PATCH 015/199] testing: restore integration testing with pytest-bdd The problem was fixed. --- testing/plugins_integration/requirements.txt | 4 +--- tox.ini | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 9e152f1191b..d60bc5d3c08 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,9 +1,7 @@ anyio[curio,trio]==4.3.0 django==5.0.4 pytest-asyncio==0.23.6 -# Temporarily not installed until pytest-bdd is fixed: -# https://github.com/pytest-dev/pytest/pull/11785 -# pytest-bdd==7.0.1 +pytest-bdd==7.1.2 pytest-cov==5.0.0 pytest-django==4.8.0 pytest-flakes==4.0.5 diff --git a/tox.ini b/tox.ini index cb3ca4b8366..4e1ff111966 100644 --- a/tox.ini +++ b/tox.ini @@ -134,11 +134,9 @@ changedir = testing/plugins_integration deps = -rtesting/plugins_integration/requirements.txt setenv = PYTHONPATH=. -# Command temporarily removed until pytest-bdd is fixed: -# https://github.com/pytest-dev/pytest/pull/11785 -# pytest bdd_wallet.py commands = pip check + pytest bdd_wallet.py pytest --cov=. simple_integration.py pytest --ds=django_settings simple_integration.py pytest --html=simple.html simple_integration.py From da53e297807ddf85c74052b8ae32dc45404d1f63 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 29 Apr 2024 22:30:08 +0000 Subject: [PATCH 016/199] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.1 → v0.4.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.1...v0.4.2) - [github.com/pre-commit/mirrors-mypy: v1.9.0 → v1.10.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.9.0...v1.10.0) --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6cebeeb4617..c3ebc88501f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.1" + rev: "v0.4.2" hooks: - id: ruff args: ["--fix"] @@ -21,7 +21,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.9.0 + rev: v1.10.0 hooks: - id: mypy files: ^(src/|testing/|scripts/) From 4788165e69d08e10fc6b9c0124083fb358e2e9b0 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 30 Apr 2024 18:06:26 +0200 Subject: [PATCH 017/199] [ruff UP031] Fix to use format specifiers instead of percent format --- bench/bench.py | 2 +- doc/en/conf.py | 2 +- extra/get_issues.py | 4 ++-- src/_pytest/_code/code.py | 2 +- src/_pytest/_io/pprint.py | 2 +- src/_pytest/_io/terminalwriter.py | 2 +- src/_pytest/_py/path.py | 6 +++--- src/_pytest/assertion/rewrite.py | 16 +++++++------- src/_pytest/assertion/util.py | 8 +++---- src/_pytest/cacheprovider.py | 10 ++++----- src/_pytest/capture.py | 2 +- src/_pytest/config/__init__.py | 15 +++++++------ src/_pytest/config/argparsing.py | 20 ++++++++++-------- src/_pytest/debugging.py | 3 +-- src/_pytest/doctest.py | 6 +++--- src/_pytest/fixtures.py | 4 ++-- src/_pytest/helpconfig.py | 6 +++--- src/_pytest/junitxml.py | 8 +++---- src/_pytest/mark/__init__.py | 2 +- src/_pytest/mark/structures.py | 4 ++-- src/_pytest/pastebin.py | 4 ++-- src/_pytest/pytester.py | 18 +++++++++------- src/_pytest/python.py | 2 +- src/_pytest/reports.py | 6 +++--- src/_pytest/runner.py | 2 +- src/_pytest/skipping.py | 8 +++---- src/_pytest/terminal.py | 6 ++++-- testing/_py/test_local.py | 8 +++---- testing/acceptance_test.py | 6 +++--- testing/code/test_excinfo.py | 2 +- testing/freeze/tox_run.py | 2 +- testing/io/test_saferepr.py | 2 +- testing/python/collect.py | 4 ++-- testing/python/fixtures.py | 5 ++--- testing/test_assertion.py | 6 +++--- testing/test_assertrewrite.py | 6 ++---- testing/test_cacheprovider.py | 2 +- testing/test_capture.py | 9 ++++---- testing/test_collection.py | 13 ++++++------ testing/test_config.py | 28 ++++++++++++------------- testing/test_conftest.py | 14 ++++++------- testing/test_debugging.py | 29 +++++++++++++------------ testing/test_doctest.py | 2 +- testing/test_faulthandler.py | 2 +- testing/test_junitxml.py | 35 +++++++++++++++---------------- testing/test_legacypath.py | 2 +- testing/test_monkeypatch.py | 2 +- testing/test_pastebin.py | 2 +- testing/test_skipping.py | 25 +++++++++------------- testing/test_terminal.py | 12 +++++------ testing/test_tmpdir.py | 6 +++--- testing/test_warnings.py | 20 +++++++++--------- 52 files changed, 202 insertions(+), 212 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 437d3259d83..0bb13c75a15 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -8,7 +8,7 @@ import pytest # noqa: F401 script = sys.argv[1:] if len(sys.argv) > 1 else ["empty.py"] - cProfile.run("pytest.cmdline.main(%r)" % script, "prof") + cProfile.run(f"pytest.cmdline.main({script!r})", "prof") p = pstats.Stats("prof") p.strip_dirs() p.sort_stats("cumulative") diff --git a/doc/en/conf.py b/doc/en/conf.py index 32ecaa17435..af54b468996 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -236,7 +236,7 @@ html_title = "pytest documentation" # A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = "pytest-%s" % release +html_short_title = f"pytest-{release}" # The name of an image file (relative to this directory) to place at the top # of the sidebar. diff --git a/extra/get_issues.py b/extra/get_issues.py index 716233ccba1..a0c2f19adfa 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -60,7 +60,7 @@ def report(issues): kind = _get_kind(issue) status = issue["state"] number = issue["number"] - link = "https://github.com/pytest-dev/pytest/issues/%s/" % number + link = f"https://github.com/pytest-dev/pytest/issues/{number}/" print("----") print(status, kind, link) print(title) @@ -69,7 +69,7 @@ def report(issues): # print("\n".join(lines[:3])) # if len(lines) > 3 or len(body) > 240: # print("...") - print("\n\nFound %s open issues" % len(issues)) + print(f"\n\nFound {len(issues)} open issues") if __name__ == "__main__": diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index c65ce79f7e5..b80d53ca5f9 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -940,7 +940,7 @@ def repr_traceback_entry( s = self.get_source(source, line_index, excinfo, short=short) lines.extend(s) if short: - message = "in %s" % (entry.name) + message = f"in {entry.name}" else: message = excinfo and excinfo.typename or "" entry_path = entry.path diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index 75e9a7123b5..e637eec59e1 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -616,7 +616,7 @@ def _safe_repr( vrepr = self._safe_repr(v, context, maxlevels, level) append(f"{krepr}: {vrepr}") context.remove(objid) - return "{%s}" % ", ".join(components) + return "{{{}}}".format(", ".join(components)) if (issubclass(typ, list) and r is list.__repr__) or ( issubclass(typ, tuple) and r is tuple.__repr__ diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index deb6ecc3c94..5bcd0592778 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -104,7 +104,7 @@ def markup(self, text: str, **markup: bool) -> str: if self.hasmarkup: esc = [self._esctable[name] for name, on in markup.items() if on] if esc: - text = "".join("\x1b[%sm" % cod for cod in esc) + text + "\x1b[0m" + text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" return text def sep( diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py index 7bb3693f938..27b115cfd87 100644 --- a/src/_pytest/_py/path.py +++ b/src/_pytest/_py/path.py @@ -659,7 +659,7 @@ def new(self, **kw): ) if "basename" in kw: if "purebasename" in kw or "ext" in kw: - raise ValueError("invalid specification %r" % kw) + raise ValueError(f"invalid specification {kw!r}") else: pb = kw.setdefault("purebasename", purebasename) try: @@ -705,7 +705,7 @@ def _getbyspec(self, spec: str) -> list[str]: elif name == "ext": res.append(ext) else: - raise ValueError("invalid part specification %r" % name) + raise ValueError(f"invalid part specification {name!r}") return res def dirpath(self, *args, **kwargs): @@ -1026,7 +1026,7 @@ def atime(self): return self.stat().atime def __repr__(self): - return "local(%r)" % self.strpath + return f"local({self.strpath!r})" def __str__(self): """Return string representation of the Path.""" diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 678471ee992..b6f14aa9294 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -101,7 +101,7 @@ def find_spec( state = self.config.stash[assertstate_key] if self._early_rewrite_bailout(name, state): return None - state.trace("find_module called for: %s" % name) + state.trace(f"find_module called for: {name}") # Type ignored because mypy is confused about the `self` binding here. spec = self._find_spec(name, path) # type: ignore @@ -273,7 +273,7 @@ def _warn_already_imported(self, name: str) -> None: self.config.issue_config_time_warning( PytestAssertRewriteWarning( - "Module already imported so cannot be rewritten: %s" % name + f"Module already imported so cannot be rewritten: {name}" ), stacklevel=5, ) @@ -374,21 +374,21 @@ def _read_pyc( return None # Check for invalid or out of date pyc file. if len(data) != (16): - trace("_read_pyc(%s): invalid pyc (too short)" % source) + trace(f"_read_pyc({source}): invalid pyc (too short)") return None if data[:4] != importlib.util.MAGIC_NUMBER: - trace("_read_pyc(%s): invalid pyc (bad magic number)" % source) + trace(f"_read_pyc({source}): invalid pyc (bad magic number)") return None if data[4:8] != b"\x00\x00\x00\x00": - trace("_read_pyc(%s): invalid pyc (unsupported flags)" % source) + trace(f"_read_pyc({source}): invalid pyc (unsupported flags)") return None mtime_data = data[8:12] if int.from_bytes(mtime_data, "little") != mtime & 0xFFFFFFFF: - trace("_read_pyc(%s): out of date" % source) + trace(f"_read_pyc({source}): out of date") return None size_data = data[12:16] if int.from_bytes(size_data, "little") != size & 0xFFFFFFFF: - trace("_read_pyc(%s): invalid pyc (incorrect size)" % source) + trace(f"_read_pyc({source}): invalid pyc (incorrect size)") return None try: co = marshal.load(fp) @@ -396,7 +396,7 @@ def _read_pyc( trace(f"_read_pyc({source}): marshal.load error {e}") return None if not isinstance(co, types.CodeType): - trace("_read_pyc(%s): not a code object" % source) + trace(f"_read_pyc({source}): not a code object") return None return co diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index cb671641041..008eabf9a1f 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -292,7 +292,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: if i > 42: i -= 10 # Provide some context explanation = [ - "Skipping %s identical leading characters in diff, use -v to show" % i + f"Skipping {i} identical leading characters in diff, use -v to show" ] left = left[i:] right = right[i:] @@ -493,7 +493,7 @@ def _compare_eq_dict( common = set_left.intersection(set_right) same = {k: left[k] for k in common if left[k] == right[k]} if same and verbose < 2: - explanation += ["Omitting %s identical items, use -vv to show" % len(same)] + explanation += [f"Omitting {len(same)} identical items, use -vv to show"] elif same: explanation += ["Common items:"] explanation += highlighter(pprint.pformat(same)).splitlines() @@ -560,7 +560,7 @@ def _compare_eq_cls( if same or diff: explanation += [""] if same and verbose < 2: - explanation.append("Omitting %s identical items, use -vv to show" % len(same)) + explanation.append(f"Omitting {len(same)} identical items, use -vv to show") elif same: explanation += ["Matching attributes:"] explanation += highlighter(pprint.pformat(same)).splitlines() @@ -590,7 +590,7 @@ def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: tail = text[index + len(term) :] correct_text = head + tail diff = _diff_text(text, correct_text, verbose) - newdiff = ["%s is contained here:" % saferepr(term, maxsize=42)] + newdiff = [f"{saferepr(term, maxsize=42)} is contained here:"] for line in diff: if line.startswith("Skipping"): continue diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index e9f66f1f44f..4593e2a8172 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -332,7 +332,7 @@ def get_last_failed_paths(self) -> Set[Path]: def pytest_report_collectionfinish(self) -> Optional[str]: if self.active and self.config.getoption("verbose") >= 0: - return "run-last-failure: %s" % self._report_status + return f"run-last-failure: {self._report_status}" return None def pytest_runtest_logreport(self, report: TestReport) -> None: @@ -588,21 +588,21 @@ def cacheshow(config: Config, session: Session) -> int: dummy = object() basedir = config.cache._cachedir vdir = basedir / Cache._CACHE_PREFIX_VALUES - tw.sep("-", "cache values for %r" % glob) + tw.sep("-", f"cache values for {glob!r}") for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): key = str(valpath.relative_to(vdir)) val = config.cache.get(key, dummy) if val is dummy: - tw.line("%s contains unreadable content, will be ignored" % key) + tw.line(f"{key} contains unreadable content, will be ignored") else: - tw.line("%s contains:" % key) + tw.line(f"{key} contains:") for line in pformat(val).splitlines(): tw.line(" " + line) ddir = basedir / Cache._CACHE_PREFIX_DIRS if ddir.is_dir(): contents = sorted(ddir.rglob(glob)) - tw.sep("-", "cache directories for %r" % glob) + tw.sep("-", f"cache directories for {glob!r}") for p in contents: # if p.is_dir(): # print("%s/" % p.relative_to(basedir)) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 3f6a2510348..198d4195020 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -738,7 +738,7 @@ def is_capturing(self) -> Union[str, bool]: if self.is_globally_capturing(): return "global" if self._capture_fixture: - return "fixture %s" % self._capture_fixture.request.fixturename + return f"fixture {self._capture_fixture.request.fixturename}" return False # Global capturing control diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 306b14cce28..c8e8e7d29dd 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -798,7 +798,7 @@ def consider_pluginarg(self, arg: str) -> None: if arg.startswith("no:"): name = arg[3:] if name in essential_plugins: - raise UsageError("plugin %s cannot be disabled" % name) + raise UsageError(f"plugin {name} cannot be disabled") # PR #4304: remove stepwise if cacheprovider is blocked. if name == "cacheprovider": @@ -847,9 +847,9 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No # "terminal" or "capture". Those plugins are registered under their # basename for historic purposes but must be imported with the # _pytest prefix. - assert isinstance(modname, str), ( - "module name as text required, got %r" % modname - ) + assert isinstance( + modname, str + ), f"module name as text required, got {modname!r}" if self.is_blocked(modname) or self.get_plugin(modname) is not None: return @@ -892,8 +892,7 @@ def _get_plugin_specs_as_list( if isinstance(specs, collections.abc.Sequence): return list(specs) raise UsageError( - "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r" - % specs + f"Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: {specs!r}" ) @@ -1185,7 +1184,7 @@ def notify_exception( res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) if not any(res): for line in str(excrepr).split("\n"): - sys.stderr.write("INTERNALERROR> %s\n" % line) + sys.stderr.write(f"INTERNALERROR> {line}\n") sys.stderr.flush() def cwd_relative_nodeid(self, nodeid: str) -> str: @@ -1435,7 +1434,7 @@ def _checkversion(self) -> None: if not isinstance(minver, str): raise pytest.UsageError( - "%s: 'minversion' must be a single value" % self.inipath + f"{self.inipath}: 'minversion' must be a single value" ) if Version(minver) > Version(pytest.__version__): diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index 9006351af72..f270b864c6a 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -313,23 +313,23 @@ def _set_opt_strings(self, opts: Sequence[str]) -> None: for opt in opts: if len(opt) < 2: raise ArgumentError( - "invalid option string %r: " - "must be at least two characters long" % opt, + f"invalid option string {opt!r}: " + "must be at least two characters long", self, ) elif len(opt) == 2: if not (opt[0] == "-" and opt[1] != "-"): raise ArgumentError( - "invalid short option string %r: " - "must be of the form -x, (x any non-dash char)" % opt, + f"invalid short option string {opt!r}: " + "must be of the form -x, (x any non-dash char)", self, ) self._short_opts.append(opt) else: if not (opt[0:2] == "--" and opt[2] != "-"): raise ArgumentError( - "invalid long option string %r: " - "must start with --, followed by non-dash" % opt, + f"invalid long option string {opt!r}: " + "must start with --, followed by non-dash", self, ) self._long_opts.append(opt) @@ -383,7 +383,7 @@ def addoption(self, *opts: str, **attrs: Any) -> None: name for opt in self.options for name in opt.names() ) if conflict: - raise ValueError("option names %s already added" % conflict) + raise ValueError(f"option names {conflict} already added") option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=False) @@ -441,7 +441,9 @@ def parse_args( # type: ignore if unrecognized: for arg in unrecognized: if arg and arg[0] == "-": - lines = ["unrecognized arguments: %s" % (" ".join(unrecognized))] + lines = [ + "unrecognized arguments: {}".format(" ".join(unrecognized)) + ] for k, v in sorted(self.extra_info.items()): lines.append(f" {k}: {v}") self.error("\n".join(lines)) @@ -520,7 +522,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: continue if not option.startswith("--"): raise ArgumentError( - 'long optional argument without "--": [%s]' % (option), option + f'long optional argument without "--": [{option}]', option ) xxoption = option[2:] shortened = xxoption.replace("-", "") diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 7beab563ef8..1338ef9f22c 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -181,8 +181,7 @@ def do_continue(self, arg): else: tw.sep( ">", - "PDB continue (IO-capturing resumed for %s)" - % capturing, + f"PDB continue (IO-capturing resumed for {capturing})", ) assert capman is not None capman.resume() diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 7fff99f37b5..35a13676222 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -374,7 +374,7 @@ def repr_failure( # type: ignore[override] ).split("\n") else: inner_excinfo = ExceptionInfo.from_exc_info(failure.exc_info) - lines += ["UNEXPECTED EXCEPTION: %s" % repr(inner_excinfo.value)] + lines += [f"UNEXPECTED EXCEPTION: {inner_excinfo.value!r}"] lines += [ x.strip("\n") for x in traceback.format_exception(*failure.exc_info) ] @@ -382,7 +382,7 @@ def repr_failure( # type: ignore[override] return ReprFailDoctest(reprlocation_lines) def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: - return self.path, self.dtest.lineno, "[doctest] %s" % self.name + return self.path, self.dtest.lineno, f"[doctest] {self.name}" def _get_flag_lookup() -> Dict[str, int]: @@ -563,7 +563,7 @@ def _from_module(self, module, object): module = self.obj except Collector.CollectError: if self.config.getvalue("doctest_ignore_import_errors"): - skip("unable to import module %r" % self.path) + skip(f"unable to import module {self.path!r}") else: raise diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 5f10d565f32..5a290718fe4 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -675,7 +675,7 @@ def node(self): return self._pyfuncitem def __repr__(self) -> str: - return "" % (self.node) + return f"" def _fillfixtures(self) -> None: item = self._pyfuncitem @@ -1897,7 +1897,7 @@ def _showfixtures_main(config: Config, session: "Session") -> None: continue tw.write(f"{argname}", green=True) if fixturedef.scope != "function": - tw.write(" [%s scope]" % fixturedef.scope, cyan=True) + tw.write(f" [{fixturedef.scope} scope]", cyan=True) tw.write(f" -- {prettypath}", yellow=True) tw.write("\n") doc = inspect.getdoc(fixturedef.func) diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 37fbdf04d7e..68e0bd881a7 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -121,11 +121,11 @@ def pytest_cmdline_parse() -> Generator[None, Config, Config]: ) config.trace.root.setwriter(debugfile.write) undo_tracing = config.pluginmanager.enable_tracing() - sys.stderr.write("writing pytest debug information to %s\n" % path) + sys.stderr.write(f"writing pytest debug information to {path}\n") def unset_tracing() -> None: debugfile.close() - sys.stderr.write("wrote pytest debug information to %s\n" % debugfile.name) + sys.stderr.write(f"wrote pytest debug information to {debugfile.name}\n") config.trace.root.setwriter(None) undo_tracing() @@ -185,7 +185,7 @@ def showhelp(config: Config) -> None: if help is None: raise TypeError(f"help argument cannot be None for {name}") spec = f"{name} ({type}):" - tw.write(" %s" % spec) + tw.write(f" {spec}") spec_len = len(spec) if spec_len > (indent_len - 3): # Display help starting at a new line. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 13fc9277aec..011af6100e9 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -53,9 +53,9 @@ def bin_xml_escape(arg: object) -> str: def repl(matchobj: Match[str]) -> str: i = ord(matchobj.group()) if i <= 0xFF: - return "#x%02X" % i + return f"#x{i:02X}" else: - return "#x%04X" % i + return f"#x{i:04X}" # The spec range of valid chars is: # Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] @@ -149,7 +149,7 @@ def record_testreport(self, testreport: TestReport) -> None: self.attrs = temp_attrs def to_xml(self) -> ET.Element: - testcase = ET.Element("testcase", self.attrs, time="%.3f" % self.duration) + testcase = ET.Element("testcase", self.attrs, time=f"{self.duration:.3f}") properties = self.make_properties_node() if properties is not None: testcase.append(properties) @@ -670,7 +670,7 @@ def pytest_sessionfinish(self) -> None: failures=str(self.stats["failure"]), skipped=str(self.stats["skipped"]), tests=str(numtests), - time="%.3f" % suite_time_delta, + time=f"{suite_time_delta:.3f}", timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(), hostname=platform.node(), ) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 77dabd95dec..f5276d8fbda 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -122,7 +122,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: parts = line.split(":", 1) name = parts[0] rest = parts[1] if len(parts) == 2 else "" - tw.write("@pytest.mark.%s:" % name, bold=True) + tw.write(f"@pytest.mark.{name}:", bold=True) tw.line(rest) tw.line() config._ensure_unconfigure() diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index a6503bf1d46..3567142a6d9 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -552,9 +552,9 @@ def __getattr__(self, name: str) -> MarkDecorator: fail(f"Unknown '{name}' mark, did you mean 'parametrize'?") warnings.warn( - "Unknown pytest.mark.%s - is this a typo? You can register " + f"Unknown pytest.mark.{name} - is this a typo? You can register " "custom marks to avoid this warning - for details, see " - "https://docs.pytest.org/en/stable/how-to/mark.html" % name, + "https://docs.pytest.org/en/stable/how-to/mark.html", PytestUnknownMarkWarning, 2, ) diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 533d78c9a2a..20cb8ca83c3 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -65,7 +65,7 @@ def pytest_unconfigure(config: Config) -> None: # Write summary. tr.write_sep("=", "Sending information to Paste Service") pastebinurl = create_new_paste(sessionlog) - tr.write_line("pastebin session-log: %s\n" % pastebinurl) + tr.write_line(f"pastebin session-log: {pastebinurl}\n") def create_new_paste(contents: Union[str, bytes]) -> str: @@ -85,7 +85,7 @@ def create_new_paste(contents: Union[str, bytes]) -> str: urlopen(url, data=urlencode(params).encode("ascii")).read().decode("utf-8") ) except OSError as exc_info: # urllib errors - return "bad response: %s" % exc_info + return f"bad response: {exc_info}" m = re.search(r'href="/raw/(\w+)"', response) if m: return f"{url}/show/{m.group(1)}" diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 23f44da69ca..31c6de78195 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -182,13 +182,13 @@ def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object] leaked_files = [t for t in lines2 if t[0] in new_fds] if leaked_files: error = [ - "***** %s FD leakage detected" % len(leaked_files), + f"***** {len(leaked_files)} FD leakage detected", *(str(f) for f in leaked_files), "*** Before:", *(str(f) for f in lines1), "*** After:", *(str(f) for f in lines2), - "***** %s FD leakage detected" % len(leaked_files), + f"***** {len(leaked_files)} FD leakage detected", "*** function {}:{}: {} ".format(*item.location), "See issue #2366", ] @@ -313,7 +313,7 @@ def popcall(self, name: str) -> RecordedHookCall: del self.calls[i] return call lines = [f"could not find call {name!r}, in:"] - lines.extend([" %s" % x for x in self.calls]) + lines.extend([f" {x}" for x in self.calls]) fail("\n".join(lines)) def getcall(self, name: str) -> RecordedHookCall: @@ -1204,7 +1204,9 @@ def _ensure_basetemp( if str(x).startswith("--basetemp"): break else: - new_args.append("--basetemp=%s" % self.path.parent.joinpath("basetemp")) + new_args.append( + "--basetemp={}".format(self.path.parent.joinpath("basetemp")) + ) return new_args def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: @@ -1485,7 +1487,7 @@ def runpytest_subprocess( """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) - args = ("--basetemp=%s" % p, *args) + args = (f"--basetemp={p}", *args) plugins = [x for x in self.plugins if isinstance(x, str)] if plugins: args = ("-p", plugins[0], *args) @@ -1593,7 +1595,7 @@ def _match_lines_random( self._log("matched: ", repr(line)) break else: - msg = "line %r not found in output" % line + msg = f"line {line!r} not found in output" self._log(msg) self._fail(msg) @@ -1605,7 +1607,7 @@ def get_lines_after(self, fnline: str) -> Sequence[str]: for i, line in enumerate(self.lines): if fnline == line or fnmatch(line, fnline): return self.lines[i + 1 :] - raise ValueError("line %r not found in output" % fnline) + raise ValueError(f"line {fnline!r} not found in output") def _log(self, *args) -> None: self._log_output.append(" ".join(str(x) for x in args)) @@ -1690,7 +1692,7 @@ def _match_lines( started = True break elif match_func(nextline, line): - self._log("%s:" % match_nickname, repr(line)) + self._log(f"{match_nickname}:", repr(line)) self._log( "{:>{width}}".format("with:", width=wnick), repr(nextline) ) diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 5e059f2c4e6..68eceb7f4f3 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -224,7 +224,7 @@ def pytest_pycollect_makeitem( filename, lineno = getfslineno(obj) warnings.warn_explicit( message=PytestCollectionWarning( - "cannot collect %r because it is not a function." % name + f"cannot collect {name!r} because it is not a function." ), category=None, filename=str(filename), diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 70f3212ce7b..2064183d0f7 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -232,10 +232,10 @@ def _report_unserialization_failure( url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() pprint("-" * 100, stream=stream) - pprint("INTERNALERROR: Unknown entry type returned: %s" % type_name, stream=stream) - pprint("report_name: %s" % report_class, stream=stream) + pprint(f"INTERNALERROR: Unknown entry type returned: {type_name}", stream=stream) + pprint(f"report_name: {report_class}", stream=stream) pprint(reportdict, stream=stream) - pprint("Please report this bug at %s" % url, stream=stream) + pprint(f"Please report this bug at {url}", stream=stream) pprint("-" * 100, stream=stream) raise RuntimeError(stream.getvalue()) diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index bf4d9a37f60..eb444f2e322 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -90,7 +90,7 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: if not durations: tr.write_sep("=", "slowest durations") else: - tr.write_sep("=", "slowest %s durations" % durations) + tr.write_sep("=", f"slowest {durations} durations") dlist = dlist[:durations] for i, rep in enumerate(dlist): diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 188dcae3f1c..54500b2851b 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -117,7 +117,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, result = eval(condition_code, globals_) except SyntaxError as exc: msglines = [ - "Error evaluating %r condition" % mark.name, + f"Error evaluating {mark.name!r} condition", " " + condition, " " + " " * (exc.offset or 0) + "^", "SyntaxError: invalid syntax", @@ -125,7 +125,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, fail("\n".join(msglines), pytrace=False) except Exception as exc: msglines = [ - "Error evaluating %r condition" % mark.name, + f"Error evaluating {mark.name!r} condition", " " + condition, *traceback.format_exception_only(type(exc), exc), ] @@ -137,7 +137,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, result = bool(condition) except Exception as exc: msglines = [ - "Error evaluating %r condition as a boolean" % mark.name, + f"Error evaluating {mark.name!r} condition as a boolean", *traceback.format_exception_only(type(exc), exc), ] fail("\n".join(msglines), pytrace=False) @@ -149,7 +149,7 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, else: # XXX better be checked at collection time msg = ( - "Error evaluating %r: " % mark.name + f"Error evaluating {mark.name!r}: " + "you need to specify reason=STRING when using booleans as conditions." ) fail(msg, pytrace=False) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index f4b6e9b40ec..d05eb7e3dce 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -640,7 +640,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: self._write_progress_information_filling_space() else: self.ensure_newline() - self._tw.write("[%s]" % rep.node.gateway.id) + self._tw.write(f"[{rep.node.gateway.id}]") if self._show_progress_info: self._tw.write( self._get_progress_information_message() + " ", cyan=True @@ -818,7 +818,9 @@ def pytest_report_header(self, config: Config) -> List[str]: plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: - result.append("plugins: %s" % ", ".join(_plugin_nameversions(plugininfo))) + result.append( + "plugins: {}".format(", ".join(_plugin_nameversions(plugininfo))) + ) return result def pytest_collection_finish(self, session: "Session") -> None: diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index ad2526571e6..243219babc9 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -551,7 +551,7 @@ 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") + file_.write_text(f"{i}", encoding="utf-8") actual = int(file_.read_text(encoding="utf-8")) assert ( actual == i @@ -563,9 +563,9 @@ def batch_make_numbered_dirs(rootdir, repeats): class TestLocalPath(CommonFSTests): def test_join_normpath(self, tmpdir): assert tmpdir.join(".") == tmpdir - p = tmpdir.join("../%s" % tmpdir.basename) + p = tmpdir.join(f"../{tmpdir.basename}") assert p == tmpdir - p = tmpdir.join("..//%s/" % tmpdir.basename) + p = tmpdir.join(f"..//{tmpdir.basename}/") assert p == tmpdir @skiponwin32 @@ -722,7 +722,7 @@ def test_write_and_ensure(self, path1): @pytest.mark.parametrize("bin", (False, True)) def test_dump(self, tmpdir, bin): - path = tmpdir.join("dumpfile%s" % int(bin)) + path = tmpdir.join(f"dumpfile{int(bin)}") try: d = {"answer": 42} path.dump(d, bin=bin) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 8f001bc2401..ac7fab3d27a 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -400,7 +400,7 @@ def test_docstring_on_hookspec(self) -> None: for name, value in vars(hookspec).items(): if name.startswith("pytest_"): - assert value.__doc__, "no docstring for %s" % name + assert value.__doc__, f"no docstring for {name}" def test_initialization_error_issue49(self, pytester: Pytester) -> None: pytester.makeconftest( @@ -973,7 +973,7 @@ def test_calls_showall(self, pytester: Pytester, mock_timing) -> None: for x in tested: for y in ("call",): # 'setup', 'call', 'teardown': for line in result.stdout.lines: - if ("test_%s" % x) in line and y in line: + if (f"test_{x}") in line and y in line: break else: raise AssertionError(f"not found {x} {y}") @@ -986,7 +986,7 @@ def test_calls_showall_verbose(self, pytester: Pytester, mock_timing) -> None: for x in "123": for y in ("call",): # 'setup', 'call', 'teardown': for line in result.stdout.lines: - if ("test_%s" % x) in line and y in line: + if (f"test_{x}") in line and y in line: break else: raise AssertionError(f"not found {x} {y}") diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index dd4bd22c8b8..f2c689a137e 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1406,7 +1406,7 @@ def g(): mod.f() # emulate the issue described in #1984 - attr = "__%s__" % reason + attr = f"__{reason}__" getattr(excinfo.value, attr).__traceback__ = None r = excinfo.getrepr() diff --git a/testing/freeze/tox_run.py b/testing/freeze/tox_run.py index 7fd63cf1218..1230fcce140 100644 --- a/testing/freeze/tox_run.py +++ b/testing/freeze/tox_run.py @@ -10,4 +10,4 @@ executable = os.path.join(os.getcwd(), "dist", "runtests_script", "runtests_script") if sys.platform.startswith("win"): executable += ".exe" - sys.exit(os.system("%s tests" % executable)) + sys.exit(os.system(f"{executable} tests")) diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index 5d270f1756c..f627434c4e9 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -144,7 +144,7 @@ def test_big_repr(): def test_repr_on_newstyle() -> None: class Function: def __repr__(self): - return "<%s>" % (self.name) # type: ignore[attr-defined] + return f"<{self.name}>" # type: ignore[attr-defined] assert saferepr(Function()) diff --git a/testing/python/collect.py b/testing/python/collect.py index 745550f0775..a1a7dc8978d 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -36,9 +36,9 @@ def test_import_duplicate(self, pytester: Pytester) -> None: [ "*import*mismatch*", "*imported*test_whatever*", - "*%s*" % p1, + f"*{p1}*", "*not the same*", - "*%s*" % p2, + f"*{p2}*", "*HINT*", ] ) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 77914fed75d..aec0deb99a1 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -2268,18 +2268,17 @@ def test_override_parametrized_fixture_issue_979( This was a regression introduced in the fix for #736. """ pytester.makepyfile( - """ + f""" import pytest @pytest.fixture(params=[1, 2]) def fixt(request): return request.param - @pytest.mark.parametrize(%s, [(3, 'x'), (4, 'x')]) + @pytest.mark.parametrize({param_args}, [(3, 'x'), (4, 'x')]) def test_foo(fixt, val): pass """ - % param_args ) reprec = pytester.inline_run() reprec.assertoutcome(passed=2) diff --git a/testing/test_assertion.py b/testing/test_assertion.py index ef4e36644d9..a8960436b55 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -101,7 +101,7 @@ def test(check_first): """, } pytester.makepyfile(**contents) - result = pytester.runpytest_subprocess("--assert=%s" % mode) + result = pytester.runpytest_subprocess(f"--assert={mode}") if mode == "plain": expected = "E AssertionError" elif mode == "rewrite": @@ -163,7 +163,7 @@ def test_foo(check_first): """, } pytester.makepyfile(**contents) - result = pytester.runpytest_subprocess("--assert=%s" % mode) + result = pytester.runpytest_subprocess(f"--assert={mode}") if mode == "plain": expected = "E AssertionError" elif mode == "rewrite": @@ -280,7 +280,7 @@ def test2(check_first2): } pytester.makepyfile(**contents) result = pytester.run( - sys.executable, "mainwrapper.py", "-s", "--assert=%s" % mode + sys.executable, "mainwrapper.py", "-s", f"--assert={mode}" ) if mode == "plain": expected = "E AssertionError" diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 7acc8cdf1d9..bedf6e27606 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -308,9 +308,7 @@ def test_foo(): ) result = pytester.runpytest() assert result.ret == 1 - result.stdout.fnmatch_lines( - ["*AssertionError*%s*" % repr((1, 2)), "*assert 1 == 2*"] - ) + result.stdout.fnmatch_lines([f"*AssertionError*{(1, 2)!r}*", "*assert 1 == 2*"]) def test_assertion_message_expr(self, pytester: Pytester) -> None: pytester.makepyfile( @@ -908,7 +906,7 @@ def test_optimized(): assert test_optimized.__doc__ is None""" ) p = make_numbered_dir(root=Path(pytester.path), prefix="runpytest-") - tmp = "--basetemp=%s" % p + tmp = f"--basetemp={p}" with monkeypatch.context() as mp: mp.setenv("PYTHONOPTIMIZE", "2") mp.delenv("PYTHONDONTWRITEBYTECODE", raising=False) diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 304e5414abc..6c18c358a80 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -191,7 +191,7 @@ def test_cache_reportheader( monkeypatch.delenv("TOX_ENV_DIR", raising=False) expected = ".pytest_cache" result = pytester.runpytest("-v") - result.stdout.fnmatch_lines(["cachedir: %s" % expected]) + result.stdout.fnmatch_lines([f"cachedir: {expected}"]) def test_cache_reportheader_external_abspath( diff --git a/testing/test_capture.py b/testing/test_capture.py index 0521c3b6b04..b6c206ec47c 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -103,16 +103,15 @@ def test_init_capturing(self): def test_capturing_unicode(pytester: Pytester, method: str) -> None: obj = "'b\u00f6y'" pytester.makepyfile( - """\ + f"""\ # taken from issue 227 from nosetests def test_unicode(): import sys print(sys.stdout) - print(%s) + print({obj}) """ - % obj ) - result = pytester.runpytest("--capture=%s" % method) + result = pytester.runpytest(f"--capture={method}") result.stdout.fnmatch_lines(["*1 passed*"]) @@ -124,7 +123,7 @@ def test_unicode(): print('b\\u00f6y') """ ) - result = pytester.runpytest("--capture=%s" % method) + result = pytester.runpytest(f"--capture={method}") result.stdout.fnmatch_lines(["*1 passed*"]) diff --git a/testing/test_collection.py b/testing/test_collection.py index 1491ec85990..9caca622c90 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -275,14 +275,14 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No # collects the tests for dirname in ("a", "b", "c"): items, reprec = pytester.inline_genitems(tmp_path.joinpath(dirname)) - assert [x.name for x in items] == ["test_%s" % dirname] + assert [x.name for x in items] == [f"test_{dirname}"] # changing cwd to each subdirectory and running pytest without # arguments collects the tests in that directory normally for dirname in ("a", "b", "c"): monkeypatch.chdir(pytester.path.joinpath(dirname)) items, reprec = pytester.inline_genitems() - assert [x.name for x in items] == ["test_%s" % dirname] + assert [x.name for x in items] == [f"test_{dirname}"] class TestCollectPluginHookRelay: @@ -572,7 +572,7 @@ def test_method(self): def test_collect_custom_nodes_multi_id(self, pytester: Pytester) -> None: p = pytester.makepyfile("def test_func(): pass") pytester.makeconftest( - """ + f""" import pytest class SpecialItem(pytest.Item): def runtest(self): @@ -581,10 +581,9 @@ class SpecialFile(pytest.File): def collect(self): return [SpecialItem.from_parent(name="check", parent=self)] def pytest_collect_file(file_path, parent): - if file_path.name == %r: + if file_path.name == {p.name!r}: return SpecialFile.from_parent(path=file_path, parent=parent) """ - % p.name ) id = p.name @@ -862,7 +861,7 @@ def runtest(self): result = pytester.runpytest() assert result.ret == 0 result.stdout.fnmatch_lines(["*2 passed*"]) - res = pytester.runpytest("%s::item2" % p.name) + res = pytester.runpytest(f"{p.name}::item2") res.stdout.fnmatch_lines(["*1 passed*"]) @@ -1444,7 +1443,7 @@ def test_nodeid(request): symlink_to_sub = out_of_tree.joinpath("symlink_to_sub") symlink_or_skip(sub, symlink_to_sub) os.chdir(sub) - result = pytester.runpytest("-vs", "--rootdir=%s" % sub, symlink_to_sub) + result = pytester.runpytest("-vs", f"--rootdir={sub}", symlink_to_sub) result.stdout.fnmatch_lines( [ # Should not contain "sub/"! diff --git a/testing/test_config.py b/testing/test_config.py index 147c2cb851c..776c8424a1a 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -67,13 +67,12 @@ def test_setupcfg_uses_toolpytest_with_pytest(self, pytester: Pytester) -> None: p1 = pytester.makepyfile("def test(): pass") pytester.makefile( ".cfg", - setup=""" + setup=f""" [tool:pytest] - testpaths=%s + testpaths={p1.name} [pytest] testpaths=ignored - """ - % p1.name, + """, ) result = pytester.runpytest() result.stdout.fnmatch_lines(["configfile: setup.cfg", "* 1 passed in *"]) @@ -838,11 +837,10 @@ def pytest_addoption(parser): ) if str_val != "no-ini": pytester.makeini( - """ + f""" [pytest] - strip=%s + strip={str_val} """ - % str_val ) config = pytester.parseconfig() assert config.getini("strip") is bool_val @@ -1290,8 +1288,8 @@ def test_invalid_options_show_extra_information(pytester: Pytester) -> None: result.stderr.fnmatch_lines( [ "*error: unrecognized arguments: --invalid-option*", - "* inifile: %s*" % pytester.path.joinpath("tox.ini"), - "* rootdir: %s*" % pytester.path, + "* inifile: {}*".format(pytester.path.joinpath("tox.ini")), + f"* rootdir: {pytester.path}*", ] ) @@ -1423,8 +1421,8 @@ def pytest_load_initial_conftests(self): def test_get_plugin_specs_as_list() -> None: def exp_match(val: object) -> str: return ( - "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %s" - % re.escape(repr(val)) + f"Plugins may be specified as a sequence or a ','-separated string " + f"of plugin names. Got: {re.escape(repr(val))}" ) with pytest.raises(pytest.UsageError, match=exp_match({"foo"})): @@ -1837,10 +1835,10 @@ def test_addopts_before_initini( self, monkeypatch: MonkeyPatch, _config_for_test, _sys_snapshot ) -> None: cache_dir = ".custom_cache" - monkeypatch.setenv("PYTEST_ADDOPTS", "-o cache_dir=%s" % cache_dir) + monkeypatch.setenv("PYTEST_ADDOPTS", f"-o cache_dir={cache_dir}") config = _config_for_test config._preparse([], addopts=True) - assert config._override_ini == ["cache_dir=%s" % cache_dir] + assert config._override_ini == [f"cache_dir={cache_dir}"] def test_addopts_from_env_not_concatenated( self, monkeypatch: MonkeyPatch, _config_for_test @@ -2048,7 +2046,7 @@ class DummyPlugin: ) def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None: p = pytester.makepyfile("def test(): pass") - result = pytester.runpytest(str(p), "-pno:%s" % plugin) + result = pytester.runpytest(str(p), f"-pno:{plugin}") if plugin == "python": assert result.ret == ExitCode.USAGE_ERROR @@ -2065,7 +2063,7 @@ def test_config_blocked_default_plugins(pytester: Pytester, plugin: str) -> None result.stdout.fnmatch_lines(["* 1 passed in *"]) p = pytester.makepyfile("def test(): assert 0") - result = pytester.runpytest(str(p), "-pno:%s" % plugin) + result = pytester.runpytest(str(p), f"-pno:{plugin}") assert result.ret == ExitCode.TESTS_FAILED if plugin != "terminal": result.stdout.fnmatch_lines(["* 1 failed in *"]) diff --git a/testing/test_conftest.py b/testing/test_conftest.py index 3116dfe2584..a3ebcbb7862 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -280,7 +280,7 @@ def pytest_addoption(parser): ), encoding="utf-8", ) - result = pytester.runpytest("-h", "--confcutdir=%s" % x, x) + result = pytester.runpytest("-h", f"--confcutdir={x}", x) result.stdout.fnmatch_lines(["*--xyz*"]) result.stdout.no_fnmatch_line("*warning: could not load initial*") @@ -380,7 +380,7 @@ def fixture(): """ ), } - pytester.makepyfile(**{"real/%s" % k: v for k, v in source.items()}) + pytester.makepyfile(**{f"real/{k}": v for k, v in source.items()}) # Create a build directory that contains symlinks to actual files # but doesn't symlink actual directories. @@ -402,7 +402,7 @@ def test_conftest_badcase(pytester: Pytester) -> None: """Check conftest.py loading when directory casing is wrong (#5792).""" pytester.path.joinpath("JenkinsRoot/test").mkdir(parents=True) source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""} - pytester.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()}) + pytester.makepyfile(**{f"JenkinsRoot/{k}": v for k, v in source.items()}) os.chdir(pytester.path.joinpath("jenkinsroot/test")) result = pytester.runpytest() @@ -638,9 +638,9 @@ def test_parsefactories_relative_node_ids( ) -> None: """#616""" dirs = self._setup_tree(pytester) - print("pytest run in cwd: %s" % (dirs[chdir].relative_to(pytester.path))) - print("pytestarg : %s" % testarg) - print("expected pass : %s" % expect_ntests_passed) + print(f"pytest run in cwd: {dirs[chdir].relative_to(pytester.path)}") + print(f"pytestarg : {testarg}") + print(f"expected pass : {expect_ntests_passed}") os.chdir(dirs[chdir]) reprec = pytester.inline_run( testarg, @@ -699,7 +699,7 @@ def out_of_reach(): pass args = [str(src)] if confcutdir: - args = ["--confcutdir=%s" % root.joinpath(confcutdir)] + args = [f"--confcutdir={root.joinpath(confcutdir)}"] result = pytester.runpytest(*args) match = "" if passed: diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 53ebadbdba4..a99336c75a6 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -221,7 +221,7 @@ def test_not_called_due_to_quit(): pass """ ) - child = pytester.spawn_pytest("--pdb %s" % p1) + child = pytester.spawn_pytest(f"--pdb {p1}") child.expect("captured stdout") child.expect("get rekt") child.expect("captured stderr") @@ -246,7 +246,7 @@ def test_1(): assert False """ ) - child = pytester.spawn_pytest("--pdb %s" % p1) + child = pytester.spawn_pytest(f"--pdb {p1}") child.expect("Pdb") output = child.before.decode("utf8") child.sendeof() @@ -283,7 +283,7 @@ def test_1(): assert False """ ) - child = pytester.spawn_pytest("--show-capture=all --pdb -p no:logging %s" % p1) + child = pytester.spawn_pytest(f"--show-capture=all --pdb -p no:logging {p1}") child.expect("get rekt") output = child.before.decode("utf8") assert "captured log" not in output @@ -303,7 +303,7 @@ def test_1(): pytest.raises(ValueError, globalfunc) """ ) - child = pytester.spawn_pytest("--pdb %s" % p1) + child = pytester.spawn_pytest(f"--pdb {p1}") child.expect(".*def test_1") child.expect(".*pytest.raises.*globalfunc") child.expect("Pdb") @@ -320,7 +320,7 @@ def test_pdb_interaction_on_collection_issue181(self, pytester: Pytester) -> Non xxx """ ) - child = pytester.spawn_pytest("--pdb %s" % p1) + child = pytester.spawn_pytest(f"--pdb {p1}") # child.expect(".*import pytest.*") child.expect("Pdb") child.sendline("c") @@ -335,7 +335,7 @@ def pytest_runtest_protocol(): """ ) p1 = pytester.makepyfile("def test_func(): pass") - child = pytester.spawn_pytest("--pdb %s" % p1) + child = pytester.spawn_pytest(f"--pdb {p1}") child.expect("Pdb") # INTERNALERROR is only displayed once via terminal reporter. @@ -461,7 +461,7 @@ def test_1(capsys, caplog): assert 0 """ ) - child = pytester.spawn_pytest("--pdb %s" % str(p1)) + child = pytester.spawn_pytest(f"--pdb {p1!s}") child.send("caplog.record_tuples\n") child.expect_exact( "[('test_pdb_with_caplog_on_pdb_invocation', 30, 'some_warning')]" @@ -501,7 +501,7 @@ def function_1(): ''' """ ) - child = pytester.spawn_pytest("--doctest-modules --pdb %s" % p1) + child = pytester.spawn_pytest(f"--doctest-modules --pdb {p1}") child.expect("Pdb") assert "UNEXPECTED EXCEPTION: AssertionError()" in child.before.decode("utf8") @@ -528,7 +528,7 @@ def function_1(): ) # NOTE: does not use pytest.set_trace, but Python's patched pdb, # therefore "-s" is required. - child = pytester.spawn_pytest("--doctest-modules --pdb -s %s" % p1) + child = pytester.spawn_pytest(f"--doctest-modules --pdb -s {p1}") child.expect("Pdb") child.sendline("q") rest = child.read().decode("utf8") @@ -621,7 +621,7 @@ def test_1(): pytest.fail("expected_failure") """ ) - child = pytester.spawn_pytest("--pdbcls=mytest:CustomPdb %s" % str(p1)) + child = pytester.spawn_pytest(f"--pdbcls=mytest:CustomPdb {p1!s}") child.expect(r"PDB set_trace \(IO-capturing turned off\)") child.expect(r"\n\(Pdb") child.sendline("debug foo()") @@ -658,7 +658,7 @@ def test_1(): pytest.set_trace() """ ) - child = pytester.spawn_pytest("-s %s" % p1) + child = pytester.spawn_pytest(f"-s {p1}") child.expect(r">>> PDB set_trace >>>") child.expect("Pdb") child.sendline("c") @@ -914,7 +914,7 @@ def test_foo(): """ ) monkeypatch.setenv("PYTHONPATH", str(pytester.path)) - child = pytester.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1)) + child = pytester.spawn_pytest(f"--pdbcls=custom_pdb:CustomPdb {p1!s}") child.expect("__init__") child.expect("custom set_trace>") @@ -1208,8 +1208,7 @@ def test_inner({fixture}): child.expect("Pdb") before = child.before.decode("utf8") assert ( - "> PDB set_trace (IO-capturing turned off for fixture %s) >" % (fixture) - in before + f"> PDB set_trace (IO-capturing turned off for fixture {fixture}) >" in before ) # Test that capturing is really suspended. @@ -1225,7 +1224,7 @@ def test_inner({fixture}): TestPDB.flush(child) assert child.exitstatus == 0 assert "= 1 passed in" in rest - assert "> PDB continue (IO-capturing resumed for fixture %s) >" % (fixture) in rest + assert f"> PDB continue (IO-capturing resumed for fixture {fixture}) >" in rest def test_pdbcls_via_local_module(pytester: Pytester) -> None: diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 58fce244f45..de5d1353163 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1160,7 +1160,7 @@ def makeit(doctest): pytester.maketxtfile(doctest) else: assert mode == "module" - pytester.makepyfile('"""\n%s"""' % doctest) + pytester.makepyfile(f'"""\n{doctest}"""') return makeit diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index a3363de9816..e5016976130 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -101,7 +101,7 @@ def test_timeout(): result = pytester.runpytest_subprocess(*args) tb_output = "most recent call first" if enabled: - result.stderr.fnmatch_lines(["*%s*" % tb_output]) + result.stderr.fnmatch_lines([f"*{tb_output}*"]) else: assert tb_output not in result.stderr.str() result.stdout.fnmatch_lines(["*1 passed*"]) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 3b92d65bdb9..f8742dc2e3c 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -44,7 +44,7 @@ def __call__( if family: args = ("-o", "junit_family=" + family, *args) xml_path = self.pytester.path.joinpath("junit.xml") - result = self.pytester.runpytest("--junitxml=%s" % xml_path, *args) + result = self.pytester.runpytest(f"--junitxml={xml_path}", *args) if family == "xunit2": with xml_path.open(encoding="utf-8") as f: self.schema.validate(f) @@ -520,7 +520,7 @@ def test_fail(): ) result, dom = run_and_parse( - "-o", "junit_logging=%s" % junit_logging, family=xunit_family + "-o", f"junit_logging={junit_logging}", family=xunit_family ) assert result.ret, "Expected ret > 0" node = dom.find_first_by_tag("testsuite") @@ -605,11 +605,11 @@ def test_func(arg1): 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 + classname="test_failure_escape", name=f"test_func[{char}]" ) sysout = tnode.find_first_by_tag("system-out") text = sysout.text - assert "%s\n" % char in text + assert f"{char}\n" in text @parametrize_families def test_junit_prefixing( @@ -694,7 +694,7 @@ def test_fail(): assert 0 """ ) - result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") node = dom.find_first_by_tag("testsuite") tnode = node.find_first_by_tag("testcase") if junit_logging in ["system-err", "out-err", "all"]: @@ -764,13 +764,12 @@ def test_collect_error( def test_unicode(self, pytester: Pytester, run_and_parse: RunAndParse) -> None: value = "hx\xc4\x85\xc4\x87\n" pytester.makepyfile( - """\ + f"""\ # coding: latin1 def test_hello(): - print(%r) + print({value!r}) assert 0 """ - % value ) result, dom = run_and_parse() assert result.ret == 1 @@ -805,7 +804,7 @@ def test_pass(): print('hello-stdout') """ ) - result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") if junit_logging == "no": @@ -829,7 +828,7 @@ def test_pass(): sys.stderr.write('hello-stderr') """ ) - result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") if junit_logging == "no": @@ -858,7 +857,7 @@ def test_function(arg): pass """ ) - result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") if junit_logging == "no": @@ -888,7 +887,7 @@ def test_function(arg): pass """ ) - result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") if junit_logging == "no": @@ -919,7 +918,7 @@ def test_function(arg): sys.stdout.write('hello-stdout call') """ ) - result, dom = run_and_parse("-o", "junit_logging=%s" % junit_logging) + result, dom = run_and_parse("-o", f"junit_logging={junit_logging}") node = dom.find_first_by_tag("testsuite") pnode = node.find_first_by_tag("testcase") if junit_logging == "no": @@ -1013,7 +1012,7 @@ def test_print_nullbyte(): """ ) xmlf = pytester.path.joinpath("junit.xml") - pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) + pytester.runpytest(f"--junitxml={xmlf}", "-o", f"junit_logging={junit_logging}") text = xmlf.read_text(encoding="utf-8") assert "\x00" not in text if junit_logging == "system-out": @@ -1035,7 +1034,7 @@ def test_print_nullbyte(): """ ) xmlf = pytester.path.joinpath("junit.xml") - pytester.runpytest("--junitxml=%s" % xmlf, "-o", "junit_logging=%s" % junit_logging) + pytester.runpytest(f"--junitxml={xmlf}", "-o", f"junit_logging={junit_logging}") text = xmlf.read_text(encoding="utf-8") if junit_logging == "system-out": assert "#x0" in text @@ -1071,9 +1070,9 @@ def test_invalid_xml_escape() -> None: for i in invalid: got = bin_xml_escape(chr(i)) if i <= 0xFF: - expected = "#x%02X" % i + expected = f"#x{i:02X}" else: - expected = "#x%04X" % i + expected = f"#x{i:04X}" assert got == expected for i in valid: assert chr(i) == bin_xml_escape(chr(i)) @@ -1748,7 +1747,7 @@ def test_func(): """ ) result, dom = run_and_parse( - "-o", "junit_logging=%s" % junit_logging, family=xunit_family + "-o", f"junit_logging={junit_logging}", family=xunit_family ) assert result.ret == 1 node = dom.find_first_by_tag("testcase") diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index 49e620c1138..f7f45dbf02e 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -79,7 +79,7 @@ def test_1(tmpdir): assert os.path.realpath(str(tmpdir)) == str(tmpdir) """ ) - result = pytester.runpytest("-s", p, "--basetemp=%s/bt" % linktemp) + result = pytester.runpytest("-s", p, f"--basetemp={linktemp}/bt") assert not result.ret diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 12be774beca..2ad3ccc4ddc 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -442,7 +442,7 @@ def test_syspath_prepend_with_namespace_packages( lib = ns.joinpath(dirname) lib.mkdir() lib.joinpath("__init__.py").write_text( - "def check(): return %r" % dirname, encoding="utf-8" + f"def check(): return {dirname!r}", encoding="utf-8" ) monkeypatch.syspath_prepend("hello") diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 651a04da84a..9ca0da8f69d 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -171,7 +171,7 @@ def test_create_new_paste(self, pastebin, mocked_urlopen) -> None: assert type(data) is bytes lexer = "text" assert url == "https://bpa.st" - assert "lexer=%s" % lexer in data.decode() + assert f"lexer={lexer}" in data.decode() assert "code=full-paste-contents" in data.decode() assert "expiry=1week" in data.decode() diff --git a/testing/test_skipping.py b/testing/test_skipping.py index a1511b26d1c..459216a6d6b 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -297,13 +297,12 @@ class TestXFail: @pytest.mark.parametrize("strict", [True, False]) def test_xfail_simple(self, pytester: Pytester, strict: bool) -> None: item = pytester.getitem( - """ + f""" import pytest - @pytest.mark.xfail(strict=%s) + @pytest.mark.xfail(strict={strict}) def test_func(): assert 0 """ - % strict ) reports = runtestprotocol(item, log=False) assert len(reports) == 3 @@ -630,15 +629,14 @@ def test_foo(): @pytest.mark.parametrize("strict", [True, False]) def test_strict_xfail(self, pytester: Pytester, strict: bool) -> None: p = pytester.makepyfile( - """ + f""" import pytest - @pytest.mark.xfail(reason='unsupported feature', strict=%s) + @pytest.mark.xfail(reason='unsupported feature', strict={strict}) def test_foo(): with open('foo_executed', 'w', encoding='utf-8'): pass # make sure test executes """ - % strict ) result = pytester.runpytest(p, "-rxX") if strict: @@ -658,14 +656,13 @@ def test_foo(): @pytest.mark.parametrize("strict", [True, False]) def test_strict_xfail_condition(self, pytester: Pytester, strict: bool) -> None: p = pytester.makepyfile( - """ + f""" import pytest - @pytest.mark.xfail(False, reason='unsupported feature', strict=%s) + @pytest.mark.xfail(False, reason='unsupported feature', strict={strict}) def test_foo(): pass """ - % strict ) result = pytester.runpytest(p, "-rxX") result.stdout.fnmatch_lines(["*1 passed*"]) @@ -674,14 +671,13 @@ def test_foo(): @pytest.mark.parametrize("strict", [True, False]) def test_xfail_condition_keyword(self, pytester: Pytester, strict: bool) -> None: p = pytester.makepyfile( - """ + f""" import pytest - @pytest.mark.xfail(condition=False, reason='unsupported feature', strict=%s) + @pytest.mark.xfail(condition=False, reason='unsupported feature', strict={strict}) def test_foo(): pass """ - % strict ) result = pytester.runpytest(p, "-rxX") result.stdout.fnmatch_lines(["*1 passed*"]) @@ -692,11 +688,10 @@ def test_strict_xfail_default_from_file( self, pytester: Pytester, strict_val ) -> None: pytester.makeini( - """ + f""" [pytest] - xfail_strict = %s + xfail_strict = {strict_val} """ - % strict_val ) p = pytester.makepyfile( """ diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 170f1efcf91..dd5fa4bf679 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1421,7 +1421,7 @@ def test_opt(arg): s = result.stdout.str() assert "arg = 42" not in s assert "x = 0" not in s - result.stdout.fnmatch_lines(["*%s:8*" % p.name, " assert x", "E assert*"]) + result.stdout.fnmatch_lines([f"*{p.name}:8*", " assert x", "E assert*"]) result = pytester.runpytest() s = result.stdout.str() assert "x = 0" in s @@ -1497,8 +1497,8 @@ def test_func(): """ ) for tbopt in ["long", "short", "no"]: - print("testing --tb=%s..." % tbopt) - result = pytester.runpytest("-rN", "--tb=%s" % tbopt) + print(f"testing --tb={tbopt}...") + result = pytester.runpytest("-rN", f"--tb={tbopt}") s = result.stdout.str() if tbopt == "long": assert "print(6*7)" in s @@ -1528,7 +1528,7 @@ def test_func2(): result = pytester.runpytest("--tb=line") bn = p.name result.stdout.fnmatch_lines( - ["*%s:3: IndexError*" % bn, "*%s:8: AssertionError: hello*" % bn] + [f"*{bn}:3: IndexError*", f"*{bn}:8: AssertionError: hello*"] ) s = result.stdout.str() assert "def test_func2" not in s @@ -1544,7 +1544,7 @@ def test_func1(): result = pytester.runpytest("--tb=line") result.stdout.str() bn = p.name - result.stdout.fnmatch_lines(["*%s:3: Failed: test_func1" % bn]) + result.stdout.fnmatch_lines([f"*{bn}:3: Failed: test_func1"]) def test_pytest_report_header(self, pytester: Pytester, option) -> None: pytester.makeconftest( @@ -1945,7 +1945,7 @@ class fake_session: # Reset cache. tr._main_color = None - print("Based on stats: %s" % stats_arg) + print(f"Based on stats: {stats_arg}") print(f'Expect summary: "{exp_line}"; with color "{exp_color}"') (line, color) = tr.build_summary_stats_line() print(f'Actually got: "{line}"; with color "{color}"') diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index 331ee7da6c7..f424998e50f 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -87,11 +87,11 @@ def test_1(tmp_path): pass """ ) - pytester.runpytest(p, "--basetemp=%s" % mytemp) + pytester.runpytest(p, f"--basetemp={mytemp}") assert mytemp.exists() mytemp.joinpath("hello").touch() - pytester.runpytest(p, "--basetemp=%s" % mytemp) + pytester.runpytest(p, f"--basetemp={mytemp}") assert mytemp.exists() assert not mytemp.joinpath("hello").exists() @@ -248,7 +248,7 @@ def test_abs_path(tmp_path_factory): """ ) - result = pytester.runpytest(p, "--basetemp=%s" % mytemp) + result = pytester.runpytest(p, f"--basetemp={mytemp}") if is_ok: assert result.ret == 0 assert mytemp.joinpath(basename).exists() diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 3ef0cd3b546..770454e83df 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -44,7 +44,7 @@ def test_normal_flow(pytester: Pytester, pyfile_with_warnings) -> None: result = pytester.runpytest(pyfile_with_warnings) result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "test_normal_flow.py::test_func", "*normal_flow_module.py:3: UserWarning: user warning", '* warnings.warn(UserWarning("user warning"))', @@ -75,7 +75,7 @@ def test_func(fix): result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "*test_setup_teardown_warnings.py:6: UserWarning: warning during setup", '*warnings.warn(UserWarning("warning during setup"))', "*test_setup_teardown_warnings.py:8: UserWarning: warning during teardown", @@ -143,7 +143,7 @@ def test_func(fix): result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "*test_unicode.py:7: UserWarning: \u6d4b\u8bd5*", "* 1 passed, 1 warning*", ] @@ -315,7 +315,7 @@ def test_foo(): result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", " *collection_warnings.py:3: UserWarning: collection warning", ' warnings.warn(UserWarning("collection warning"))', "* 1 passed, 1 warning*", @@ -374,7 +374,7 @@ def test_bar(): else: result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "*test_hide_pytest_internal_warnings.py:4: PytestWarning: some internal warning", "* 1 passed, 1 warning *", ] @@ -461,7 +461,7 @@ def test_shown_by_default(self, pytester: Pytester, customize_filters) -> None: result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "*test_shown_by_default.py:3: DeprecationWarning: collection", "*test_shown_by_default.py:7: PendingDeprecationWarning: test run", "* 1 passed, 2 warnings*", @@ -492,7 +492,7 @@ def test_hidden_by_mark(self, pytester: Pytester) -> None: result = pytester.runpytest_subprocess() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "*test_hidden_by_mark.py:3: DeprecationWarning: collection", "* 1 passed, 1 warning*", ] @@ -555,7 +555,7 @@ def test(): class TestAssertionWarnings: @staticmethod def assert_result_warns(result, msg) -> None: - result.stdout.fnmatch_lines(["*PytestAssertRewriteWarning: %s*" % msg]) + result.stdout.fnmatch_lines([f"*PytestAssertRewriteWarning: {msg}*"]) def test_tuple_warning(self, pytester: Pytester) -> None: pytester.makepyfile( @@ -585,7 +585,7 @@ def test_group_warnings_by_message(pytester: Pytester) -> None: result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "test_group_warnings_by_message.py::test_foo[[]0[]]", "test_group_warnings_by_message.py::test_foo[[]1[]]", "test_group_warnings_by_message.py::test_foo[[]2[]]", @@ -617,7 +617,7 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None: result = pytester.runpytest() result.stdout.fnmatch_lines( [ - "*== %s ==*" % WARNINGS_SUMMARY_HEADER, + f"*== {WARNINGS_SUMMARY_HEADER} ==*", "test_1.py: 21 warnings", "test_2.py: 1 warning", " */test_1.py:8: UserWarning: foo", From 2b6c946a2bdcf4bb6bf9b04fadf8e24160aa631f Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 30 Apr 2024 18:11:06 +0200 Subject: [PATCH 018/199] [mypy 1.10.0] Remove 'type: ignore' that became useless --- src/_pytest/python_api.py | 2 +- src/_pytest/runner.py | 4 ++-- testing/test_runner.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 7d89fdd809e..9b17e5cb786 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -454,7 +454,7 @@ def __eq__(self, actual) -> bool: return False # Return true if the two numbers are within the tolerance. - result: bool = abs(self.expected - actual) <= self.tolerance # type: ignore[arg-type] + result: bool = abs(self.expected - actual) <= self.tolerance return result # Ignore type because of https://github.com/python/mypy/issues/4266. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index eb444f2e322..5b7cd3e1dad 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -167,7 +167,7 @@ def pytest_runtest_call(item: Item) -> None: del sys.last_value del sys.last_traceback if sys.version_info >= (3, 12, 0): - del sys.last_exc # type: ignore[attr-defined] + del sys.last_exc except AttributeError: pass try: @@ -177,7 +177,7 @@ def pytest_runtest_call(item: Item) -> None: sys.last_type = type(e) sys.last_value = e if sys.version_info >= (3, 12, 0): - sys.last_exc = e # type: ignore[attr-defined] + sys.last_exc = e assert e.__traceback__ is not None # Skip *this* frame sys.last_traceback = e.__traceback__.tb_next diff --git a/testing/test_runner.py b/testing/test_runner.py index ecb98f2ffc2..6bd4a045db6 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1032,7 +1032,7 @@ def runtest(self): assert sys.last_type is IndexError assert isinstance(sys.last_value, IndexError) if sys.version_info >= (3, 12, 0): - assert isinstance(sys.last_exc, IndexError) # type: ignore[attr-defined] + assert isinstance(sys.last_exc, IndexError) assert sys.last_value.args[0] == "TEST" assert sys.last_traceback From 65d7d6135e86125ef22973806cf6ab2cf2752841 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 30 Apr 2024 18:13:04 +0200 Subject: [PATCH 019/199] [ruff] Add change to format in the git blame ignore file --- .git-blame-ignore-revs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index bce64a374be..d6aac5c425d 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -31,3 +31,5 @@ c9df77cbd6a365dcb73c39618e4842711817e871 4546d5445aaefe6a03957db028c263521dfb5c4b # Migration to ruff / ruff format 4588653b2497ed25976b7aaff225b889fb476756 +# Use format specifiers instead of percent format +4788165e69d08e10fc6b9c0124083fb358e2e9b0 From 4c5298c3954efa71436364efaf531d3851a6aeb4 Mon Sep 17 00:00:00 2001 From: Ben Brown Date: Thu, 2 May 2024 03:42:31 -0400 Subject: [PATCH 020/199] Add back "Fix teardown error reporting when --maxfail=1 (#11721)" (#12279) Closes #11706. Originally fixed in #11721, but then reverted in #12022 due to a regression in pytest-xdist. The regression was fixed on the pytest-xdist side in pytest-dev/pytest-xdist#1026. --- changelog/11706.bugfix.rst | 4 +++ src/_pytest/runner.py | 4 +++ testing/test_runner.py | 50 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 changelog/11706.bugfix.rst diff --git a/changelog/11706.bugfix.rst b/changelog/11706.bugfix.rst new file mode 100644 index 00000000000..a86db5ef66a --- /dev/null +++ b/changelog/11706.bugfix.rst @@ -0,0 +1,4 @@ +Fix reporting of teardown errors in higher-scoped fixtures when using `--maxfail` or `--stepwise`. + +Originally added in pytest 8.0.0, but reverted in 8.0.2 due to a regression in pytest-xdist. +This regression was fixed in pytest-xdist 3.6.1. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index 5b7cd3e1dad..dfefa73b72d 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -134,6 +134,10 @@ def runtestprotocol( show_test_item(item) if not item.config.getoption("setuponly", False): reports.append(call_and_report(item, "call", log)) + # If the session is about to fail or stop, teardown everything - this is + # necessary to correctly report fixture teardown errors (see #11706) + if item.session.shouldfail or item.session.shouldstop: + nextitem = None reports.append(call_and_report(item, "teardown", log, nextitem=nextitem)) # After all teardown hooks have been called # want funcargs and request info to go away. diff --git a/testing/test_runner.py b/testing/test_runner.py index 6bd4a045db6..436ce2f1062 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1216,3 +1216,53 @@ def test(): result = pytester.runpytest_inprocess() assert result.ret == ExitCode.OK assert os.environ["PYTEST_VERSION"] == "old version" + + +def test_teardown_session_failed(pytester: Pytester) -> None: + """Test that higher-scoped fixture teardowns run in the context of the last + item after the test session bails early due to --maxfail. + + Regression test for #11706. + """ + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def baz(): + yield + pytest.fail("This is a failing teardown") + + def test_foo(baz): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + result = pytester.runpytest("--maxfail=1") + result.assert_outcomes(failed=1, errors=1) + + +def test_teardown_session_stopped(pytester: Pytester) -> None: + """Test that higher-scoped fixture teardowns run in the context of the last + item after the test session bails early due to --stepwise. + + Regression test for #11706. + """ + pytester.makepyfile( + """ + import pytest + + @pytest.fixture(scope="module") + def baz(): + yield + pytest.fail("This is a failing teardown") + + def test_foo(baz): + pytest.fail("This is a failing test") + + def test_bar(): pass + """ + ) + result = pytester.runpytest("--stepwise") + result.assert_outcomes(failed=1, errors=1) From 97610067acf220b13cc4c6fca34e5aac4834b04d Mon Sep 17 00:00:00 2001 From: Anita Hammer <166057949+anitahammer@users.noreply.github.com> Date: Thu, 2 May 2024 12:59:09 +0100 Subject: [PATCH 021/199] Handle KeyboardInterrupt and SystemExit at collection time (#12191) --- AUTHORS | 1 + changelog/12191.bugfix.rst | 1 + src/_pytest/runner.py | 4 +++- testing/test_collection.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 changelog/12191.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 4f61c05914b..4619cf1bc44 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,6 +36,7 @@ Andrey Paramonov Andrzej Klajnert Andrzej Ostrowski Andy Freeland +Anita Hammer Anthon van der Neut Anthony Shaw Anthony Sottile diff --git a/changelog/12191.bugfix.rst b/changelog/12191.bugfix.rst new file mode 100644 index 00000000000..5102d469814 --- /dev/null +++ b/changelog/12191.bugfix.rst @@ -0,0 +1 @@ +Keyboard interrupts and system exits are now properly handled during the test collection. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index dfefa73b72d..e5af60e388b 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -393,7 +393,9 @@ def collect() -> List[Union[Item, Collector]]: return list(collector.collect()) - call = CallInfo.from_call(collect, "collect") + call = CallInfo.from_call( + collect, "collect", reraise=(KeyboardInterrupt, SystemExit) + ) longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" diff --git a/testing/test_collection.py b/testing/test_collection.py index 9caca622c90..995e2999bbe 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -7,6 +7,7 @@ import tempfile import textwrap from typing import List +from typing import Type from _pytest.assertion.util import running_on_ci from _pytest.config import ExitCode @@ -1856,3 +1857,33 @@ def test_do_not_collect_symlink_siblings( # Ensure we collect it only once if we pass the symlinked directory. result = pytester.runpytest(symlink_path, "-sv") result.assert_outcomes(passed=1) + + +@pytest.mark.parametrize( + "exception_class, msg", + [ + (KeyboardInterrupt, "*!!! KeyboardInterrupt !!!*"), + (SystemExit, "INTERNALERROR> SystemExit"), + ], +) +def test_respect_system_exceptions( + pytester: Pytester, + exception_class: Type[BaseException], + msg: str, +): + head = "Before exception" + tail = "After exception" + ensure_file(pytester.path / "test_eggs.py").write_text( + f"print('{head}')", encoding="UTF-8" + ) + ensure_file(pytester.path / "test_ham.py").write_text( + f"raise {exception_class.__name__}()", encoding="UTF-8" + ) + ensure_file(pytester.path / "test_spam.py").write_text( + f"print('{tail}')", encoding="UTF-8" + ) + + result = pytester.runpytest_subprocess("-s") + result.stdout.fnmatch_lines([f"*{head}*"]) + result.stdout.fnmatch_lines([msg]) + result.stdout.no_fnmatch_line(f"*{tail}*") From 3d09c85df0e5e6d4cf2a7ad087fe15618d61cdb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 4 May 2024 21:26:09 -0300 Subject: [PATCH 022/199] [automated] Update plugin list (#12286) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 128 ++++++++++++++++--------------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 68d017c31c4..2a68f9e49a6 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =5.0.0,<6.0.0) :pypi:`pytest-check` A pytest plugin that allows multiple failures per test. Jan 18, 2024 N/A pytest>=7.0.0 - :pypi:`pytest-checkdocs` check the README when running tests Mar 31, 2024 5 - Production/Stable pytest>=6; extra == "testing" + :pypi:`pytest-checkdocs` check the README when running tests Apr 30, 2024 5 - Production/Stable pytest!=8.1.*,>=6; extra == "testing" :pypi:`pytest-checkipdb` plugin to check if there are ipdb debugs left Dec 04, 2023 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 @@ -356,6 +356,7 @@ This list contains 1451 plugins. :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. Oct 18, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 :pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest + :pypi:`pytest-ditto` May 04, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.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 @@ -437,14 +438,14 @@ This list contains 1451 plugins. :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 Apr 04, 2024 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. Apr 26, 2024 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Apr 26, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Apr 26, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Apr 26, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Apr 26, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Apr 26, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Apr 26, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. Apr 30, 2024 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Apr 30, 2024 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) @@ -518,7 +519,7 @@ This list contains 1451 plugins. :pypi:`pytest-figleaf` py.test figleaf coverage plugin Jan 18, 2010 5 - Production/Stable N/A :pypi:`pytest-file` Pytest File Mar 18, 2024 1 - Planning 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-filedata` easily load test data from files Apr 29, 2024 5 - Production/Stable 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 @@ -593,7 +594,7 @@ This list contains 1451 plugins. :pypi:`pytest-gitlab-code-quality` Collects warnings while testing and generates a GitLab Code Quality Report. Apr 03, 2024 N/A pytest>=8.1.1 :pypi:`pytest-gitlab-fold` Folds output sections in GitLab CI build log Dec 31, 2023 4 - Beta pytest >=2.6.0 :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 Apr 22, 2024 4 - Beta pytest<=8.1.1 + :pypi:`pytest-glamor-allure` Extends allure-pytest functionality Apr 30, 2024 4 - Beta pytest<=8.2.0 :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) @@ -623,7 +624,7 @@ This list contains 1451 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 Apr 25, 2024 3 - Alpha pytest==8.1.1 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components May 04, 2024 3 - Alpha pytest==8.1.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` Apr 18, 2024 N/A N/A @@ -671,7 +672,7 @@ This list contains 1451 plugins. :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-initry` Plugin for sending automation test data from Pytest to the initry Apr 14, 2024 N/A pytest<9.0.0,>=8.1.1 + :pypi:`pytest-initry` Plugin for sending automation test data from Pytest to the initry Apr 30, 2024 N/A pytest<9.0.0,>=8.1.1 :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest :pypi:`pytest-inmanta-extensions` Inmanta tests package Apr 02, 2024 5 - Production/Stable N/A @@ -703,7 +704,7 @@ This list contains 1451 plugins. :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 Apr 12, 2024 3 - Alpha N/A + :pypi:`pytest-jira` py.test JIRA integration plugin, using markers Apr 30, 2024 3 - Alpha N/A :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jun 19, 2023 N/A pytest (>=7.2.0) :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Mar 27, 2024 4 - Beta pytest>=6.2.4 :pypi:`pytest-job-selection` A pytest plugin for load balancing test suites Jan 30, 2023 4 - Beta pytest (>=3.5.0) @@ -795,7 +796,7 @@ This list contains 1451 plugins. :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. Feb 04, 2024 4 - Beta pytest !=6.0.0,<9,>=3.3.2 + :pypi:`pytest-md-report` A pytest plugin to generate test outcomes reports with markdown table format. May 03, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 :pypi:`pytest-meilisearch` Pytest helpers for testing projects using Meilisearch Feb 15, 2024 N/A pytest (>=7.4.3) :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 @@ -858,10 +859,10 @@ This list contains 1451 plugins. :pypi:`pytest-mypy-runner` Run the mypy static type checker as a pytest test case Apr 23, 2024 N/A pytest>=8.0 :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Mar 04, 2024 N/A pytest>=7,<9 :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Oct 30, 2023 5 - Production/Stable pytest >=6.2 - :pypi:`pytest-ndb` pytest notebook debugger Oct 15, 2023 N/A pytest + :pypi:`pytest-ndb` pytest notebook debugger Apr 28, 2024 N/A pytest :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-neos` Pytest plugin for neos Apr 15, 2024 1 - Planning N/A + :pypi:`pytest-neos` Pytest plugin for neos May 01, 2024 1 - Planning N/A :pypi:`pytest-netdut` "Automated software testing for switches using pytest" Mar 07, 2024 N/A pytest <7.3,>=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 @@ -1020,7 +1021,7 @@ This list contains 1451 plugins. :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 Oct 06, 2023 5 - Production/Stable pytest >=7.0 :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 Dec 09, 2023 N/A pytest + :pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Apr 30, 2024 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) @@ -1135,14 +1136,14 @@ This list contains 1451 plugins. :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_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them Mar 29, 2024 N/A pytest<9,>=7 + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 03, 2024 N/A pytest<9,>=7 :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. Mar 10, 2024 4 - Beta pytest (>=5) + :pypi:`pytest-ruff` pytest plugin to check ruff requirements. May 02, 2024 4 - Beta pytest>=5 :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-run-subprocess` Pytest Plugin for running and testing subprocesses. Nov 12, 2022 5 - Production/Stable pytest @@ -1159,7 +1160,7 @@ This list contains 1451 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. May 03, 2024 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 Feb 16, 2024 5 - Production/Stable pytest >=3.5.0 @@ -1168,7 +1169,7 @@ This list contains 1451 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Apr 26, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. May 03, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -1398,7 +1399,7 @@ This list contains 1451 plugins. :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-uncollect-if` A plugin to uncollect pytests tests rather than using skipif Mar 24, 2024 4 - Beta pytest>=6.2.0 - :pypi:`pytest-unflakable` Unflakable plugin for PyTest Nov 12, 2023 4 - Beta pytest >=6.2.0 + :pypi:`pytest-unflakable` Unflakable plugin for PyTest Apr 30, 2024 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-unique` Pytest fixture to generate unique values. Sep 15, 2023 N/A pytest (>=7.4.2,<8.0.0) :pypi:`pytest-unittest-filter` A pytest plugin for filtering unittest-based test classes Jan 12, 2019 4 - Beta pytest (>=3.1.0) @@ -1448,7 +1449,7 @@ This list contains 1451 plugins. :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 Mar 18, 2024 5 - Production/Stable pytest >=7.0.0 - :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs Apr 19, 2024 5 - Production/Stable pytest >=6.2.0 + :pypi:`pytest-xdist` pytest xdist plugin for distributed testing, most importantly across multiple CPUs Apr 28, 2024 5 - Production/Stable pytest>=7.0.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) @@ -2268,7 +2269,7 @@ This list contains 1451 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Apr 26, 2024, + *last release*: May 03, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2723,9 +2724,9 @@ This list contains 1451 plugins. A pytest plugin that allows multiple failures per test. :pypi:`pytest-checkdocs` - *last release*: Mar 31, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, - *requires*: pytest>=6; extra == "testing" + *requires*: pytest!=8.1.*,>=6; extra == "testing" check the README when running tests @@ -3744,6 +3745,13 @@ This list contains 1451 plugins. Pytest plugin to record discovered tests in a file + :pypi:`pytest-ditto` + *last release*: May 04, 2024, + *status*: 4 - Beta, + *requires*: pytest>=3.5.0 + + + :pypi:`pytest-django` *last release*: Jan 30, 2024, *status*: 5 - Production/Stable, @@ -4312,56 +4320,56 @@ This list contains 1451 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Apr 26, 2024, + *last release*: Apr 30, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -4879,11 +4887,11 @@ This list contains 1451 plugins. A pytest plugin to detect unused files :pypi:`pytest-filedata` - *last release*: Jan 17, 2019, - *status*: 4 - Beta, + *last release*: Apr 29, 2024, + *status*: 5 - Production/Stable, *requires*: N/A - easily load data from files + easily load test data from files :pypi:`pytest-filemarker` *last release*: Dec 01, 2020, @@ -5404,9 +5412,9 @@ This list contains 1451 plugins. Utility to select tests that have had its dependencies modified (as identified by git diff) :pypi:`pytest-glamor-allure` - *last release*: Apr 22, 2024, + *last release*: Apr 30, 2024, *status*: 4 - Beta, - *requires*: pytest<=8.1.1 + *requires*: pytest<=8.2.0 Extends allure-pytest functionality @@ -5614,7 +5622,7 @@ This list contains 1451 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Apr 25, 2024, + *last release*: May 04, 2024, *status*: 3 - Alpha, *requires*: pytest==8.1.1 @@ -5950,7 +5958,7 @@ This list contains 1451 plugins. Reuse pytest.ini to store env variables :pypi:`pytest-initry` - *last release*: Apr 14, 2024, + *last release*: Apr 30, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.1.1 @@ -6174,7 +6182,7 @@ This list contains 1451 plugins. A plugin to generate customizable jinja-based HTML reports in pytest :pypi:`pytest-jira` - *last release*: Apr 12, 2024, + *last release*: Apr 30, 2024, *status*: 3 - Alpha, *requires*: N/A @@ -6818,11 +6826,11 @@ This list contains 1451 plugins. Plugin for generating Markdown reports for pytest results :pypi:`pytest-md-report` - *last release*: Feb 04, 2024, + *last release*: May 03, 2024, *status*: 4 - Beta, - *requires*: pytest !=6.0.0,<9,>=3.3.2 + *requires*: pytest!=6.0.0,<9,>=3.3.2 - A pytest plugin to make a test results report with Markdown table format. + A pytest plugin to generate test outcomes reports with markdown table format. :pypi:`pytest-meilisearch` *last release*: Feb 15, 2024, @@ -7259,7 +7267,7 @@ This list contains 1451 plugins. MySQL process and client fixtures for pytest :pypi:`pytest-ndb` - *last release*: Oct 15, 2023, + *last release*: Apr 28, 2024, *status*: N/A, *requires*: pytest @@ -7280,7 +7288,7 @@ This list contains 1451 plugins. pytest-neo is a plugin for pytest that shows tests like screen of Matrix. :pypi:`pytest-neos` - *last release*: Apr 15, 2024, + *last release*: May 01, 2024, *status*: 1 - Planning, *requires*: N/A @@ -8393,7 +8401,7 @@ This list contains 1451 plugins. Record PyMySQL queries and mock with the stored data. :pypi:`pytest-pyodide` - *last release*: Dec 09, 2023, + *last release*: Apr 30, 2024, *status*: N/A, *requires*: pytest @@ -9198,7 +9206,7 @@ This list contains 1451 plugins. pytest plugin for ROAST configuration override and fixtures :pypi:`pytest_robotframework` - *last release*: Mar 29, 2024, + *last release*: May 03, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -9247,9 +9255,9 @@ This list contains 1451 plugins. Coverage-based regression test selection (RTS) plugin for pytest :pypi:`pytest-ruff` - *last release*: Mar 10, 2024, + *last release*: May 02, 2024, *status*: 4 - Beta, - *requires*: pytest (>=5) + *requires*: pytest>=5 pytest plugin to check ruff requirements. @@ -9366,7 +9374,7 @@ This list contains 1451 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: Apr 26, 2024, + *last release*: May 03, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9429,7 +9437,7 @@ This list contains 1451 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Apr 26, 2024, + *last release*: May 03, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -11039,9 +11047,9 @@ This list contains 1451 plugins. A plugin to uncollect pytests tests rather than using skipif :pypi:`pytest-unflakable` - *last release*: Nov 12, 2023, + *last release*: Apr 30, 2024, *status*: 4 - Beta, - *requires*: pytest >=6.2.0 + *requires*: pytest>=6.2.0 Unflakable plugin for PyTest @@ -11389,9 +11397,9 @@ This list contains 1451 plugins. A pytest plugin for configuring workflow/pipeline tests using YAML files :pypi:`pytest-xdist` - *last release*: Apr 19, 2024, + *last release*: Apr 28, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=6.2.0 + *requires*: pytest>=7.0.0 pytest xdist plugin for distributed testing, most importantly across multiple CPUs From 84e59af8c829de659efb339d5a9c7cec98eee719 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sun, 5 May 2024 19:53:16 -0300 Subject: [PATCH 023/199] Document exceptions raised by exit, skip, xfail, fail, and importorskip (#12285) As commented on: https://github.com/pytest-dev/pytest/issues/7469#issuecomment-2094104215 --- doc/en/reference/reference.rst | 19 ++++++++++++++++++- src/_pytest/outcomes.py | 15 +++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 39317497ebd..e46478da02b 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -59,11 +59,19 @@ pytest.fail .. autofunction:: pytest.fail(reason, [pytrace=True, msg=None]) +.. class:: pytest.fail.Exception + + The exception raised by :func:`pytest.fail`. + pytest.skip ~~~~~~~~~~~ .. autofunction:: pytest.skip(reason, [allow_module_level=False, msg=None]) +.. class:: pytest.skip.Exception + + The exception raised by :func:`pytest.skip`. + .. _`pytest.importorskip ref`: pytest.importorskip @@ -76,11 +84,19 @@ pytest.xfail .. autofunction:: pytest.xfail +.. class:: pytest.xfail.Exception + + The exception raised by :func:`pytest.xfail`. + pytest.exit ~~~~~~~~~~~ .. autofunction:: pytest.exit(reason, [returncode=None, msg=None]) +.. class:: pytest.exit.Exception + + The exception raised by :func:`pytest.exit`. + pytest.main ~~~~~~~~~~~ @@ -246,9 +262,10 @@ Marks a test function as *expected to fail*. to specify ``reason`` (see :ref:`condition string `). :keyword str reason: Reason why the test function is marked as xfail. - :keyword Type[Exception] raises: + :keyword raises: Exception class (or tuple of classes) expected to be raised by the test function; other exceptions will fail the test. Note that subclasses of the classes passed will also result in a match (similar to how the ``except`` statement works). + :type raises: Type[:py:exc:`Exception`] :keyword bool run: Whether the test function should actually be executed. If ``False``, the function will always xfail and will diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index 76d94accd0d..f953dabe03d 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -114,6 +114,9 @@ def exit( :param returncode: Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`. + + :raises pytest.exit.Exception: + The exception that is raised. """ __tracebackhide__ = True raise Exit(reason, returncode) @@ -142,6 +145,9 @@ def skip( Defaults to False. + :raises pytest.skip.Exception: + The exception that is raised. + .. note:: It is better to use the :ref:`pytest.mark.skipif ref` marker when possible to declare a test to be skipped under certain conditions @@ -163,6 +169,9 @@ def fail(reason: str = "", pytrace: bool = True) -> NoReturn: :param pytrace: If False, msg represents the full failure information and no python traceback will be reported. + + :raises pytest.fail.Exception: + The exception that is raised. """ __tracebackhide__ = True raise Failed(msg=reason, pytrace=pytrace) @@ -188,6 +197,9 @@ def xfail(reason: str = "") -> NoReturn: It is better to use the :ref:`pytest.mark.xfail ref` marker when possible to declare a test to be xfailed under certain conditions like known bugs or missing features. + + :raises pytest.xfail.Exception: + The exception that is raised. """ __tracebackhide__ = True raise XFailed(reason) @@ -227,6 +239,9 @@ def importorskip( :returns: The imported module. This should be assigned to its canonical name. + :raises pytest.skip.Exception: + If the module cannot be imported. + Example:: docutils = pytest.importorskip("docutils") From 4080459f041f02c66356777001d18155d2796ee7 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 7 May 2024 07:28:34 +0200 Subject: [PATCH 024/199] [pre-commit.ci] pre-commit autoupdate (#12292) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.2 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.2...v0.4.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c3ebc88501f..b83ae969661 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.2" + rev: "v0.4.3" hooks: - id: ruff args: ["--fix"] From 8efbefb2d7968e44ea3e220ab102872c0f96ccff Mon Sep 17 00:00:00 2001 From: Brian Okken <1568356+okken@users.noreply.github.com> Date: Tue, 7 May 2024 10:37:00 -0700 Subject: [PATCH 025/199] New --xfail-tb flag (#12280) Fix #12231 --- changelog/12231.feature.rst | 11 +++++ src/_pytest/terminal.py | 25 +++++++++--- testing/test_terminal.py | 81 ++++++++++++++++++++++++------------- 3 files changed, 83 insertions(+), 34 deletions(-) create mode 100644 changelog/12231.feature.rst diff --git a/changelog/12231.feature.rst b/changelog/12231.feature.rst new file mode 100644 index 00000000000..dad04bc20c1 --- /dev/null +++ b/changelog/12231.feature.rst @@ -0,0 +1,11 @@ +Added `--xfail-tb` flag, which turns on traceback output for XFAIL results. + +* If the `--xfail-tb` flag is not sent, tracebacks for XFAIL results are NOT shown. +* The style of traceback for XFAIL is set with `--tb`, and can be `auto|long|short|line|native|no`. +* Note: Even if you have `--xfail-tb` set, you won't see them if `--tb=no`. + +Some history: + +With pytest 8.0, `-rx` or `-ra` would not only turn on summary reports for xfail, but also report the tracebacks for xfail results. This caused issues with some projects that utilize xfail, but don't want to see all of the xfail tracebacks. + +This change detaches xfail tracebacks from `-rx`, and now we turn on xfail tracebacks with `--xfail-tb`. With this, the default `-rx`/ `-ra` behavior is identical to pre-8.0 with respect to xfail tracebacks. While this is a behavior change, it brings default behavior back to pre-8.0.0 behavior, which ultimately was considered the better course of action. diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index d05eb7e3dce..2eef8d1f227 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -216,6 +216,13 @@ def pytest_addoption(parser: Parser) -> None: choices=["auto", "long", "short", "no", "line", "native"], help="Traceback print mode (auto/long/short/line/native/no)", ) + group._addoption( + "--xfail-tb", + action="store_true", + dest="xfail_tb", + default=False, + help="Show tracebacks for xfail (as long as --tb != no)", + ) group._addoption( "--show-capture", action="store", @@ -1086,21 +1093,29 @@ def print_teardown_sections(self, rep: TestReport) -> None: self._tw.line(content) def summary_failures(self) -> None: - self.summary_failures_combined("failed", "FAILURES") + style = self.config.option.tbstyle + self.summary_failures_combined("failed", "FAILURES", style=style) def summary_xfailures(self) -> None: - self.summary_failures_combined("xfailed", "XFAILURES", "x") + show_tb = self.config.option.xfail_tb + style = self.config.option.tbstyle if show_tb else "no" + self.summary_failures_combined("xfailed", "XFAILURES", style=style) def summary_failures_combined( - self, which_reports: str, sep_title: str, needed_opt: Optional[str] = None + self, + which_reports: str, + sep_title: str, + *, + style: str, + needed_opt: Optional[str] = None, ) -> None: - if self.config.option.tbstyle != "no": + if style != "no": if not needed_opt or self.hasopt(needed_opt): reports: List[BaseReport] = self.getreports(which_reports) if not reports: return self.write_sep("=", sep_title) - if self.config.option.tbstyle == "line": + if style == "line": for rep in reports: line = self._getcrashline(rep) self.write_line(line) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index dd5fa4bf679..19848e7cbaf 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2909,54 +2909,77 @@ def test_xfail_reason(): assert result.stdout.lines.count(expect2) == 1 -def test_summary_xfail_tb(pytester: Pytester) -> None: - pytester.makepyfile( +@pytest.fixture() +def xfail_testfile(pytester: Pytester) -> Path: + return pytester.makepyfile( """ import pytest - @pytest.mark.xfail - def test_xfail(): + def test_fail(): a, b = 1, 2 assert a == b + + @pytest.mark.xfail + def test_xfail(): + c, d = 3, 4 + assert c == d """ ) - result = pytester.runpytest("-rx") + + +def test_xfail_tb_default(xfail_testfile, pytester: Pytester) -> None: + result = pytester.runpytest(xfail_testfile) + + # test_fail, show traceback result.stdout.fnmatch_lines( [ + "*= FAILURES =*", + "*_ test_fail _*", + "*def test_fail():*", + "* a, b = 1, 2*", + "*> assert a == b*", + "*E assert 1 == 2*", + ] + ) + + # test_xfail, don't show traceback + result.stdout.no_fnmatch_line("*= XFAILURES =*") + + +def test_xfail_tb_true(xfail_testfile, pytester: Pytester) -> None: + result = pytester.runpytest(xfail_testfile, "--xfail-tb") + + # both test_fail and test_xfail, show traceback + result.stdout.fnmatch_lines( + [ + "*= FAILURES =*", + "*_ test_fail _*", + "*def test_fail():*", + "* a, b = 1, 2*", + "*> assert a == b*", + "*E assert 1 == 2*", "*= XFAILURES =*", "*_ test_xfail _*", - "* @pytest.mark.xfail*", - "* def test_xfail():*", - "* a, b = 1, 2*", - "> *assert a == b*", - "E *assert 1 == 2*", - "test_summary_xfail_tb.py:6: AssertionError*", - "*= short test summary info =*", - "XFAIL test_summary_xfail_tb.py::test_xfail", - "*= 1 xfailed in * =*", + "*def test_xfail():*", + "* c, d = 3, 4*", + "*> assert c == d*", + "*E assert 3 == 4*", + "*short test summary info*", ] ) -def test_xfail_tb_line(pytester: Pytester) -> None: - pytester.makepyfile( - """ - import pytest +def test_xfail_tb_line(xfail_testfile, pytester: Pytester) -> None: + result = pytester.runpytest(xfail_testfile, "--xfail-tb", "--tb=line") - @pytest.mark.xfail - def test_xfail(): - a, b = 1, 2 - assert a == b - """ - ) - result = pytester.runpytest("-rx", "--tb=line") + # both test_fail and test_xfail, show line result.stdout.fnmatch_lines( [ + "*= FAILURES =*", + "*test_xfail_tb_line.py:5: assert 1 == 2", "*= XFAILURES =*", - "*test_xfail_tb_line.py:6: assert 1 == 2", - "*= short test summary info =*", - "XFAIL test_xfail_tb_line.py::test_xfail", - "*= 1 xfailed in * =*", + "*test_xfail_tb_line.py:10: assert 3 == 4", + "*short test summary info*", ] ) From d49f1fc4dbf9e04da016335d9e82527423d9ad1f Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 8 May 2024 15:13:30 +0200 Subject: [PATCH 026/199] doc: update sprint repo link (#12296) --- doc/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 67501c0530c..83eb27b0a53 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -5,7 +5,7 @@ - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): * **June 11th to 13th 2024**, Remote * **March 4th to 6th 2025**, Leipzig, Germany / Remote - - `pytest development sprint `_, **June 17th -- 22nd 2024** + - `pytest development sprint `_, **June 17th -- 22nd 2024** - pytest tips and tricks for a better testsuite, `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague Also see :doc:`previous talks and blogposts `. From 5af46f3d4e8264c50ffd635dbb395199670e70b7 Mon Sep 17 00:00:00 2001 From: Yutian Li Date: Thu, 9 May 2024 14:04:58 -0400 Subject: [PATCH 027/199] Fix crashing under squashfuse_ll read-only mounts (#12301) Fixes #12300 --- AUTHORS | 1 + changelog/12300.bugfix.rst | 1 + src/_pytest/assertion/rewrite.py | 5 ++++- testing/test_assertrewrite.py | 5 +++++ 4 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 changelog/12300.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 4619cf1bc44..54ed85fc732 100644 --- a/AUTHORS +++ b/AUTHORS @@ -441,6 +441,7 @@ Yao Xiao Yoav Caspi Yuliang Shao Yusuke Kadowaki +Yutian Li Yuval Shimon Zac Hatfield-Dodds Zachary Kneupper diff --git a/changelog/12300.bugfix.rst b/changelog/12300.bugfix.rst new file mode 100644 index 00000000000..6c162482022 --- /dev/null +++ b/changelog/12300.bugfix.rst @@ -0,0 +1 @@ +Fixed handling of 'Function not implemented' error under squashfuse_ll, which is a different way to say that the mountpoint is read-only. diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index b6f14aa9294..3d5df0d6c34 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -1171,7 +1171,10 @@ def try_makedirs(cache_dir: Path) -> bool: return False except OSError as e: # as of now, EROFS doesn't have an equivalent OSError-subclass - if e.errno == errno.EROFS: + # + # squashfuse_ll returns ENOSYS "OSError: [Errno 38] Function not + # implemented" for a read-only error + if e.errno in {errno.EROFS, errno.ENOSYS}: return False raise return True diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index bedf6e27606..82c7055b968 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1972,6 +1972,11 @@ def fake_mkdir(p, exist_ok=False, *, exc): monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) assert not try_makedirs(p) + err = OSError() + err.errno = errno.ENOSYS + monkeypatch.setattr(os, "makedirs", partial(fake_mkdir, exc=err)) + assert not try_makedirs(p) + # unhandled OSError should raise err = OSError() err.errno = errno.ECHILD From e20a08565f6ecdd7953589e6da11b998e94c1948 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 12 May 2024 00:21:02 +0000 Subject: [PATCH 028/199] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 126 ++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 43 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 2a68f9e49a6..d03861ccfd1 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =3.5.0) :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A + :pypi:`pytest-aws-apigateway` pytest plugin for AWS ApiGateway May 10, 2024 4 - Beta pytest :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A :pypi:`pytest-aws-fixtures` A series of fixtures to use in integration tests involving actual AWS services. Feb 02, 2024 N/A pytest (>=8.0.0,<9.0.0) :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) @@ -136,6 +137,7 @@ This list contains 1452 plugins. :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 Jan 31, 2024 5 - Production/Stable pytest>=7.0.0 + :pypi:`pytest-batch-regression` A pytest plugin to repeat the entire test suite in batches. May 08, 2024 N/A pytest>=6.0.0 :pypi:`pytest-bdd` BDD for pytest Mar 17, 2024 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 Dec 31, 2023 4 - Beta pytest >=5.0 @@ -145,7 +147,7 @@ This list contains 1452 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 03, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 08, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -169,6 +171,7 @@ This list contains 1452 plugins. :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-bq` BigQuery fixtures and fixture factories for Pytest. May 08, 2024 5 - Production/Stable pytest>=6.2 :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) @@ -238,7 +241,7 @@ This list contains 1452 plugins. :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 Mar 18, 2024 N/A pytest<9,>=4 + :pypi:`pytest-cmake` Provide CMake module for Pytest May 06, 2024 N/A pytest<9,>=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_codeblocks` Test code blocks in your READMEs Sep 17, 2023 5 - Production/Stable pytest >= 7.0.0 @@ -356,7 +359,7 @@ This list contains 1452 plugins. :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. Oct 18, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 :pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest - :pypi:`pytest-ditto` May 04, 2024 4 - Beta pytest>=3.5.0 + :pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. May 07, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.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 @@ -479,7 +482,7 @@ This list contains 1452 plugins. :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 Oct 07, 2023 N/A pytest <8,>=5 :pypi:`pytest-execution-timer` A timer for the phases of Pytest's execution. Dec 24, 2021 4 - Beta N/A - :pypi:`pytest-exit-code` A pytest plugin that overrides the built-in exit codes to retain more information about the test results. Feb 23, 2024 4 - Beta pytest >=6.2.0 + :pypi:`pytest-exit-code` A pytest plugin that overrides the built-in exit codes to retain more information about the test results. May 06, 2024 4 - Beta pytest>=6.2.0 :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 @@ -612,19 +615,19 @@ This list contains 1452 plugins. :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. Mar 16, 2024 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. Feb 07, 2024 4 - Beta pytest (>=8.0.0,<9.0.0) - :pypi:`pytest-helm-templates` Pytest fixtures for unit testing the output of helm templates Apr 05, 2024 N/A pytest~=7.4.0; extra == "dev" + :pypi:`pytest-helm-templates` Pytest fixtures for unit testing the output of helm templates May 08, 2024 N/A pytest~=7.4.0; extra == "dev" :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-henry` Aug 29, 2023 N/A N/A :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) - :pypi:`pytest-himark` A plugin that will filter pytest's test collection using a json file. It will read a json file provided with a --json argument in pytest command line (or in pytest.ini), search the markers key and automatically add -m option to the command line for filtering out the tests marked with disabled markers. Apr 14, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-himark` This plugin aims to create markers automatically based on a json configuration. May 10, 2024 4 - Beta pytest>=6.2.0 :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-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 May 04, 2024 3 - Alpha pytest==8.1.1 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components May 11, 2024 3 - Alpha pytest==8.1.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` Apr 18, 2024 N/A N/A @@ -648,7 +651,7 @@ This list contains 1452 plugins. :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 Feb 24, 2024 3 - Alpha N/A - :pypi:`pytest-httptesting` http_testing framework on top of pytest Jul 24, 2023 N/A pytest (>=7.2.0,<8.0.0) + :pypi:`pytest-httptesting` http_testing framework on top of pytest May 08, 2024 N/A pytest<9.0.0,>=8.2.0 :pypi:`pytest-httpx` Send responses to httpx. Feb 21, 2024 5 - Production/Stable pytest <9,>=7 :pypi:`pytest-httpx-blockage` Disable httpx requests during a test run Feb 16, 2023 N/A pytest (>=7.2.1) :pypi:`pytest-httpx-recorder` Recorder feature based on pytest_httpx, like recorder feature in responses. Jan 04, 2024 5 - Production/Stable pytest @@ -676,7 +679,7 @@ This list contains 1452 plugins. :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest :pypi:`pytest-inmanta-extensions` Inmanta tests package Apr 02, 2024 5 - Production/Stable N/A - :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Apr 15, 2024 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules May 10, 2024 5 - Production/Stable N/A :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Feb 22, 2024 4 - Beta pytest :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A :pypi:`pytest-in-robotframework` The extension enables easy execution of pytest tests within the Robot Framework environment. Mar 02, 2024 N/A pytest @@ -729,6 +732,7 @@ This list contains 1452 plugins. :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-kookit` Your simple but kooky integration testing with pytest May 06, 2024 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` Sep 14, 2023 N/A pytest (>=7.2.1,<8.0.0) @@ -845,7 +849,7 @@ This list contains 1452 plugins. :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 Feb 14, 2024 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 31, 2024 4 - Beta pytest<8; extra == "test" + :pypi:`pytest-mqtt` pytest-mqtt supports testing systems based on MQTT May 08, 2024 4 - Beta pytest<9; 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 @@ -964,8 +968,8 @@ This list contains 1452 plugins. :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 Feb 02, 2024 N/A pytest (<9.0.0,>=6.2.4) - :pypi:`pytest_playwright_async` ASYNC Pytest plugin for Playwright Feb 25, 2024 N/A N/A + :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers May 06, 2024 N/A N/A + :pypi:`pytest_playwright_async` ASYNC Pytest plugin for Playwright May 04, 2024 N/A N/A :pypi:`pytest-playwright-asyncio` Aug 29, 2023 N/A N/A :pypi:`pytest-playwright-enhanced` A pytest plugin for playwright python Mar 24, 2024 N/A pytest<9.0.0,>=8.0.0 :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A @@ -1028,7 +1032,7 @@ This list contains 1452 plugins. :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 11, 2023 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 Feb 03, 2024 N/A pytest + :pypi:`pytest-pyreport` PyReport is a lightweight reporting plugin for Pytest that provides concise HTML report May 05, 2024 N/A pytest :pypi:`pytest-pyright` Pytest plugin for type checking code with Pyright Jan 26, 2024 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". Jan 02, 2024 N/A pytest (>=7.2.1,<8.0.0) :pypi:`pytest-pystack` Plugin to run pystack after a timeout for a test suite. Jan 04, 2024 N/A pytest >=3.5.0 @@ -1049,7 +1053,7 @@ This list contains 1452 plugins. :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_quickify` Run test suites with pytest-quickify. Jun 14, 2019 N/A pytest - :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest Jul 05, 2023 5 - Production/Stable pytest (>=6.2) + :pypi:`pytest-rabbitmq` RabbitMQ process and client fixtures for pytest May 08, 2024 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) @@ -1062,7 +1066,7 @@ This list contains 1452 plugins. :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 Jan 20, 2024 5 - Production/Stable pytest >=3.0.0 - :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection Mar 18, 2024 4 - Beta pytest >=7.4.3 + :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection May 10, 2024 4 - Beta pytest>=7.4.3 :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A :pypi:`pytest-reana` Pytest fixtures for REANA. Mar 14, 2024 3 - Alpha N/A :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A @@ -1156,6 +1160,7 @@ This list contains 1452 plugins. :pypi:`pytest-salt-factories` Pytest Salt Plugin Mar 22, 2024 5 - Production/Stable pytest>=7.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-sample-argvalues` A utility function to help choose a random sample from your argvalues in pytest. May 07, 2024 N/A pytest :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 @@ -1178,7 +1183,7 @@ This list contains 1452 plugins. :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 Dec 19, 2023 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 Mar 19, 2024 3 - Alpha pytest>=6.2 + :pypi:`pytest-servers` pytest servers May 09, 2024 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 @@ -1246,7 +1251,7 @@ This list contains 1452 plugins. :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 Apr 19, 2024 N/A pytest (>5.4.0,<8) - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Mar 26, 2024 N/A N/A + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX May 10, 2024 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 @@ -2135,6 +2140,13 @@ This list contains 1452 plugins. pytest plugin for testing AWS resource configurations + :pypi:`pytest-aws-apigateway` + *last release*: May 10, 2024, + *status*: 4 - Beta, + *requires*: pytest + + pytest plugin for AWS ApiGateway + :pypi:`pytest-aws-config` *last release*: May 28, 2021, *status*: N/A, @@ -2205,6 +2217,13 @@ This list contains 1452 plugins. pytest plugin for URL based testing + :pypi:`pytest-batch-regression` + *last release*: May 08, 2024, + *status*: N/A, + *requires*: pytest>=6.0.0 + + A pytest plugin to repeat the entire test suite in batches. + :pypi:`pytest-bdd` *last release*: Mar 17, 2024, *status*: 6 - Mature, @@ -2269,7 +2288,7 @@ This list contains 1452 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: May 03, 2024, + *last release*: May 08, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2436,6 +2455,13 @@ This list contains 1452 plugins. A py.test plug-in to enable drop to bpdb debugger on test failure. + :pypi:`pytest-bq` + *last release*: May 08, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=6.2 + + BigQuery fixtures and fixture factories for Pytest. + :pypi:`pytest-bravado` *last release*: Feb 15, 2022, *status*: N/A, @@ -2920,7 +2946,7 @@ This list contains 1452 plugins. Distribute tests to cloud machines without fuss :pypi:`pytest-cmake` - *last release*: Mar 18, 2024, + *last release*: May 06, 2024, *status*: N/A, *requires*: pytest<9,>=4 @@ -3746,11 +3772,11 @@ This list contains 1452 plugins. Pytest plugin to record discovered tests in a file :pypi:`pytest-ditto` - *last release*: May 04, 2024, + *last release*: May 07, 2024, *status*: 4 - Beta, *requires*: pytest>=3.5.0 - + Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. :pypi:`pytest-django` *last release*: Jan 30, 2024, @@ -4607,9 +4633,9 @@ This list contains 1452 plugins. A timer for the phases of Pytest's execution. :pypi:`pytest-exit-code` - *last release*: Feb 23, 2024, + *last release*: May 06, 2024, *status*: 4 - Beta, - *requires*: pytest >=6.2.0 + *requires*: pytest>=6.2.0 A pytest plugin that overrides the built-in exit codes to retain more information about the test results. @@ -5538,7 +5564,7 @@ This list contains 1452 plugins. A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. :pypi:`pytest-helm-templates` - *last release*: Apr 05, 2024, + *last release*: May 08, 2024, *status*: N/A, *requires*: pytest~=7.4.0; extra == "dev" @@ -5580,11 +5606,11 @@ This list contains 1452 plugins. Hide captured output :pypi:`pytest-himark` - *last release*: Apr 14, 2024, + *last release*: May 10, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 - A plugin that will filter pytest's test collection using a json file. It will read a json file provided with a --json argument in pytest command line (or in pytest.ini), search the markers key and automatically add -m option to the command line for filtering out the tests marked with disabled markers. + This plugin aims to create markers automatically based on a json configuration. :pypi:`pytest-historic` *last release*: Apr 08, 2020, @@ -5622,7 +5648,7 @@ This list contains 1452 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: May 04, 2024, + *last release*: May 11, 2024, *status*: 3 - Alpha, *requires*: pytest==8.1.1 @@ -5790,9 +5816,9 @@ This list contains 1452 plugins. pytest-httpserver is a httpserver for pytest :pypi:`pytest-httptesting` - *last release*: Jul 24, 2023, + *last release*: May 08, 2024, *status*: N/A, - *requires*: pytest (>=7.2.0,<8.0.0) + *requires*: pytest<9.0.0,>=8.2.0 http_testing framework on top of pytest @@ -5986,7 +6012,7 @@ This list contains 1452 plugins. Inmanta tests package :pypi:`pytest-inmanta-lsm` - *last release*: Apr 15, 2024, + *last release*: May 10, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -6356,6 +6382,13 @@ This list contains 1452 plugins. Run Konira DSL tests with py.test + :pypi:`pytest-kookit` + *last release*: May 06, 2024, + *status*: N/A, + *requires*: N/A + + Your simple but kooky integration testing with pytest + :pypi:`pytest-koopmans` *last release*: Nov 21, 2022, *status*: 4 - Beta, @@ -7169,9 +7202,9 @@ This list contains 1452 plugins. low-startup-overhead, scalable, distributed-testing pytest plugin :pypi:`pytest-mqtt` - *last release*: Mar 31, 2024, + *last release*: May 08, 2024, *status*: 4 - Beta, - *requires*: pytest<8; extra == "test" + *requires*: pytest<9; extra == "test" pytest-mqtt supports testing systems based on MQTT @@ -8002,14 +8035,14 @@ This list contains 1452 plugins. Pytest plugin for reading playbooks. :pypi:`pytest-playwright` - *last release*: Feb 02, 2024, + *last release*: May 06, 2024, *status*: N/A, - *requires*: pytest (<9.0.0,>=6.2.4) + *requires*: N/A A pytest wrapper with fixtures for Playwright to automate web browsers :pypi:`pytest_playwright_async` - *last release*: Feb 25, 2024, + *last release*: May 04, 2024, *status*: N/A, *requires*: N/A @@ -8450,7 +8483,7 @@ This list contains 1452 plugins. Pyramid server fixture for py.test :pypi:`pytest-pyreport` - *last release*: Feb 03, 2024, + *last release*: May 05, 2024, *status*: N/A, *requires*: pytest @@ -8597,9 +8630,9 @@ This list contains 1452 plugins. Run test suites with pytest-quickify. :pypi:`pytest-rabbitmq` - *last release*: Jul 05, 2023, + *last release*: May 08, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2) + *requires*: pytest>=6.2 RabbitMQ process and client fixtures for pytest @@ -8688,9 +8721,9 @@ This list contains 1452 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-ranking` - *last release*: Mar 18, 2024, + *last release*: May 10, 2024, *status*: 4 - Beta, - *requires*: pytest >=7.4.3 + *requires*: pytest>=7.4.3 A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection @@ -9345,6 +9378,13 @@ This list contains 1452 plugins. Simple PyTest Plugin For Salt's Test Suite Specifically + :pypi:`pytest-sample-argvalues` + *last release*: May 07, 2024, + *status*: N/A, + *requires*: pytest + + A utility function to help choose a random sample from your argvalues in pytest. + :pypi:`pytest-sanic` *last release*: Oct 25, 2021, *status*: N/A, @@ -9500,7 +9540,7 @@ This list contains 1452 plugins. Automatically mocks resources from serverless.yml in pytest using moto. :pypi:`pytest-servers` - *last release*: Mar 19, 2024, + *last release*: May 09, 2024, *status*: 3 - Alpha, *requires*: pytest>=6.2 @@ -9976,7 +10016,7 @@ This list contains 1452 plugins. A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Mar 26, 2024, + *last release*: May 10, 2024, *status*: N/A, *requires*: N/A From 90a3ef2f36f6c282d1175ce0c13149ce57d1362d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 12 May 2024 22:28:49 +0300 Subject: [PATCH 029/199] python: add workaround for permission error crashes from non-selected directories Fix #12120. --- changelog/12120.bugfix.rst | 1 + src/_pytest/python.py | 7 ++++++- testing/test_collection.py | 17 +++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 changelog/12120.bugfix.rst diff --git a/changelog/12120.bugfix.rst b/changelog/12120.bugfix.rst new file mode 100644 index 00000000000..b1ca4913b37 --- /dev/null +++ b/changelog/12120.bugfix.rst @@ -0,0 +1 @@ +Fix `PermissionError` crashes arising from directories which are not selected on the command-line. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 68eceb7f4f3..8efaf80787a 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -176,7 +176,12 @@ def pytest_collect_directory( path: Path, parent: nodes.Collector ) -> Optional[nodes.Collector]: pkginit = path / "__init__.py" - if pkginit.is_file(): + try: + has_pkginit = pkginit.is_file() + except PermissionError: + # See https://github.com/pytest-dev/pytest/issues/12120#issuecomment-2106349096. + return None + if has_pkginit: return Package.from_parent(parent, path=path) return None diff --git a/testing/test_collection.py b/testing/test_collection.py index 995e2999bbe..8ff38a334f4 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -285,6 +285,23 @@ def test_testpaths_ini(self, pytester: Pytester, monkeypatch: MonkeyPatch) -> No items, reprec = pytester.inline_genitems() assert [x.name for x in items] == [f"test_{dirname}"] + def test_missing_permissions_on_unselected_directory_doesnt_crash( + self, pytester: Pytester + ) -> None: + """Regression test for #12120.""" + test = pytester.makepyfile(test="def test(): pass") + bad = pytester.mkdir("bad") + try: + bad.chmod(0) + + result = pytester.runpytest(test) + finally: + bad.chmod(750) + bad.rmdir() + + assert result.ret == ExitCode.OK + result.assert_outcomes(passed=1) + class TestCollectPluginHookRelay: def test_pytest_collect_file(self, pytester: Pytester) -> None: From eea04c289108749ba96ed42b410baaae5932708d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 05:56:16 +0200 Subject: [PATCH 030/199] build(deps): Bump django in /testing/plugins_integration (#12314) Bumps [django](https://github.com/django/django) from 5.0.4 to 5.0.6. - [Commits](https://github.com/django/django/compare/5.0.4...5.0.6) --- updated-dependencies: - dependency-name: django dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index d60bc5d3c08..d4d0fc633c0 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,5 +1,5 @@ anyio[curio,trio]==4.3.0 -django==5.0.4 +django==5.0.6 pytest-asyncio==0.23.6 pytest-bdd==7.1.2 pytest-cov==5.0.0 From 37489d3c6c81bc3f1badf7c50e568d4fffe6deb9 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 13 May 2024 12:27:20 +0300 Subject: [PATCH 031/199] python,unittest: don't collect abstract classes Fix #12275. --- changelog/12275.bugfix.rst | 1 + src/_pytest/python.py | 6 +++++- src/_pytest/unittest.py | 8 +++++++- testing/python/collect.py | 26 ++++++++++++++++++++++++++ testing/test_unittest.py | 28 ++++++++++++++++++++++++++++ 5 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 changelog/12275.bugfix.rst diff --git a/changelog/12275.bugfix.rst b/changelog/12275.bugfix.rst new file mode 100644 index 00000000000..2d040a3a063 --- /dev/null +++ b/changelog/12275.bugfix.rst @@ -0,0 +1 @@ +Fix collection error upon encountering an :mod:`abstract ` class, including abstract `unittest.TestCase` subclasses. diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 68eceb7f4f3..5196b9fe891 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -368,7 +368,11 @@ def istestfunction(self, obj: object, name: str) -> bool: return False def istestclass(self, obj: object, name: str) -> bool: - return self.classnamefilter(name) or self.isnosetest(obj) + if not (self.classnamefilter(name) or self.isnosetest(obj)): + return False + if inspect.isabstract(obj): + return False + return True def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: """Check if the given name matches the prefix or glob-pattern defined diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 8f1791bf744..919b497c295 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs """Discover and run std-library "unittest" style tests.""" +import inspect import sys import traceback import types @@ -49,14 +50,19 @@ def pytest_pycollect_makeitem( collector: Union[Module, Class], name: str, obj: object ) -> Optional["UnitTestCase"]: - # Has unittest been imported and is obj a subclass of its TestCase? try: + # Has unittest been imported? ut = sys.modules["unittest"] + # Is obj a subclass of unittest.TestCase? # Type ignored because `ut` is an opaque module. if not issubclass(obj, ut.TestCase): # type: ignore return None except Exception: return None + # Is obj a concrete class? + # Abstract classes can't be instantiated so no point collecting them. + if inspect.isabstract(obj): + return None # Yes, so let's collect it. return UnitTestCase.from_parent(collector, name=name, obj=obj) diff --git a/testing/python/collect.py b/testing/python/collect.py index a1a7dc8978d..843fa3c0e6b 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -262,6 +262,32 @@ def prop(self): result = pytester.runpytest() assert result.ret == ExitCode.NO_TESTS_COLLECTED + def test_abstract_class_is_not_collected(self, pytester: Pytester) -> None: + """Regression test for #12275 (non-unittest version).""" + pytester.makepyfile( + """ + import abc + + class TestBase(abc.ABC): + @abc.abstractmethod + def abstract1(self): pass + + @abc.abstractmethod + def abstract2(self): pass + + def test_it(self): pass + + class TestPartial(TestBase): + def abstract1(self): pass + + class TestConcrete(TestPartial): + def abstract2(self): pass + """ + ) + result = pytester.runpytest() + assert result.ret == ExitCode.OK + result.assert_outcomes(passed=1) + class TestFunction: def test_getmodulecollector(self, pytester: Pytester) -> None: diff --git a/testing/test_unittest.py b/testing/test_unittest.py index d726e74d603..f73e083be78 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1640,3 +1640,31 @@ def test_it2(self): pass assert skipped == 1 assert failed == 0 assert reprec.ret == ExitCode.NO_TESTS_COLLECTED + + +def test_abstract_testcase_is_not_collected(pytester: Pytester) -> None: + """Regression test for #12275.""" + pytester.makepyfile( + """ + import abc + import unittest + + class TestBase(unittest.TestCase, abc.ABC): + @abc.abstractmethod + def abstract1(self): pass + + @abc.abstractmethod + def abstract2(self): pass + + def test_it(self): pass + + class TestPartial(TestBase): + def abstract1(self): pass + + class TestConcrete(TestPartial): + def abstract2(self): pass + """ + ) + result = pytester.runpytest() + assert result.ret == ExitCode.OK + result.assert_outcomes(passed=1) From ebe2e8eac9db5a662cf60c9058e0c5ba480d3ad0 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 13 May 2024 22:11:40 +0300 Subject: [PATCH 032/199] changelog: document unittest 8.2 change as breaking --- doc/en/changelog.rst | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 2630d95cf28..fb276a93839 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -31,6 +31,24 @@ with advance notice in the **Deprecations** section of releases. pytest 8.2.0 (2024-04-27) ========================= +Breaking Changes +---------------- + +- `#12089 `_: pytest now requires that :class:`unittest.TestCase` subclasses can be instantiated freely using ``MyTestCase('runTest')``. + + If the class doesn't allow this, you may see an error during collection such as ``AttributeError: 'MyTestCase' object has no attribute 'runTest'``. + + Classes which do not override ``__init__``, or do not access the test method in ``__init__`` using ``getattr`` or similar, are unaffected. + + Classes which do should take care to not crash when ``"runTest"`` is given, as is shown in `unittest.TestCases's implementation `_. + Alternatively, consider using :meth:`setUp ` instead of ``__init__``. + + If you run into this issue using ``tornado.AsyncTestCase``, please see `issue 12263 `_. + + If you run into this issue using an abstract ``TestCase`` subclass, please see `issue 12275 `_. + + Historical note: the effect of this change on custom TestCase implementations was not properly considered initially, this is why it was done in a minor release. We apologize for the inconvenience. + Deprecations ------------ From 3a64c47f1f713fbc154d04960fe8382ecf94bdc4 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 14 May 2024 23:26:15 +0300 Subject: [PATCH 033/199] cacheprovider: fix `.pytest_cache` not being world-readable Fix #12308. --- changelog/12308.bugfix.rst | 1 + src/_pytest/cacheprovider.py | 7 +++++++ testing/test_cacheprovider.py | 15 +++++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 changelog/12308.bugfix.rst diff --git a/changelog/12308.bugfix.rst b/changelog/12308.bugfix.rst new file mode 100644 index 00000000000..07995427a1a --- /dev/null +++ b/changelog/12308.bugfix.rst @@ -0,0 +1 @@ +Fix a regression in pytest 8.2.0 where the permissions of automatically-created ``.pytest_cache`` directories became ``rwx------`` instead of the expected ``rwxr-xr-x``. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 4593e2a8172..06def557e41 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -213,6 +213,13 @@ def _ensure_cache_dir_and_supporting_files(self) -> None: dir=self._cachedir.parent, ) as newpath: path = Path(newpath) + + # Reset permissions to the default, see #12308. + # Note: there's no way to get the current umask atomically, eek. + umask = os.umask(0o022) + os.umask(umask) + path.chmod(0o777 - umask) + with open(path.joinpath("README.md"), "xt", encoding="UTF-8") as f: f.write(README_CONTENT) with open(path.joinpath(".gitignore"), "xt", encoding="UTF-8") as f: diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 6c18c358a80..c85c1d04cc7 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -31,6 +31,21 @@ def test_config_cache_mkdir(self, pytester: Pytester) -> None: p = config.cache.mkdir("name") assert p.is_dir() + def test_cache_dir_permissions(self, pytester: Pytester) -> None: + """The .pytest_cache directory should have world-readable permissions + (depending on umask). + + Regression test for #12308. + """ + pytester.makeini("[pytest]") + config = pytester.parseconfigure() + assert config.cache is not None + p = config.cache.mkdir("name") + assert p.is_dir() + # Instead of messing with umask, make sure .pytest_cache has the same + # permissions as the default that `mkdir` gives `p`. + assert (p.parent.stat().st_mode & 0o777) == (p.stat().st_mode & 0o777) + def test_config_cache_dataerror(self, pytester: Pytester) -> None: pytester.makeini("[pytest]") config = pytester.parseconfigure() From 1acf56d033e801f34ddc78d1124d818d96ba31b7 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 15 May 2024 17:11:26 +0300 Subject: [PATCH 034/199] fixtures: fix non-working package-scope parametrization reordering The `.parent` was incorrectly removed in a21fb87a90974189c1b8b26189959507189bb3a1, but it was actually correct (well, it assumes that Module.path.parent == Package.path, which I'm not sure is always correct, but that's a different matter). Restore it. Fix #12328. --- changelog/12328.bugfix.rst | 1 + src/_pytest/fixtures.py | 3 ++- testing/python/fixtures.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 changelog/12328.bugfix.rst diff --git a/changelog/12328.bugfix.rst b/changelog/12328.bugfix.rst new file mode 100644 index 00000000000..f334425850b --- /dev/null +++ b/changelog/12328.bugfix.rst @@ -0,0 +1 @@ +Fix a regression in pytest 8.0.0 where package-scoped parameterized items were not correctly reordered to minimize setups/teardowns in some cases. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 5a290718fe4..a271f947d45 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -187,7 +187,8 @@ def get_parametrized_fixture_keys( if scope is Scope.Session: scoped_item_path = None elif scope is Scope.Package: - scoped_item_path = item.path + # Package key = module's directory. + scoped_item_path = item.path.parent elif scope is Scope.Module: scoped_item_path = item.path elif scope is Scope.Class: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index aec0deb99a1..13ab4904c1a 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -4274,6 +4274,39 @@ def test_func(self, f2, f1, m2): request = TopRequest(items[0], _ispytest=True) assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split() + def test_parametrized_package_scope_reordering(self, pytester: Pytester) -> None: + """A paramaterized package-scoped fixture correctly reorders items to + minimize setups & teardowns. + + Regression test for #12328. + """ + pytester.makepyfile( + __init__="", + conftest=""" + import pytest + @pytest.fixture(scope="package", params=["a", "b"]) + def fix(request): + return request.param + """, + test_1="def test1(fix): pass", + test_2="def test2(fix): pass", + ) + + result = pytester.runpytest("--setup-plan") + assert result.ret == ExitCode.OK + result.stdout.fnmatch_lines( + [ + " SETUP P fix['a']", + " test_1.py::test1[a] (fixtures used: fix, request)", + " test_2.py::test2[a] (fixtures used: fix, request)", + " TEARDOWN P fix['a']", + " SETUP P fix['b']", + " test_1.py::test1[b] (fixtures used: fix, request)", + " test_2.py::test2[b] (fixtures used: fix, request)", + " TEARDOWN P fix['b']", + ], + ) + def test_multiple_packages(self, pytester: Pytester) -> None: """Complex test involving multiple package fixtures. Make sure teardowns are executed in order. From 8d0081182266a38cf7e4abd1d3cebb74a622469a Mon Sep 17 00:00:00 2001 From: Josh Soref <2119212+jsoref@users.noreply.github.com> Date: Wed, 15 May 2024 12:49:34 -0400 Subject: [PATCH 035/199] Spelling and minor changes (#12122) --- doc/en/announce/release-2.0.0.rst | 4 +-- doc/en/announce/release-2.2.2.rst | 2 +- doc/en/announce/release-2.4.0.rst | 2 +- doc/en/announce/release-2.5.0.rst | 2 +- doc/en/announce/release-2.6.0.rst | 2 +- doc/en/announce/release-2.7.0.rst | 2 +- doc/en/announce/sprint2016.rst | 2 +- doc/en/changelog.rst | 38 +++++++++++----------- doc/en/deprecations.rst | 2 +- doc/en/example/customdirectory/conftest.py | 2 +- doc/en/getting-started.rst | 2 +- doc/en/naming20.rst | 2 +- doc/en/reference/fixtures.rst | 2 +- doc/en/reference/reference.rst | 2 +- src/_pytest/_py/error.py | 2 +- src/_pytest/_py/path.py | 4 +-- src/_pytest/assertion/rewrite.py | 2 +- src/_pytest/assertion/util.py | 4 +-- src/_pytest/cacheprovider.py | 2 +- src/_pytest/compat.py | 2 +- src/_pytest/config/__init__.py | 6 ++-- src/_pytest/fixtures.py | 4 +-- src/_pytest/hookspec.py | 6 ++-- src/_pytest/legacypath.py | 4 +-- src/_pytest/mark/__init__.py | 2 +- src/_pytest/pathlib.py | 4 +-- src/_pytest/stepwise.py | 2 +- testing/_py/test_local.py | 4 +-- testing/code/test_excinfo.py | 2 +- testing/code/test_source.py | 2 +- testing/logging/test_fixture.py | 31 ++++++++++-------- testing/python/fixtures.py | 2 +- testing/python/integration.py | 4 +-- testing/test_cacheprovider.py | 2 +- testing/test_compat.py | 2 +- testing/test_config.py | 4 +-- testing/test_conftest.py | 4 +-- testing/test_debugging.py | 4 +-- testing/test_doctest.py | 6 ++-- testing/test_junitxml.py | 2 +- testing/test_legacypath.py | 2 +- testing/test_mark_expression.py | 2 +- testing/test_pluginmanager.py | 4 +-- testing/test_runner.py | 2 +- testing/test_terminal.py | 2 +- testing/test_unittest.py | 6 ++-- 46 files changed, 100 insertions(+), 97 deletions(-) diff --git a/doc/en/announce/release-2.0.0.rst b/doc/en/announce/release-2.0.0.rst index ecb1a1db988..c2a9f6da4d5 100644 --- a/doc/en/announce/release-2.0.0.rst +++ b/doc/en/announce/release-2.0.0.rst @@ -62,7 +62,7 @@ New Features - new "-q" option which decreases verbosity and prints a more nose/unittest-style "dot" output. -- many many more detailed improvements details +- many, many, more detailed improvements details Fixes ----------------------- @@ -109,7 +109,7 @@ Important Notes in conftest.py files. They will cause nothing special. - removed support for calling the pre-1.0 collection API of "run()" and "join" - removed reading option values from conftest.py files or env variables. - This can now be done much much better and easier through the ini-file + This can now be done much, much, better and easier through the ini-file mechanism and the "addopts" entry in particular. - removed the "disabled" attribute in test classes. Use the skipping and pytestmark mechanism to skip or xfail a test class. diff --git a/doc/en/announce/release-2.2.2.rst b/doc/en/announce/release-2.2.2.rst index 22ef0bc7a16..510b35ee1d0 100644 --- a/doc/en/announce/release-2.2.2.rst +++ b/doc/en/announce/release-2.2.2.rst @@ -4,7 +4,7 @@ pytest-2.2.2: bug fixes pytest-2.2.2 (updated to 2.2.3 to fix packaging issues) is a minor backward-compatible release of the versatile py.test testing tool. It contains bug fixes and a few refinements particularly to reporting with -"--collectonly", see below for betails. +"--collectonly", see below for details. For general information see here: diff --git a/doc/en/announce/release-2.4.0.rst b/doc/en/announce/release-2.4.0.rst index 138cc89576c..9b864329674 100644 --- a/doc/en/announce/release-2.4.0.rst +++ b/doc/en/announce/release-2.4.0.rst @@ -181,7 +181,7 @@ Bug fixes: partially failed (finalizers would not always be called before) - fix issue320 - fix class scope for fixtures when mixed with - module-level functions. Thanks Anatloy Bubenkoff. + module-level functions. Thanks Anatoly Bubenkoff. - you can specify "-q" or "-qq" to get different levels of "quieter" reporting (thanks Katarzyna Jachim) diff --git a/doc/en/announce/release-2.5.0.rst b/doc/en/announce/release-2.5.0.rst index c6cdcdd8a83..fe64f1b8668 100644 --- a/doc/en/announce/release-2.5.0.rst +++ b/doc/en/announce/release-2.5.0.rst @@ -83,7 +83,7 @@ holger krekel Thanks Ralph Schmitt for the precise failure example. - fix issue244 by implementing special index for parameters to only use - indices for paramentrized test ids + indices for parametrized test ids - fix issue287 by running all finalizers but saving the exception from the first failing finalizer and re-raising it so teardown will diff --git a/doc/en/announce/release-2.6.0.rst b/doc/en/announce/release-2.6.0.rst index 56fbd6cc1e4..c00df585738 100644 --- a/doc/en/announce/release-2.6.0.rst +++ b/doc/en/announce/release-2.6.0.rst @@ -73,7 +73,7 @@ holger krekel - cleanup setup.py a bit and specify supported versions. Thanks Jurko Gospodnetic for the PR. -- change XPASS colour to yellow rather then red when tests are run +- change XPASS colour to yellow rather than red when tests are run with -v. - fix issue473: work around mock putting an unbound method into a class diff --git a/doc/en/announce/release-2.7.0.rst b/doc/en/announce/release-2.7.0.rst index 2840178a07f..83cddb34157 100644 --- a/doc/en/announce/release-2.7.0.rst +++ b/doc/en/announce/release-2.7.0.rst @@ -55,7 +55,7 @@ holger krekel github. See https://pytest.org/en/stable/contributing.html . Thanks to Anatoly for pushing and initial work on this. -- fix issue650: new option ``--docttest-ignore-import-errors`` which +- fix issue650: new option ``--doctest-ignore-import-errors`` which will turn import errors in doctests into skips. Thanks Charles Cloud for the complete PR. diff --git a/doc/en/announce/sprint2016.rst b/doc/en/announce/sprint2016.rst index 8e706589876..8d47a205c71 100644 --- a/doc/en/announce/sprint2016.rst +++ b/doc/en/announce/sprint2016.rst @@ -49,7 +49,7 @@ place on 20th, 21st, 22nd, 24th and 25th. On the 23rd we took a break day for some hot hiking in the Black Forest. Sprint activity was organised heavily around pairing, with plenty of group -discusssions to take advantage of the high bandwidth, and lightning talks +discussions to take advantage of the high bandwidth, and lightning talks as well. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index fb276a93839..d5cde498859 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -168,7 +168,7 @@ Improvements - `#11311 `_: When using ``--override-ini`` for paths in invocations without a configuration file defined, the current working directory is used as the relative directory. - Previoulsy this would raise an :class:`AssertionError`. + Previously this would raise an :class:`AssertionError`. - `#11475 `_: :ref:`--import-mode=importlib ` now tries to import modules using the standard import mechanism (but still without changing :py:data:`sys.path`), falling back to importing modules directly only if that fails. @@ -1411,7 +1411,7 @@ Deprecations ``__init__`` method, they should take ``**kwargs``. See :ref:`uncooperative-constructors-deprecated` for details. - Note that a deprection warning is only emitted when there is a conflict in the + Note that a deprecation warning is only emitted when there is a conflict in the arguments pytest expected to pass. This deprecation was already part of pytest 7.0.0rc1 but wasn't documented. @@ -1453,7 +1453,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 an `os.PathLike[str]`, these plugins are unaffacted. + Since `py.path.local` is an `os.PathLike[str]`, these plugins are unaffected. 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. @@ -1961,7 +1961,7 @@ Bug Fixes the ``tmp_path``/``tmpdir`` fixture). Now the directories are created with private permissions. - pytest used to silently use a pre-existing ``/tmp/pytest-of-`` directory, + pytest used to silently use a preexisting ``/tmp/pytest-of-`` directory, even if owned by another user. This means another user could pre-create such a directory and gain control of another user's temporary directory. Now such a condition results in an error. @@ -2688,7 +2688,7 @@ Features also changes ``sys.modules`` as a side-effect), which works but has a number of drawbacks, like requiring test modules that don't live in packages to have unique names (as they need to reside under a unique name in ``sys.modules``). - ``--import-mode=importlib`` uses more fine grained import mechanisms from ``importlib`` which don't + ``--import-mode=importlib`` uses more fine-grained import mechanisms from ``importlib`` which don't require pytest to change ``sys.path`` or ``sys.modules`` at all, eliminating much of the drawbacks of the previous mode. @@ -2705,7 +2705,7 @@ Improvements ------------ - :issue:`4375`: The ``pytest`` command now suppresses the ``BrokenPipeError`` error message that - is printed to stderr when the output of ``pytest`` is piped and and the pipe is + is printed to stderr when the output of ``pytest`` is piped and the pipe is closed by the piped-to program (common examples are ``less`` and ``head``). @@ -3007,7 +3007,7 @@ Breaking Changes This hook has been marked as deprecated and not been even called by pytest for over 10 years now. -- :issue:`6673`: Reversed / fix meaning of "+/-" in error diffs. "-" means that sth. expected is missing in the result and "+" means that there are unexpected extras in the result. +- :issue:`6673`: Reversed / fix meaning of "+/-" in error diffs. "-" means that something expected is missing in the result and "+" means that there are unexpected extras in the result. - :issue:`6737`: The ``cached_result`` attribute of ``FixtureDef`` is now set to ``None`` when @@ -4612,7 +4612,7 @@ Bug Fixes Improved Documentation ---------------------- -- :issue:`4974`: Update docs for ``pytest_cmdline_parse`` hook to note availability liminations +- :issue:`4974`: Update docs for ``pytest_cmdline_parse`` hook to note availability limitations @@ -6470,7 +6470,7 @@ Features Bug Fixes --------- -- Fix hanging pexpect test on MacOS by using flush() instead of wait(). +- Fix hanging pexpect test on macOS by using flush() instead of wait(). (:issue:`2022`) - Fix restoring Python state after in-process pytest runs with the @@ -6518,7 +6518,7 @@ Trivial/Internal Changes ------------------------ - Show a simple and easy error when keyword expressions trigger a syntax error - (for example, ``"-k foo and import"`` will show an error that you can not use + (for example, ``"-k foo and import"`` will show an error that you cannot use the ``import`` keyword in expressions). (:issue:`2953`) - Change parametrized automatic test id generation to use the ``__name__`` @@ -8294,7 +8294,7 @@ time or change existing behaviors in order to make them less surprising/more use one will also have a "reprec" attribute with the recorded events/reports. - fix monkeypatch.setattr("x.y", raising=False) to actually not raise - if "y" is not a pre-existing attribute. Thanks Florian Bruhin. + if "y" is not a preexisting attribute. Thanks Florian Bruhin. - fix issue741: make running output from testdir.run copy/pasteable Thanks Bruno Oliveira. @@ -8350,7 +8350,7 @@ time or change existing behaviors in order to make them less surprising/more use - fix issue854: autouse yield_fixtures defined as class members of unittest.TestCase subclasses now work as expected. - Thannks xmo-odoo for the report and Bruno Oliveira for the PR. + Thanks xmo-odoo for the report and Bruno Oliveira for the PR. - fix issue833: --fixtures now shows all fixtures of collected test files, instead of just the fixtures declared on the first one. @@ -8454,7 +8454,7 @@ time or change existing behaviors in order to make them less surprising/more use github. See https://pytest.org/en/stable/contributing.html . Thanks to Anatoly for pushing and initial work on this. -- fix issue650: new option ``--docttest-ignore-import-errors`` which +- fix issue650: new option ``--doctest-ignore-import-errors`` which will turn import errors in doctests into skips. Thanks Charles Cloud for the complete PR. @@ -8642,7 +8642,7 @@ time or change existing behaviors in order to make them less surprising/more use - cleanup setup.py a bit and specify supported versions. Thanks Jurko Gospodnetic for the PR. -- change XPASS colour to yellow rather then red when tests are run +- change XPASS colour to yellow rather than red when tests are run with -v. - fix issue473: work around mock putting an unbound method into a class @@ -8815,7 +8815,7 @@ time or change existing behaviors in order to make them less surprising/more use Thanks Ralph Schmitt for the precise failure example. - fix issue244 by implementing special index for parameters to only use - indices for paramentrized test ids + indices for parametrized test ids - fix issue287 by running all finalizers but saving the exception from the first failing finalizer and re-raising it so teardown will @@ -8823,7 +8823,7 @@ time or change existing behaviors in order to make them less surprising/more use it might be the cause for other finalizers to fail. - fix ordering when mock.patch or other standard decorator-wrappings - are used with test methods. This fixues issue346 and should + are used with test methods. This fixes issue346 and should help with random "xdist" collection failures. Thanks to Ronny Pfannschmidt and Donald Stufft for helping to isolate it. @@ -9080,7 +9080,7 @@ Bug fixes: partially failed (finalizers would not always be called before) - fix issue320 - fix class scope for fixtures when mixed with - module-level functions. Thanks Anatloy Bubenkoff. + module-level functions. Thanks Anatoly Bubenkoff. - you can specify "-q" or "-qq" to get different levels of "quieter" reporting (thanks Katarzyna Jachim) @@ -9502,7 +9502,7 @@ Bug fixes: unexpected exceptions - fix issue47: timing output in junitxml for test cases is now correct - fix issue48: typo in MarkInfo repr leading to exception -- fix issue49: avoid confusing error when initizaliation partially fails +- fix issue49: avoid confusing error when initialization partially fails - fix issue44: env/username expansion for junitxml file path - show releaselevel information in test runs for pypy - reworked doc pages for better navigation and PDF generation @@ -9627,7 +9627,7 @@ Bug fixes: collection-before-running semantics were not setup as with pytest 1.3.4. Note, however, that the recommended and much cleaner way to do test - parametraization remains the "pytest_generate_tests" + parameterization remains the "pytest_generate_tests" mechanism, see the docs. 2.0.0 (2010-11-25) diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index 5ac93f15144..a65ea331663 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -462,7 +462,7 @@ Now :class:`~pytest.Class` collects the test methods directly. Most plugins which reference ``Instance`` do so in order to ignore or skip it, using a check such as ``if isinstance(node, Instance): return``. Such plugins should simply remove consideration of ``Instance`` on pytest>=7. -However, to keep such uses working, a dummy type has been instanted in ``pytest.Instance`` and ``_pytest.python.Instance``, +However, to keep such uses working, a dummy type has been instanced in ``pytest.Instance`` and ``_pytest.python.Instance``, and importing it emits a deprecation warning. This was removed in pytest 8. diff --git a/doc/en/example/customdirectory/conftest.py b/doc/en/example/customdirectory/conftest.py index 350893cab43..b2f68dba41a 100644 --- a/doc/en/example/customdirectory/conftest.py +++ b/doc/en/example/customdirectory/conftest.py @@ -21,7 +21,7 @@ def collect(self): @pytest.hookimpl def pytest_collect_directory(path, parent): - # Use our custom collector for directories containing a `mainfest.json` file. + # Use our custom collector for directories containing a `manifest.json` file. if path.joinpath("manifest.json").is_file(): return ManifestDirectory.from_parent(parent=parent, path=path) # Otherwise fallback to the standard behavior. diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 5b33e308d13..468de3654d9 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -274,7 +274,7 @@ Continue reading Check out additional pytest resources to help you customize tests for your unique workflow: * ":ref:`usage`" for command line invocation examples -* ":ref:`existingtestsuite`" for working with pre-existing tests +* ":ref:`existingtestsuite`" for working with preexisting tests * ":ref:`mark`" for information on the ``pytest.mark`` mechanism * ":ref:`fixtures`" for providing a functional baseline to your tests * ":ref:`plugins`" for managing and writing plugins diff --git a/doc/en/naming20.rst b/doc/en/naming20.rst index 5a81df2698d..11213066384 100644 --- a/doc/en/naming20.rst +++ b/doc/en/naming20.rst @@ -8,7 +8,7 @@ If you used older version of the ``py`` distribution (which included the py.test command line tool and Python name space) you accessed helpers and possibly collection classes through the ``py.test`` Python namespaces. The new ``pytest`` -Python module flaty provides the same objects, following +Python module flatly provides the same objects, following these renaming rules:: py.test.XYZ -> pytest.XYZ diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst index 8ba59395e3e..dff93a035ef 100644 --- a/doc/en/reference/fixtures.rst +++ b/doc/en/reference/fixtures.rst @@ -39,7 +39,7 @@ Built-in fixtures Store and retrieve values across pytest runs. :fixture:`doctest_namespace` - Provide a dict injected into the docstests namespace. + Provide a dict injected into the doctests namespace. :fixture:`monkeypatch` Temporarily modify classes, functions, dictionaries, diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index e46478da02b..4036b7d9912 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1940,7 +1940,7 @@ All the command-line flags can be obtained by running ``pytest --help``:: general: -k EXPRESSION Only run tests which match the given substring - expression. An expression is a Python evaluatable + expression. An expression is a Python evaluable expression where all names are substring-matched against test names and their parent classes. Example: -k 'test_method or test_other' matches all diff --git a/src/_pytest/_py/error.py b/src/_pytest/_py/error.py index 68f1eed7ec0..ab3a4ed318e 100644 --- a/src/_pytest/_py/error.py +++ b/src/_pytest/_py/error.py @@ -41,7 +41,7 @@ def __str__(self) -> str: 3: errno.ENOENT, 17: errno.EEXIST, 18: errno.EXDEV, - 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailiable + 13: errno.EBUSY, # empty cd drive, but ENOMEDIUM seems unavailable 22: errno.ENOTDIR, 20: errno.ENOTDIR, 267: errno.ENOTDIR, diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py index 27b115cfd87..bcb05ac34cd 100644 --- a/src/_pytest/_py/path.py +++ b/src/_pytest/_py/path.py @@ -836,7 +836,7 @@ def mtime(self) -> float: 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 mode is True, will copy permission from path to target. If stat is True, copy permission, last modification time, last access time, and flags from path to target. """ @@ -1047,7 +1047,7 @@ def chmod(self, mode, rec=0): 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. + Return None if a pkgpath cannot be determined. """ pkgpath = None for parent in self.parts(reverse=True): diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 3d5df0d6c34..928c4f5e3b2 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -584,7 +584,7 @@ def _write_and_reset() -> None: # multi-line assert with message elif lineno in seen_lines: lines[-1] = lines[-1][:offset] - # multi line assert with escapd newline before message + # multi line assert with escaped newline before message else: lines.append(line[:offset]) _write_and_reset() diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index 008eabf9a1f..a118befcc16 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -325,7 +325,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: def _compare_eq_iterable( left: Iterable[Any], right: Iterable[Any], - highligher: _HighlightFunc, + highlighter: _HighlightFunc, verbose: int = 0, ) -> List[str]: if verbose <= 0 and not running_on_ci(): @@ -340,7 +340,7 @@ def _compare_eq_iterable( # "right" is the expected base against which we compare "left", # see https://github.com/pytest-dev/pytest/issues/3333 explanation.extend( - highligher( + highlighter( "\n".join( line.rstrip() for line in difflib.ndiff(right_formatting, left_formatting) diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 06def557e41..5aa8f483522 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -251,7 +251,7 @@ def pytest_make_collect_report( # Sort any lf-paths to the beginning. lf_paths = self.lfplugin._last_failed_paths - # Use stable sort to priorize last failed. + # Use stable sort to prioritize last failed. def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: return node.path in lf_paths diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py index 9d9411818ac..614848e0dba 100644 --- a/src/_pytest/compat.py +++ b/src/_pytest/compat.py @@ -53,7 +53,7 @@ def iscoroutinefunction(func: object) -> bool: def syntax, and doesn't contain yield), or a function decorated with @asyncio.coroutine. - Note: copied and modified from Python 3.5's builtin couroutines.py to avoid + Note: copied and modified from Python 3.5's builtin coroutines.py to avoid importing asyncio directly, which in turns also initializes the "logging" module as a side-effect (see issue #8). """ diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c8e8e7d29dd..c698ea9a777 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -462,7 +462,7 @@ def parse_hookimpl_opts( # (see issue #1073). if not name.startswith("pytest_"): return None - # Ignore names which can not be hooks. + # Ignore names which cannot be hooks. if name == "pytest_plugins": return None @@ -574,8 +574,8 @@ def _set_initial_conftests( self._noconftest = noconftest self._using_pyargs = pyargs foundanchor = False - for intitial_path in args: - path = str(intitial_path) + for initial_path in args: + path = str(initial_path) # remove node-id syntax i = path.find("::") if i != -1: diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 5a290718fe4..3ce8b3fb4c0 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -326,7 +326,7 @@ def prune_dependency_tree(self) -> None: working_set = set(self.initialnames) while working_set: argname = working_set.pop() - # Argname may be smth not included in the original names_closure, + # Argname may be something not included in the original names_closure, # in which case we ignore it. This currently happens with pseudo # FixtureDefs which wrap 'get_direct_param_fixture_func(request)'. # So they introduce the new dependency 'request' which might have @@ -1702,7 +1702,7 @@ def parsefactories( 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 + determine the fixtures' visibility. `nodeid` must not be specified in this case. If `node_or_object` is an object (e.g. a plugin), the object is diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 01d3b664080..8a38ff4fb43 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -651,7 +651,7 @@ def pytest_runtest_protocol( - ``pytest_runtest_logreport(report)`` - ``pytest_exception_interact(call, report)`` if an interactive exception occurred - - Call phase, if the the setup passed and the ``setuponly`` pytest option is not set: + - Call phase, if the setup passed and the ``setuponly`` pytest option is not set: - ``call = pytest_runtest_call(item)`` (wrapped in ``CallInfo(when="call")``) - ``report = pytest_runtest_makereport(item, call)`` - ``pytest_runtest_logreport(report)`` @@ -861,7 +861,7 @@ def pytest_fixture_setup( ) -> Optional[object]: """Perform fixture setup execution. - :param fixturdef: + :param fixturedef: The fixture definition object. :param request: The fixture request object. @@ -891,7 +891,7 @@ def pytest_fixture_post_finalizer( the fixture result ``fixturedef.cached_result`` is still available (not ``None``). - :param fixturdef: + :param fixturedef: The fixture definition object. :param request: The fixture request object. diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index b28c89767fe..d9de65b1a53 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -384,7 +384,7 @@ def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: return legacy_path(str(self.inipath)) if self.inipath else None -def Session_stardir(self: Session) -> LEGACY_PATH: +def Session_startdir(self: Session) -> LEGACY_PATH: """The path from which pytest was invoked. Prefer to use ``startpath`` which is a :class:`pathlib.Path`. @@ -439,7 +439,7 @@ def pytest_load_initial_conftests(early_config: Config) -> None: mp.setattr(Config, "inifile", property(Config_inifile), raising=False) # Add Session.startdir property. - mp.setattr(Session, "startdir", property(Session_stardir), raising=False) + mp.setattr(Session, "startdir", property(Session_startdir), raising=False) # Add pathlist configuration type. mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index f5276d8fbda..950a6959ec9 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -78,7 +78,7 @@ def pytest_addoption(parser: Parser) -> None: default="", metavar="EXPRESSION", help="Only run tests which match the given substring expression. " - "An expression is a Python evaluatable expression " + "An expression is a Python evaluable expression " "where all names are substring-matched against test names " "and their parent classes. Example: -k 'test_method or test_" "other' matches all test functions and classes whose name " diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index e14c2acd328..b11eea4e7ef 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -173,7 +173,7 @@ def rm_rf(path: Path) -> None: def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]: - """Find all elements in root that begin with the prefix, case insensitive.""" + """Find all elements in root that begin with the prefix, case-insensitive.""" l_prefix = prefix.lower() for x in os.scandir(root): if x.name.lower().startswith(l_prefix): @@ -776,7 +776,7 @@ def resolve_package_path(path: Path) -> Optional[Path]: """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. - Returns None if it can not be determined. + Returns None if it cannot be determined. """ result = None for parent in itertools.chain((path,), path.parents): diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 3ebebc288f8..92d3a297e0d 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -40,7 +40,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl def pytest_configure(config: Config) -> None: if config.option.stepwise_skip: - # allow --stepwise-skip to work on it's own merits. + # allow --stepwise-skip to work on its own merits. config.option.stepwise = True if config.getoption("stepwise"): config.pluginmanager.register(StepwisePlugin(config), "stepwiseplugin") diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 243219babc9..ea5794b251b 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -667,7 +667,7 @@ def test_tilde_expansion(self, monkeypatch, tmpdir): assert p == os.path.expanduser("~") @pytest.mark.skipif( - not sys.platform.startswith("win32"), reason="case insensitive only on windows" + 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") @@ -898,7 +898,7 @@ def test_sysfind_bat_exe_before(self, tmpdir, monkeypatch): class TestExecution: pytestmark = skiponwin32 - def test_sysfind_no_permisson_ignored(self, monkeypatch, tmpdir): + def test_sysfind_no_permission_ignored(self, monkeypatch, tmpdir): noperm = tmpdir.ensure("noperm", dir=True) monkeypatch.setenv("PATH", str(noperm), prepend=":") noperm.chmod(0) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index f2c689a137e..86e30dc4830 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1515,7 +1515,7 @@ def test(tmp_path): result.stderr.no_fnmatch_line("*INTERNALERROR*") -def test_regression_nagative_line_index(pytester: Pytester) -> None: +def test_regression_negative_line_index(pytester: Pytester) -> None: """ With Python 3.10 alphas, there was an INTERNALERROR reported in https://github.com/pytest-dev/pytest/pull/8227 diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 12ea27b3517..2fa85205795 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -255,7 +255,7 @@ def g(): assert str(g_source).strip() == "def g():\n pass # pragma: no cover" -def test_getfuncsource_with_multine_string() -> None: +def test_getfuncsource_with_multiline_string() -> None: def f(): c = """while True: pass diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index 2e16913f099..c1cfff632af 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -117,7 +117,7 @@ def test2(caplog): result.stdout.no_fnmatch_line("*log from test2*") -def test_change_level_undos_handler_level(pytester: Pytester) -> None: +def test_change_level_undoes_handler_level(pytester: Pytester) -> None: """Ensure that 'set_level' is undone after the end of the test (handler). Issue #7569. Tests the handler level specifically. @@ -302,7 +302,15 @@ def logging_during_setup_and_teardown( assert [x.message for x in caplog.get_records("teardown")] == ["a_teardown_log"] -def test_caplog_captures_for_all_stages( +def private_assert_caplog_records_is_setup_call( + caplog: pytest.LogCaptureFixture, +) -> None: + # This reaches into private API, don't use this type of thing in real tests! + caplog_records = caplog._item.stash[caplog_records_key] + assert set(caplog_records) == {"setup", "call"} + + +def test_captures_for_all_stages( caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None ) -> None: assert not caplog.records @@ -312,9 +320,7 @@ def test_caplog_captures_for_all_stages( assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] - # This reaches into private API, don't use this type of thing in real tests! - caplog_records = caplog._item.stash[caplog_records_key] - assert set(caplog_records) == {"setup", "call"} + private_assert_caplog_records_is_setup_call(caplog) def test_clear_for_call_stage( @@ -323,21 +329,18 @@ def test_clear_for_call_stage( logger.info("a_call_log") assert [x.message for x in caplog.get_records("call")] == ["a_call_log"] assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] - caplog_records = caplog._item.stash[caplog_records_key] - assert set(caplog_records) == {"setup", "call"} + private_assert_caplog_records_is_setup_call(caplog) caplog.clear() assert caplog.get_records("call") == [] assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] - caplog_records = caplog._item.stash[caplog_records_key] - assert set(caplog_records) == {"setup", "call"} + private_assert_caplog_records_is_setup_call(caplog) logging.info("a_call_log_after_clear") assert [x.message for x in caplog.get_records("call")] == ["a_call_log_after_clear"] assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"] - caplog_records = caplog._item.stash[caplog_records_key] - assert set(caplog_records) == {"setup", "call"} + private_assert_caplog_records_is_setup_call(caplog) def test_ini_controls_global_log_level(pytester: Pytester) -> None: @@ -363,11 +366,11 @@ def test_log_level_override(request, caplog): ) result = pytester.runpytest() - # make sure that that we get a '0' exit code for the testsuite + # make sure that we get a '0' exit code for the testsuite assert result.ret == 0 -def test_caplog_can_override_global_log_level(pytester: Pytester) -> None: +def test_can_override_global_log_level(pytester: Pytester) -> None: pytester.makepyfile( """ import pytest @@ -406,7 +409,7 @@ def test_log_level_override(request, caplog): assert result.ret == 0 -def test_caplog_captures_despite_exception(pytester: Pytester) -> None: +def test_captures_despite_exception(pytester: Pytester) -> None: pytester.makepyfile( """ import pytest diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index aec0deb99a1..15d7c33b215 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -933,7 +933,7 @@ def test_request_subrequest_addfinalizer_exceptions( ) -> None: """ Ensure exceptions raised during teardown by finalizers are suppressed - until all finalizers are called, then re-reaised together in an + until all finalizers are called, then re-raised together in an exception group (#2440) """ pytester.makepyfile( diff --git a/testing/python/integration.py b/testing/python/integration.py index 219ebf9cec8..c20aaeed839 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -163,7 +163,7 @@ def mock_basename(path): @mock.patch("os.path.abspath") @mock.patch("os.path.normpath") @mock.patch("os.path.basename", new=mock_basename) - def test_someting(normpath, abspath, tmp_path): + def test_something(normpath, abspath, tmp_path): abspath.return_value = "this" os.path.normpath(os.path.abspath("hello")) normpath.assert_any_call("this") @@ -176,7 +176,7 @@ def test_someting(normpath, abspath, tmp_path): funcnames = [ call.report.location[2] for call in calls if call.report.when == "call" ] - assert funcnames == ["T.test_hello", "test_someting"] + assert funcnames == ["T.test_hello", "test_something"] def test_mock_sorting(self, pytester: Pytester) -> None: pytest.importorskip("mock", "1.0.1") diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index c85c1d04cc7..d7815f77b9a 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -58,7 +58,7 @@ def test_config_cache_dataerror(self, pytester: Pytester) -> None: assert val == -2 @pytest.mark.filterwarnings("ignore:could not create cache path") - def test_cache_writefail_cachfile_silent(self, pytester: Pytester) -> None: + def test_cache_writefail_cachefile_silent(self, pytester: Pytester) -> None: pytester.makeini("[pytest]") pytester.path.joinpath(".pytest_cache").write_text( "gone wrong", encoding="utf-8" diff --git a/testing/test_compat.py b/testing/test_compat.py index c898af7c531..73ac1bad858 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -94,7 +94,7 @@ def foo(x): assert get_real_func(partial(foo)) is foo -@pytest.mark.skipif(sys.version_info >= (3, 11), reason="couroutine removed") +@pytest.mark.skipif(sys.version_info >= (3, 11), reason="coroutine removed") def test_is_generator_asyncio(pytester: Pytester) -> None: pytester.makepyfile( """ diff --git a/testing/test_config.py b/testing/test_config.py index 776c8424a1a..1cb31fed06d 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -216,7 +216,7 @@ def test_toml_parse_error(self, pytester: Pytester) -> None: def test_confcutdir_default_without_configfile(self, pytester: Pytester) -> None: # If --confcutdir is not specified, and there is no configfile, default - # to the roothpath. + # to the rootpath. sub = pytester.mkdir("sub") os.chdir(sub) config = pytester.parseconfigure() @@ -1738,7 +1738,7 @@ def pytest_addoption(parser): ) pytester.makepyfile( r""" - def test_overriden(pytestconfig): + def test_overridden(pytestconfig): config_paths = pytestconfig.getini("paths") print(config_paths) for cpf in config_paths: diff --git a/testing/test_conftest.py b/testing/test_conftest.py index a3ebcbb7862..eb3ebecdc98 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -70,7 +70,7 @@ def test_basic_init(self, basedir: Path) -> None: ) assert conftest._rget_with_confmod("a", p)[1] == 1 - def test_immediate_initialiation_and_incremental_are_the_same( + def test_immediate_initialization_and_incremental_are_the_same( self, basedir: Path ) -> None: conftest = PytestPluginManager() @@ -396,7 +396,7 @@ def fixture(): @pytest.mark.skipif( os.path.normcase("x") != os.path.normcase("X"), - reason="only relevant for case insensitive file systems", + reason="only relevant for case-insensitive file systems", ) def test_conftest_badcase(pytester: Pytester) -> None: """Check conftest.py loading when directory casing is wrong (#5792).""" diff --git a/testing/test_debugging.py b/testing/test_debugging.py index a99336c75a6..1f3422947de 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -1122,7 +1122,7 @@ def test_func_kw(myparam, request, func="func_kw"): def test_trace_after_runpytest(pytester: Pytester) -> None: - """Test that debugging's pytest_configure is re-entrant.""" + """Test that debugging's pytest_configure is reentrant.""" p1 = pytester.makepyfile( """ from _pytest.debugging import pytestPDB @@ -1153,7 +1153,7 @@ def test_inner(): def test_quit_with_swallowed_SystemExit(pytester: Pytester) -> None: - """Test that debugging's pytest_configure is re-entrant.""" + """Test that debugging's pytest_configure is reentrant.""" p1 = pytester.makepyfile( """ def call_pdb_set_trace(): diff --git a/testing/test_doctest.py b/testing/test_doctest.py index de5d1353163..d731121795d 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -396,7 +396,7 @@ def some_property(self): ] ) - def test_doctest_no_linedata_on_overriden_property(self, pytester: Pytester): + def test_doctest_no_linedata_on_overridden_property(self, pytester: Pytester): pytester.makepyfile( """ class Sample(object): @@ -414,7 +414,7 @@ def some_property(self): result.stdout.fnmatch_lines( [ "*= FAILURES =*", - "*_ [[]doctest[]] test_doctest_no_linedata_on_overriden_property.Sample.some_property _*", + "*_ [[]doctest[]] test_doctest_no_linedata_on_overridden_property.Sample.some_property _*", "EXAMPLE LOCATION UNKNOWN, not showing all tests of that example", "[?][?][?] >>> Sample().some_property", "Expected:", @@ -422,7 +422,7 @@ def some_property(self): "Got:", " 'something'", "", - "*/test_doctest_no_linedata_on_overriden_property.py:None: DocTestFailure", + "*/test_doctest_no_linedata_on_overridden_property.py:None: DocTestFailure", "*= 1 failed in *", ] ) diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index f8742dc2e3c..1ebc3ed3a51 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1001,7 +1001,7 @@ def repr_failure(self, excinfo): @pytest.mark.parametrize("junit_logging", ["no", "system-out"]) def test_nullbyte(pytester: Pytester, junit_logging: str) -> None: - # A null byte can not occur in XML (see section 2.2 of the spec) + # A null byte cannot occur in XML (see section 2.2 of the spec) pytester.makepyfile( """ import sys diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index f7f45dbf02e..d2b33b4fb8b 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -155,7 +155,7 @@ def pytest_addoption(parser): ) pytester.makepyfile( r""" - def test_overriden(pytestconfig): + def test_overridden(pytestconfig): config_paths = pytestconfig.getini("paths") print(config_paths) for cpf in config_paths: diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index a7a9cf3044a..07c89f90838 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -61,7 +61,7 @@ def test_basic(expr: str, expected: bool) -> None: ("not not not not not true", False), ), ) -def test_syntax_oddeties(expr: str, expected: bool) -> None: +def test_syntax_oddities(expr: str, expected: bool) -> None: matcher = {"true": True, "false": False}.__getitem__ assert evaluate(expr, matcher) is expected diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index da43364f643..99b003b66ed 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -420,7 +420,7 @@ def test_consider_conftest_deps( pytestpm.consider_conftest(mod, registration_name="unused") -class TestPytestPluginManagerBootstrapming: +class TestPytestPluginManagerBootstrapping: def test_preparse_args(self, pytestpm: PytestPluginManager) -> None: pytest.raises( ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) @@ -446,7 +446,7 @@ def test_plugin_prevent_register(self, pytestpm: PytestPluginManager) -> None: assert len(l2) == len(l1) assert 42 not in l2 - def test_plugin_prevent_register_unregistered_alredy_registered( + def test_plugin_prevent_register_unregistered_already_registered( self, pytestpm: PytestPluginManager ) -> None: pytestpm.register(42, name="abc") diff --git a/testing/test_runner.py b/testing/test_runner.py index 436ce2f1062..3ec5678274e 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -446,7 +446,7 @@ def test_func(): # assert rep.outcome.when == "setup" # assert rep.outcome.where.lineno == 3 # assert rep.outcome.where.path.basename == "test_func.py" - # assert instanace(rep.failed.failurerepr, PythonFailureRepr) + # assert isinstance(rep.failed.failurerepr, PythonFailureRepr) def test_systemexit_does_not_bail_out(self, pytester: Pytester) -> None: try: diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 19848e7cbaf..6cd4a1827d2 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -926,7 +926,7 @@ def test_header(self, pytester: Pytester) -> None: def test_header_absolute_testpath( self, pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: - """Regresstion test for #7814.""" + """Regression test for #7814.""" tests = pytester.path.joinpath("tests") tests.mkdir() pytester.makepyprojecttoml( diff --git a/testing/test_unittest.py b/testing/test_unittest.py index f73e083be78..003a74d3849 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -299,7 +299,7 @@ def test_func2(self): @classmethod def tearDownClass(cls): cls.x -= 1 - def test_teareddown(): + def test_torn_down(): assert MyTestCase.x == 0 """ ) @@ -346,7 +346,7 @@ def test_func2(self): assert self.x == 1 def teardown_class(cls): cls.x -= 1 - def test_teareddown(): + def test_torn_down(): assert MyTestCase.x == 0 """ ) @@ -881,7 +881,7 @@ def test_method1(self): def tearDownClass(cls): cls.x = 1 - def test_not_teareddown(): + def test_not_torn_down(): assert TestFoo.x == 0 """ From 635fbe2bff805c86c0df1db463f4c5eea087aec6 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 17 May 2024 08:19:13 -0300 Subject: [PATCH 036/199] Attest package provenance (#12333) Use the new build provenance support added in [build-and-inspect-python-package 2.5.0](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md#250---2024-05-13). More information: https://github.blog/2024-05-02-introducing-artifact-attestations-now-in-public-beta/ Tested also in https://github.com/pytest-dev/pytest-mock/pull/431. Note: even though it is technically necessary only for the `deploy` workflow, as the `test` workflow does not publish its packages, decided to always attest the provenance in both cases to avoid any surprises related to this (say a misconfiguration) when deploying. --- .github/workflows/deploy.yml | 9 ++++++++- .github/workflows/test.yml | 2 +- changelog/12333.trivial.rst | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 changelog/12333.trivial.rst diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cc0e6331d45..20a72270fde 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,6 +19,11 @@ jobs: SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} timeout-minutes: 10 + # Required by attest-build-provenance-github. + permissions: + id-token: write + attestations: write + steps: - uses: actions/checkout@v4 with: @@ -26,7 +31,9 @@ jobs: persist-credentials: false - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.4.0 + uses: hynek/build-and-inspect-python-package@v2.5.0 + with: + attest-build-provenance-github: 'true' deploy: if: github.repository == 'pytest-dev/pytest' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4434740675e..df801864fd8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: fetch-depth: 0 persist-credentials: false - name: Build and Check Package - uses: hynek/build-and-inspect-python-package@v2.4.0 + uses: hynek/build-and-inspect-python-package@v2.5.0 build: needs: [package] diff --git a/changelog/12333.trivial.rst b/changelog/12333.trivial.rst new file mode 100644 index 00000000000..32c4c5771a7 --- /dev/null +++ b/changelog/12333.trivial.rst @@ -0,0 +1 @@ +pytest releases are now attested using the recent `Artifact Attestation ` support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts. From ee9ea703f98df811738e7ea58bffe6c1bc660371 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 18 May 2024 12:07:06 -0400 Subject: [PATCH 037/199] Fix new typing issues in AST code (#12337) python/typeshed#11880 adds more precise types for AST nodes. I'm submitting some changes to adapt pytest to these changes. --- src/_pytest/assertion/rewrite.py | 12 ++++++------ testing/test_assertrewrite.py | 1 + 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 928c4f5e3b2..b29a254f5f7 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -835,7 +835,7 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys = [ast.Constant(key) for key in current.keys()] + keys: List[Optional[ast.expr]] = [ast.Constant(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)) @@ -926,13 +926,13 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: [*self.expl_stmts, hook_call_pass], [], ) - statements_pass = [hook_impl_test] + statements_pass: List[ast.stmt] = [hook_impl_test] # Test for assertion condition main_test = ast.If(negation, statements_fail, statements_pass) self.statements.append(main_test) if self.format_variables: - variables = [ + variables: List[ast.expr] = [ ast.Name(name, ast.Store()) for name in self.format_variables ] clear_format = ast.Assign(variables, ast.Constant(None)) @@ -1114,11 +1114,11 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" res_variables = [self.variable() for i in range(len(comp.ops))] - load_names = [ast.Name(v, ast.Load()) for v in res_variables] + load_names: List[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] store_names = [ast.Name(v, ast.Store()) for v in res_variables] it = zip(range(len(comp.ops)), comp.ops, comp.comparators) - expls = [] - syms = [] + expls: List[ast.expr] = [] + syms: List[ast.expr] = [] results = [left_res] for i, op, next_operand in it: if ( diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 82c7055b968..8db9dbbe5ff 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -130,6 +130,7 @@ def test_location_is_set(self) -> None: if isinstance(node, ast.Import): continue for n in [node, *ast.iter_child_nodes(node)]: + assert isinstance(n, (ast.stmt, ast.expr)) assert n.lineno == 3 assert n.col_offset == 0 assert n.end_lineno == 6 From dbee3fa34a9fb3712742f28ba15676274e68121d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 17 May 2024 11:22:53 +0300 Subject: [PATCH 038/199] testing: remove conditionals for Python 3.11 beta releases No need to support beta releases of an older version anymore. Ref: 09b2c9532090db84daa3aa1a243f90dc8709fc00 --- testing/test_doctest.py | 6 ------ testing/test_main.py | 17 +---------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index d731121795d..c6f156b0e2d 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,7 +1,6 @@ # mypy: allow-untyped-defs import inspect from pathlib import Path -import sys import textwrap from typing import Callable from typing import Optional @@ -224,11 +223,6 @@ def test_doctest_unexpected_exception(self, pytester: Pytester): "Traceback (most recent call last):", ' File "*/doctest.py", line *, in __run', " *", - *( - (" *^^^^*",) - if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) - else () - ), ' File "", line 1, in ', "ZeroDivisionError: division by zero", "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", diff --git a/testing/test_main.py b/testing/test_main.py index 345aa1e62cf..6294f66b360 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -3,7 +3,6 @@ import os from pathlib import Path import re -import sys from typing import Optional from _pytest.config import ExitCode @@ -45,32 +44,18 @@ def pytest_internalerror(excrepr, excinfo): assert result.ret == ExitCode.INTERNAL_ERROR assert result.stdout.lines[0] == "INTERNALERROR> Traceback (most recent call last):" - end_lines = ( - result.stdout.lines[-4:] - if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) - else result.stdout.lines[-3:] - ) + end_lines = result.stdout.lines[-3:] if exc == SystemExit: assert end_lines == [ f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart', 'INTERNALERROR> raise SystemExit("boom")', - *( - ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) - if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) - else () - ), "INTERNALERROR> SystemExit: boom", ] else: assert end_lines == [ f'INTERNALERROR> File "{c1}", line 4, in pytest_sessionstart', 'INTERNALERROR> raise ValueError("boom")', - *( - ("INTERNALERROR> ^^^^^^^^^^^^^^^^^^^^^^^^",) - if (3, 11, 0, "beta", 4) > sys.version_info >= (3, 11) - else () - ), "INTERNALERROR> ValueError: boom", ] if returncode is False: From cb732bbfb0a2dd8d7acbc703ce485bd2f631cf92 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 19 May 2024 00:21:06 +0000 Subject: [PATCH 039/199] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 148 +++++++++++++++++++------------ 1 file changed, 90 insertions(+), 58 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index d03861ccfd1..43c4748ea03 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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-id2history` Overwrite allure history id with testcase full name and testcase id if testcase has id, exclude parameters. May 14, 2024 4 - Beta pytest>=6.2.0 :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) @@ -126,7 +127,7 @@ This list contains 1457 plugins. :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-apigateway` pytest plugin for AWS ApiGateway May 10, 2024 4 - Beta pytest + :pypi:`pytest-aws-apigateway` pytest plugin for AWS ApiGateway May 18, 2024 4 - Beta pytest :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A :pypi:`pytest-aws-fixtures` A series of fixtures to use in integration tests involving actual AWS services. Feb 02, 2024 N/A pytest (>=8.0.0,<9.0.0) :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) @@ -147,7 +148,7 @@ This list contains 1457 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 08, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 17, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -232,6 +233,7 @@ This list contains 1457 plugins. :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-cleanslate` Collects and executes pytest tests separately May 15, 2024 N/A pytest :pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A :pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -241,7 +243,7 @@ This list contains 1457 plugins. :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 May 06, 2024 N/A pytest<9,>=4 + :pypi:`pytest-cmake` Provide CMake module for Pytest May 12, 2024 N/A pytest<9,>=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_codeblocks` Test code blocks in your READMEs Sep 17, 2023 5 - Production/Stable pytest >= 7.0.0 @@ -357,7 +359,7 @@ This list contains 1457 plugins. :pypi:`pytest-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Dec 11, 2023 4 - Beta pytest>=7.3.2 :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. Oct 18, 2023 4 - Beta pytest !=6.0.0,<8,>=3.3.2 + :pypi:`pytest-discord` A pytest plugin to notify test results to a Discord channel. May 11, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 :pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest :pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. May 07, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.0 @@ -553,7 +555,7 @@ This list contains 1457 plugins. :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 Jun 26, 2023 4 - Beta pytest (>=7.0.0) + :pypi:`pytest-fluent` A pytest plugin in order to provide logs via fluentd May 15, 2024 4 - Beta pytest>=7.0.0 :pypi:`pytest-fluentbit` A pytest plugin in order to provide logs via fluentbit Jun 16, 2023 4 - Beta pytest (>=7.0.0) :pypi:`pytest-fly` pytest observer Apr 14, 2024 3 - Alpha pytest :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest @@ -621,13 +623,13 @@ This list contains 1457 plugins. :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Dec 29, 2021 5 - Production/Stable pytest (>=6.0.0) :pypi:`pytest-henry` Aug 29, 2023 N/A N/A :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) - :pypi:`pytest-himark` This plugin aims to create markers automatically based on a json configuration. May 10, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-himark` This plugin aims to create markers automatically based on a json configuration. May 15, 2024 4 - Beta pytest>=6.2.0 :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-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 May 11, 2024 3 - Alpha pytest==8.1.1 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components May 18, 2024 3 - Alpha pytest==8.1.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` Apr 18, 2024 N/A N/A @@ -708,7 +710,7 @@ This list contains 1457 plugins. :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 Apr 30, 2024 3 - Alpha N/A - :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jun 19, 2023 N/A pytest (>=7.2.0) + :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked May 16, 2024 N/A pytest>=7.2.0 :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Mar 27, 2024 4 - Beta pytest>=6.2.4 :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 @@ -732,7 +734,7 @@ This list contains 1457 plugins. :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-kookit` Your simple but kooky integration testing with pytest May 06, 2024 N/A N/A + :pypi:`pytest-kookit` Your simple but kooky integration testing with pytest May 16, 2024 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` Sep 14, 2023 N/A pytest (>=7.2.1,<8.0.0) @@ -764,7 +766,7 @@ This list contains 1457 plugins. :pypi:`pytest-litter` Pytest plugin which verifies that tests do not modify file trees. Nov 23, 2023 4 - Beta pytest >=6.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 14, 2023 5 - Production/Stable pytest + :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests May 17, 2024 5 - Production/Stable pytest :pypi:`pytest-localserver` pytest plugin to test server connections locally. Oct 12, 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-lock` pytest-lock is a pytest plugin that allows you to "lock" the results of unit tests, storing them in a local cache. This is particularly useful for tests that are resource-intensive or don't need to be run every time. When the tests are run subsequently, pytest-lock will compare the current results with the locked results and issue a warning if there are any discrepancies. Feb 03, 2024 N/A pytest (>=7.4.3,<8.0.0) @@ -800,7 +802,7 @@ This list contains 1457 plugins. :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 generate test outcomes reports with markdown table format. May 03, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 + :pypi:`pytest-md-report` A pytest plugin to generate test outcomes reports with markdown table format. May 18, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 :pypi:`pytest-meilisearch` Pytest helpers for testing projects using Meilisearch Feb 15, 2024 N/A pytest (>=7.4.3) :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 @@ -875,7 +877,7 @@ This list contains 1457 plugins. :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 Feb 16, 2024 N/A pytest (>=6.2.5,<7.0.0) + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies May 17, 2024 N/A pytest<9.0.0,>=8.2.0 :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 Apr 11, 2024 N/A N/A @@ -975,7 +977,7 @@ This list contains 1457 plugins. :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-plone` Pytest plugin to test Plone addons May 15, 2024 3 - Alpha pytest<8.0.0 :pypi:`pytest-plt` Fixtures for quickly making Matplotlib plots in tests Jan 17, 2024 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 26, 2024 5 - Production/Stable pytest>=7.4.2 @@ -1013,6 +1015,7 @@ This list contains 1457 plugins. :pypi:`pytest-prysk` Pytest plugin for prysk Mar 12, 2024 4 - Beta pytest (>=7.3.2) :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-pt` pytest plugin to use \*.pt files as tests May 15, 2024 4 - Beta pytest :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 @@ -1039,6 +1042,7 @@ This list contains 1457 plugins. :pypi:`pytest-pytestrail` Pytest plugin for interaction with TestRail Aug 27, 2020 4 - Beta pytest (>=3.8.0) :pypi:`pytest-pythonhashseed` Pytest plugin to set PYTHONHASHSEED env var. Feb 25, 2024 4 - Beta pytest>=3.0.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-python-test-engineer-sort` Sort plugin for Pytest May 13, 2024 N/A pytest>=6.2.0 :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-pyvenv` A package for create venv in tests Feb 27, 2024 N/A pytest ; extra == 'test' :pypi:`pytest-pyvista` Pytest-pyvista package Sep 29, 2023 4 - Beta pytest>=3.5.0 @@ -1066,7 +1070,7 @@ This list contains 1457 plugins. :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 Jan 20, 2024 5 - Production/Stable pytest >=3.0.0 - :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection May 10, 2024 4 - Beta pytest>=7.4.3 + :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection May 12, 2024 4 - Beta pytest>=7.4.3 :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A :pypi:`pytest-reana` Pytest fixtures for REANA. Mar 14, 2024 3 - Alpha N/A :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A @@ -1116,7 +1120,7 @@ This list contains 1457 plugins. :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Mar 13, 2024 5 - Production/Stable pytest >=7.2 :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. Feb 08, 2024 4 - Beta pytest - :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest Apr 03, 2024 N/A pytest~=4.6; python_version == "2.7" + :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest May 17, 2024 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 @@ -1128,7 +1132,7 @@ This list contains 1457 plugins. :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 Feb 04, 2024 N/A pytest >=7.0.0 + :pypi:`pytest-retry` Adds the ability to retry flaky tests in CI environments May 14, 2024 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. Jul 10, 2023 5 - Production/Stable pytest @@ -1140,7 +1144,7 @@ This list contains 1457 plugins. :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_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 03, 2024 N/A pytest<9,>=7 + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 18, 2024 N/A pytest<9,>=7 :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) @@ -1165,7 +1169,7 @@ This list contains 1457 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. May 03, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. May 16, 2024 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 Feb 16, 2024 5 - Production/Stable pytest >=3.5.0 @@ -1174,7 +1178,7 @@ This list contains 1457 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. May 03, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. May 16, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -1184,6 +1188,7 @@ This list contains 1457 plugins. :pypi:`pytest-server-fixtures` Extensible server fixures for py.test Dec 19, 2023 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 May 09, 2024 3 - Alpha pytest>=6.2 + :pypi:`pytest-service` May 11, 2024 5 - Production/Stable pytest>=6.0.0 :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 @@ -1250,7 +1255,7 @@ This list contains 1457 plugins. :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 Apr 19, 2024 N/A pytest (>5.4.0,<8) + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons May 16, 2024 N/A pytest<8,>5.4.0 :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX May 10, 2024 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 @@ -1423,7 +1428,6 @@ This list contains 1457 plugins. :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 Nov 23, 2023 4 - Beta pytest - :pypi:`pytest-ver` Pytest module with Verification Protocol, Verification Report and Trace Matrix Feb 07, 2024 4 - Beta pytest :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 @@ -1463,7 +1467,7 @@ This list contains 1457 plugins. :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-xiuyu` This is a pytest plugin Jul 25, 2023 5 - Production/Stable 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) Apr 23, 2024 N/A N/A + :pypi:`pytest-xlsx` pytest plugin for generating test cases by xlsx(excel) Apr 23, 2024 N/A pytest~=7.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. Mar 31, 2024 4 - Beta pytest>=2.8 :pypi:`pytest-xray` May 30, 2019 3 - Alpha N/A @@ -1720,6 +1724,13 @@ This list contains 1457 plugins. pytest plugin to test case doc string dls instructions + :pypi:`pytest-allure-id2history` + *last release*: May 14, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + Overwrite allure history id with testcase full name and testcase id if testcase has id, exclude parameters. + :pypi:`pytest-allure-intersection` *last release*: Oct 27, 2022, *status*: N/A, @@ -2141,7 +2152,7 @@ This list contains 1457 plugins. pytest plugin for testing AWS resource configurations :pypi:`pytest-aws-apigateway` - *last release*: May 10, 2024, + *last release*: May 18, 2024, *status*: 4 - Beta, *requires*: pytest @@ -2288,7 +2299,7 @@ This list contains 1457 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: May 08, 2024, + *last release*: May 17, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2882,6 +2893,13 @@ This list contains 1457 plugins. Easy quality control for CLDF datasets using pytest + :pypi:`pytest-cleanslate` + *last release*: May 15, 2024, + *status*: N/A, + *requires*: pytest + + Collects and executes pytest tests separately + :pypi:`pytest_cleanup` *last release*: Jan 28, 2020, *status*: N/A, @@ -2946,7 +2964,7 @@ This list contains 1457 plugins. Distribute tests to cloud machines without fuss :pypi:`pytest-cmake` - *last release*: May 06, 2024, + *last release*: May 12, 2024, *status*: N/A, *requires*: pytest<9,>=4 @@ -3758,9 +3776,9 @@ This list contains 1457 plugins. Disable plugins per test :pypi:`pytest-discord` - *last release*: Oct 18, 2023, + *last release*: May 11, 2024, *status*: 4 - Beta, - *requires*: pytest !=6.0.0,<8,>=3.3.2 + *requires*: pytest!=6.0.0,<9,>=3.3.2 A pytest plugin to notify test results to a Discord channel. @@ -5130,9 +5148,9 @@ This list contains 1457 plugins. :pypi:`pytest-fluent` - *last release*: Jun 26, 2023, + *last release*: May 15, 2024, *status*: 4 - Beta, - *requires*: pytest (>=7.0.0) + *requires*: pytest>=7.0.0 A pytest plugin in order to provide logs via fluentd @@ -5606,7 +5624,7 @@ This list contains 1457 plugins. Hide captured output :pypi:`pytest-himark` - *last release*: May 10, 2024, + *last release*: May 15, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 @@ -5648,7 +5666,7 @@ This list contains 1457 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: May 11, 2024, + *last release*: May 18, 2024, *status*: 3 - Alpha, *requires*: pytest==8.1.1 @@ -6215,9 +6233,9 @@ This list contains 1457 plugins. py.test JIRA integration plugin, using markers :pypi:`pytest-jira-xfail` - *last release*: Jun 19, 2023, + *last release*: May 16, 2024, *status*: N/A, - *requires*: pytest (>=7.2.0) + *requires*: pytest>=7.2.0 Plugin skips (xfail) tests if unresolved Jira issue(s) linked @@ -6383,7 +6401,7 @@ This list contains 1457 plugins. Run Konira DSL tests with py.test :pypi:`pytest-kookit` - *last release*: May 06, 2024, + *last release*: May 16, 2024, *status*: N/A, *requires*: N/A @@ -6607,7 +6625,7 @@ This list contains 1457 plugins. Generate local badges (shields) reporting your test suite status. :pypi:`pytest-localftpserver` - *last release*: Oct 14, 2023, + *last release*: May 17, 2024, *status*: 5 - Production/Stable, *requires*: pytest @@ -6859,7 +6877,7 @@ This list contains 1457 plugins. Plugin for generating Markdown reports for pytest results :pypi:`pytest-md-report` - *last release*: May 03, 2024, + *last release*: May 18, 2024, *status*: 4 - Beta, *requires*: pytest!=6.0.0,<9,>=3.3.2 @@ -7384,9 +7402,9 @@ This list contains 1457 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: Feb 16, 2024, + *last release*: May 17, 2024, *status*: N/A, - *requires*: pytest (>=6.2.5,<7.0.0) + *requires*: pytest<9.0.0,>=8.2.0 Pytest plugin accessing NHSDigital's APIM proxies @@ -8084,9 +8102,9 @@ This list contains 1457 plugins. A pytest fixture for visual testing with Playwright :pypi:`pytest-plone` - *last release*: Jan 05, 2023, + *last release*: May 15, 2024, *status*: 3 - Alpha, - *requires*: pytest + *requires*: pytest<8.0.0 Pytest plugin to test Plone addons @@ -8349,6 +8367,13 @@ This list contains 1457 plugins. pytest plugin for testing applications that use psqlgraph + :pypi:`pytest-pt` + *last release*: May 15, 2024, + *status*: 4 - Beta, + *requires*: pytest + + pytest plugin to use \*.pt files as tests + :pypi:`pytest-ptera` *last release*: Mar 01, 2022, *status*: N/A, @@ -8531,6 +8556,13 @@ This list contains 1457 plugins. pytest plugin for adding to the PYTHONPATH from command line or configs. + :pypi:`pytest-python-test-engineer-sort` + *last release*: May 13, 2024, + *status*: N/A, + *requires*: pytest>=6.2.0 + + Sort plugin for Pytest + :pypi:`pytest-pytorch` *last release*: May 25, 2021, *status*: 4 - Beta, @@ -8721,7 +8753,7 @@ This list contains 1457 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-ranking` - *last release*: May 10, 2024, + *last release*: May 12, 2024, *status*: 4 - Beta, *requires*: pytest>=7.4.3 @@ -9071,7 +9103,7 @@ This list contains 1457 plugins. Pytest fixture for recording and replaying serial port traffic. :pypi:`pytest-resilient-circuits` - *last release*: Apr 03, 2024, + *last release*: May 17, 2024, *status*: N/A, *requires*: pytest~=4.6; python_version == "2.7" @@ -9155,9 +9187,9 @@ This list contains 1457 plugins. A RethinkDB plugin for pytest. :pypi:`pytest-retry` - *last release*: Feb 04, 2024, + *last release*: May 14, 2024, *status*: N/A, - *requires*: pytest >=7.0.0 + *requires*: pytest>=7.0.0 Adds the ability to retry flaky tests in CI environments @@ -9239,7 +9271,7 @@ This list contains 1457 plugins. pytest plugin for ROAST configuration override and fixtures :pypi:`pytest_robotframework` - *last release*: May 03, 2024, + *last release*: May 18, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -9414,7 +9446,7 @@ This list contains 1457 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: May 03, 2024, + *last release*: May 16, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9477,7 +9509,7 @@ This list contains 1457 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: May 03, 2024, + *last release*: May 16, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9546,6 +9578,13 @@ This list contains 1457 plugins. pytest servers + :pypi:`pytest-service` + *last release*: May 11, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=6.0.0 + + + :pypi:`pytest-services` *last release*: Oct 30, 2020, *status*: 6 - Mature, @@ -10009,9 +10048,9 @@ This list contains 1457 plugins. :pypi:`pytest-splunk-addon` - *last release*: Apr 19, 2024, + *last release*: May 16, 2024, *status*: N/A, - *requires*: pytest (>5.4.0,<8) + *requires*: pytest<8,>5.4.0 A Dynamic test tool for Splunk Apps and Add-ons @@ -11219,13 +11258,6 @@ This list contains 1457 plugins. py.test fixture for creating a virtual environment - :pypi:`pytest-ver` - *last release*: Feb 07, 2024, - *status*: 4 - Beta, - *requires*: pytest - - Pytest module with Verification Protocol, Verification Report and Trace Matrix - :pypi:`pytest-verbose-parametrize` *last release*: May 28, 2019, *status*: 5 - Production/Stable, @@ -11502,7 +11534,7 @@ This list contains 1457 plugins. :pypi:`pytest-xlsx` *last release*: Apr 23, 2024, *status*: N/A, - *requires*: N/A + *requires*: pytest~=7.0 pytest plugin for generating test cases by xlsx(excel) From 1cb704ff2c6f6d4e8abd45734d05e9a3f326348b Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 17 May 2024 09:59:29 +0300 Subject: [PATCH 040/199] Add Python 3.13 (beta1) support --- .github/workflows/test.yml | 22 +++++++++++++++++++--- changelog/12334.improvement.rst | 1 + pyproject.toml | 1 + src/_pytest/_code/code.py | 7 +++---- src/_pytest/pytester.py | 6 +++++- testing/code/test_excinfo.py | 8 ++++++-- testing/code/test_source.py | 6 +++++- testing/test_cacheprovider.py | 2 +- testing/test_doctest.py | 4 +++- tox.ini | 1 + 10 files changed, 45 insertions(+), 13 deletions(-) create mode 100644 changelog/12334.improvement.rst diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index df801864fd8..09d37aaa2c8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,7 @@ jobs: "windows-py310", "windows-py311", "windows-py312", + "windows-py313", "ubuntu-py38", "ubuntu-py38-pluggy", @@ -63,12 +64,14 @@ jobs: "ubuntu-py310", "ubuntu-py311", "ubuntu-py312", + "ubuntu-py313", "ubuntu-pypy3", "macos-py38", "macos-py39", "macos-py310", "macos-py312", + "macos-py313", "doctesting", "plugins", @@ -97,9 +100,13 @@ jobs: os: windows-latest tox_env: "py311" - name: "windows-py312" - python: "3.12-dev" + python: "3.12" os: windows-latest tox_env: "py312" + - name: "windows-py313" + python: "3.13-dev" + os: windows-latest + tox_env: "py313" - name: "ubuntu-py38" python: "3.8" @@ -128,10 +135,15 @@ jobs: tox_env: "py311" use_coverage: true - name: "ubuntu-py312" - python: "3.12-dev" + python: "3.12" os: ubuntu-latest tox_env: "py312" use_coverage: true + - name: "ubuntu-py313" + python: "3.13-dev" + os: ubuntu-latest + tox_env: "py313" + use_coverage: true - name: "ubuntu-pypy3" python: "pypy-3.8" os: ubuntu-latest @@ -151,9 +163,13 @@ jobs: os: macos-latest tox_env: "py310-xdist" - name: "macos-py312" - python: "3.12-dev" + python: "3.12" os: macos-latest tox_env: "py312-xdist" + - name: "macos-py313" + python: "3.13-dev" + os: macos-latest + tox_env: "py313-xdist" - name: "plugins" python: "3.12" diff --git a/changelog/12334.improvement.rst b/changelog/12334.improvement.rst new file mode 100644 index 00000000000..7fd52e9dbb8 --- /dev/null +++ b/changelog/12334.improvement.rst @@ -0,0 +1 @@ +Support for Python 3.13 (beta1 at the time of writing). diff --git a/pyproject.toml b/pyproject.toml index 2be02ee7e23..b85f39d856c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Testing", "Topic :: Utilities", diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b80d53ca5f9..cfa226bb749 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -424,15 +424,14 @@ def recursionindex(self) -> Optional[int]: # which generates code objects that have hash/value equality # XXX needs a test key = entry.frame.code.path, id(entry.frame.code.raw), entry.lineno - # print "checking for recursion at", key values = cache.setdefault(key, []) + # Since Python 3.13 f_locals is a proxy, freeze it. + loc = dict(entry.frame.f_locals) if values: - f = entry.frame - loc = f.f_locals for otherloc in values: if otherloc == loc: return i - values.append(entry.frame.f_locals) + values.append(loc) return None diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 31c6de78195..f9ab007a4d1 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -289,7 +289,8 @@ def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) - backlocals = sys._getframe(1).f_locals + # Since Python 3.13, f_locals is not a dict, but eval requires a dict. + backlocals = dict(sys._getframe(1).f_locals) while entries: name, check = entries.pop(0) for ind, call in enumerate(self.calls[i:]): @@ -760,6 +761,9 @@ def _makefile( ) -> Path: items = list(files.items()) + if ext is None: + raise TypeError("ext must not be None") + if ext and not ext.startswith("."): raise ValueError( f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 86e30dc4830..b5474512987 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs from __future__ import annotations +import fnmatch import importlib import io import operator @@ -237,7 +238,7 @@ def f(n): n += 1 f(n) - excinfo = pytest.raises(RuntimeError, f, 8) + excinfo = pytest.raises(RecursionError, f, 8) traceback = excinfo.traceback recindex = traceback.recursionindex() assert recindex == 3 @@ -373,7 +374,10 @@ def test_excinfo_no_sourcecode(): except ValueError: excinfo = _pytest._code.ExceptionInfo.from_current() s = str(excinfo.traceback[-1]) - assert s == " File '':1 in \n ???\n" + # TODO: Since Python 3.13b1 under pytest-xdist, the * is `import + # sys;exec(eval(sys.stdin.readline()))` (execnet bootstrap code) + # instead of `???` like before. Is this OK? + fnmatch.fnmatch(s, " File '':1 in \n *\n") def test_excinfo_no_python_sourcecode(tmp_path: Path) -> None: diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 2fa85205795..a00259976c4 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -370,7 +370,11 @@ class B: pass B.__name__ = B.__qualname__ = "B2" - assert getfslineno(B)[1] == -1 + # Since Python 3.13 this started working. + if sys.version_info >= (3, 13): + assert getfslineno(B)[1] != -1 + else: + assert getfslineno(B)[1] == -1 def test_code_of_object_instance_with_call() -> None: diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index d7815f77b9a..8728ae84fdc 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -194,7 +194,7 @@ def test_custom_cache_dir_with_env_var( assert pytester.path.joinpath("custom_cache_dir").is_dir() -@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "/tox_env_dir"))) +@pytest.mark.parametrize("env", ((), ("TOX_ENV_DIR", "mydir/tox-env"))) def test_cache_reportheader( env: Sequence[str], pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: diff --git a/testing/test_doctest.py b/testing/test_doctest.py index c6f156b0e2d..9b33d641a14 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs import inspect from pathlib import Path +import sys import textwrap from typing import Callable from typing import Optional @@ -223,6 +224,7 @@ def test_doctest_unexpected_exception(self, pytester: Pytester): "Traceback (most recent call last):", ' File "*/doctest.py", line *, in __run', " *", + *((" *^^^^*", " *", " *") if sys.version_info >= (3, 13) else ()), ' File "", line 1, in ', "ZeroDivisionError: division by zero", "*/test_doctest_unexpected_exception.txt:2: UnexpectedException", @@ -379,7 +381,7 @@ def some_property(self): "*= FAILURES =*", "*_ [[]doctest[]] test_doctest_linedata_on_property.Sample.some_property _*", "004 ", - "005 >>> Sample().some_property", + "005 *>>> Sample().some_property", "Expected:", " 'another thing'", "Got:", diff --git a/tox.ini b/tox.ini index 4e1ff111966..0a3f0acf5b8 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ envlist = py310 py311 py312 + py313 pypy3 py38-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib} doctesting From 32baa0b93d770a7acb9f7b59eb2d3382e6b9b78e Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 19 May 2024 16:43:40 +0000 Subject: [PATCH 041/199] Prepare release version 8.2.1 (cherry picked from commit 66ff8dffdf9eee9b3dd6686de34542c49ff80dcd) --- changelog/12120.bugfix.rst | 1 - changelog/12191.bugfix.rst | 1 - changelog/12300.bugfix.rst | 1 - changelog/12308.bugfix.rst | 1 - changelog/12333.trivial.rst | 1 - changelog/12334.improvement.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-8.2.1.rst | 19 +++++++++++++++++ doc/en/builtin.rst | 2 +- doc/en/changelog.rst | 32 +++++++++++++++++++++++++++++ doc/en/example/parametrize.rst | 6 +++--- doc/en/example/pythoncollection.rst | 4 ++-- doc/en/getting-started.rst | 2 +- doc/en/how-to/fixtures.rst | 2 +- 14 files changed, 60 insertions(+), 14 deletions(-) delete mode 100644 changelog/12120.bugfix.rst delete mode 100644 changelog/12191.bugfix.rst delete mode 100644 changelog/12300.bugfix.rst delete mode 100644 changelog/12308.bugfix.rst delete mode 100644 changelog/12333.trivial.rst delete mode 100644 changelog/12334.improvement.rst create mode 100644 doc/en/announce/release-8.2.1.rst diff --git a/changelog/12120.bugfix.rst b/changelog/12120.bugfix.rst deleted file mode 100644 index b1ca4913b37..00000000000 --- a/changelog/12120.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix `PermissionError` crashes arising from directories which are not selected on the command-line. diff --git a/changelog/12191.bugfix.rst b/changelog/12191.bugfix.rst deleted file mode 100644 index 5102d469814..00000000000 --- a/changelog/12191.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Keyboard interrupts and system exits are now properly handled during the test collection. diff --git a/changelog/12300.bugfix.rst b/changelog/12300.bugfix.rst deleted file mode 100644 index 6c162482022..00000000000 --- a/changelog/12300.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed handling of 'Function not implemented' error under squashfuse_ll, which is a different way to say that the mountpoint is read-only. diff --git a/changelog/12308.bugfix.rst b/changelog/12308.bugfix.rst deleted file mode 100644 index 07995427a1a..00000000000 --- a/changelog/12308.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix a regression in pytest 8.2.0 where the permissions of automatically-created ``.pytest_cache`` directories became ``rwx------`` instead of the expected ``rwxr-xr-x``. diff --git a/changelog/12333.trivial.rst b/changelog/12333.trivial.rst deleted file mode 100644 index 32c4c5771a7..00000000000 --- a/changelog/12333.trivial.rst +++ /dev/null @@ -1 +0,0 @@ -pytest releases are now attested using the recent `Artifact Attestation ` support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts. diff --git a/changelog/12334.improvement.rst b/changelog/12334.improvement.rst deleted file mode 100644 index 7fd52e9dbb8..00000000000 --- a/changelog/12334.improvement.rst +++ /dev/null @@ -1 +0,0 @@ -Support for Python 3.13 (beta1 at the time of writing). diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 4d0a3ab558e..8a33f7fb57d 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.2.1 release-8.2.0 release-8.1.2 release-8.1.1 diff --git a/doc/en/announce/release-8.2.1.rst b/doc/en/announce/release-8.2.1.rst new file mode 100644 index 00000000000..4452edec110 --- /dev/null +++ b/doc/en/announce/release-8.2.1.rst @@ -0,0 +1,19 @@ +pytest-8.2.1 +======================================= + +pytest 8.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: + +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index d5f2e9a1b0f..458253fabbb 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:542 + cache -- .../_pytest/cacheprovider.py:549 Return a cache object that can persist state between testing sessions. cache.get(key, default) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index d5cde498859..f69b9782bbc 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,38 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.2.1 (2024-05-19) +========================= + +Improvements +------------ + +- `#12334 `_: Support for Python 3.13 (beta1 at the time of writing). + + + +Bug Fixes +--------- + +- `#12120 `_: Fix `PermissionError` crashes arising from directories which are not selected on the command-line. + + +- `#12191 `_: Keyboard interrupts and system exits are now properly handled during the test collection. + + +- `#12300 `_: Fixed handling of 'Function not implemented' error under squashfuse_ll, which is a different way to say that the mountpoint is read-only. + + +- `#12308 `_: Fix a regression in pytest 8.2.0 where the permissions of automatically-created ``.pytest_cache`` directories became ``rwx------`` instead of the expected ``rwxr-xr-x``. + + + +Trivial/Internal Changes +------------------------ + +- `#12333 `_: pytest releases are now attested using the recent `Artifact Attestation ` support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts. + + pytest 8.2.0 (2024-04-27) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 1bbe2faaad0..03f6852e5c0 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -162,7 +162,7 @@ objects, they are still using the default pytest representation: rootdir: /home/sweet/project collected 8 items - + @@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia rootdir: /home/sweet/project collected 4 items - + @@ -318,7 +318,7 @@ Let's first see how it looks like at collection time: rootdir: /home/sweet/project collected 2 items - + diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index a383173d07e..aa9d05d7227 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -152,7 +152,7 @@ The test collection would look like this: configfile: pytest.ini collected 2 items - + @@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this: configfile: pytest.ini collected 3 items - + diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 468de3654d9..94e0d80e656 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 8.2.0 + pytest 8.2.1 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 72b69a14681..6cc20c8c3e4 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used: rootdir: /home/sweet/project collected 12 items - + From d4f827d86b1c15a4628c1a41bdea57e577d71784 Mon Sep 17 00:00:00 2001 From: Hynek Schlawack Date: Sun, 19 May 2024 17:14:45 -0400 Subject: [PATCH 042/199] Fix link in changelog (#12343) --- doc/en/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index f69b9782bbc..ca7f5b999bd 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -57,7 +57,7 @@ Bug Fixes Trivial/Internal Changes ------------------------ -- `#12333 `_: pytest releases are now attested using the recent `Artifact Attestation ` support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts. +- `#12333 `_: pytest releases are now attested using the recent `Artifact Attestation `_ support from GitHub, allowing users to verify the provenance of pytest's sdist and wheel artifacts. pytest 8.2.0 (2024-04-27) From 00be7c07390fa5b85882bb19bbb246732a5d84cb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 07:11:04 +0200 Subject: [PATCH 043/199] build(deps): Bump pytest-asyncio in /testing/plugins_integration (#12345) Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.23.6 to 0.23.7. - [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases) - [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.23.6...v0.23.7) --- updated-dependencies: - dependency-name: pytest-asyncio dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index d4d0fc633c0..458cc2f9743 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,6 +1,6 @@ anyio[curio,trio]==4.3.0 django==5.0.6 -pytest-asyncio==0.23.6 +pytest-asyncio==0.23.7 pytest-bdd==7.1.2 pytest-cov==5.0.0 pytest-django==4.8.0 From 5d5c9dc85850f64621190e62bd20d4b190bdbdb6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 07:04:09 +0000 Subject: [PATCH 044/199] [pre-commit.ci] pre-commit autoupdate (#12321) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.3 → v0.4.4](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.3...v0.4.4) - [github.com/tox-dev/pyproject-fmt: 1.8.0 → 2.1.3](https://github.com/tox-dev/pyproject-fmt/compare/1.8.0...2.1.3) Also fix the comment following autofix Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 4 +- pyproject.toml | 274 +++++++++++++++++++++------------------- 2 files changed, 148 insertions(+), 130 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b83ae969661..f20f61761e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.3" + rev: "v0.4.4" hooks: - id: ruff args: ["--fix"] @@ -38,7 +38,7 @@ repos: # on <3.11 - exceptiongroup>=1.0.0rc8 - repo: https://github.com/tox-dev/pyproject-fmt - rev: "1.8.0" + rev: "2.1.3" hooks: - id: pyproject-fmt # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version diff --git a/pyproject.toml b/pyproject.toml index b85f39d856c..e3c64b3e9d8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,10 @@ +[build-system] +build-backend = "setuptools.build_meta" +requires = [ + "setuptools>=61", + "setuptools-scm[toml]>=6.2.3", +] + [project] name = "pytest" description = "pytest: simple powerful testing with Python" @@ -6,15 +13,15 @@ keywords = [ "test", "unittest", ] -license = {text = "MIT"} +license = { text = "MIT" } authors = [ - {name = "Holger Krekel"}, - {name = "Bruno Oliveira"}, - {name = "Ronny Pfannschmidt"}, - {name = "Floris Bruynooghe"}, - {name = "Brianna Laugher"}, - {name = "Florian Bruhin"}, - {name = "Others (See AUTHORS)"}, + { name = "Holger Krekel" }, + { name = "Bruno Oliveira" }, + { name = "Ronny Pfannschmidt" }, + { name = "Floris Bruynooghe" }, + { name = "Brianna Laugher" }, + { name = "Florian Bruhin" }, + { name = "Others (See AUTHORS)" }, ] requires-python = ">=3.8" classifiers = [ @@ -31,7 +38,6 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Testing", "Topic :: Utilities", @@ -40,15 +46,14 @@ dynamic = [ "version", ] dependencies = [ - 'colorama; sys_platform == "win32"', - 'exceptiongroup>=1.0.0rc8; python_version < "3.11"', + "colorama; sys_platform=='win32'", + "exceptiongroup>=1.0.0rc8; python_version<'3.11'", "iniconfig", "packaging", - "pluggy<2.0,>=1.5", - 'tomli>=1; python_version < "3.11"', + "pluggy<2,>=1.5", + "tomli>=1; python_version<'3.11'", ] -[project.optional-dependencies] -dev = [ +optional-dependencies.dev = [ "argcomplete", "attrs>=19.2", "hypothesis>=3.56", @@ -58,59 +63,54 @@ dev = [ "setuptools", "xmlschema", ] -[project.urls] -Changelog = "https://docs.pytest.org/en/stable/changelog.html" -Homepage = "https://docs.pytest.org/en/latest/" -Source = "https://github.com/pytest-dev/pytest" -Tracker = "https://github.com/pytest-dev/pytest/issues" -Twitter = "https://twitter.com/pytestdotorg" -[project.scripts] -"py.test" = "pytest:console_main" -pytest = "pytest:console_main" - -[build-system] -build-backend = "setuptools.build_meta" -requires = [ - "setuptools>=61", - "setuptools-scm[toml]>=6.2.3", -] +urls.Changelog = "https://docs.pytest.org/en/stable/changelog.html" +urls.Homepage = "https://docs.pytest.org/en/latest/" +urls.Source = "https://github.com/pytest-dev/pytest" +urls.Tracker = "https://github.com/pytest-dev/pytest/issues" +urls.Twitter = "https://twitter.com/pytestdotorg" +scripts."py.test" = "pytest:console_main" +scripts.pytest = "pytest:console_main" [tool.setuptools.package-data] -"_pytest" = ["py.typed"] -"pytest" = ["py.typed"] +"_pytest" = [ + "py.typed", +] +"pytest" = [ + "py.typed", +] [tool.setuptools_scm] write_to = "src/_pytest/_version.py" [tool.black] -target-version = ['py38'] +target-version = [ + 'py38', +] [tool.ruff] -src = ["src"] line-length = 88 - -[tool.ruff.format] -docstring-code-format = true - -[tool.ruff.lint] -select = [ - "B", # bugbear - "D", # pydocstyle - "E", # pycodestyle - "F", # pyflakes - "I", # isort - "PYI", # flake8-pyi - "UP", # pyupgrade - "RUF", # ruff - "W", # pycodestyle - "PIE", # flake8-pie - "PGH004", # pygrep-hooks - Use specific rule codes when using noqa - "PLE", # pylint error - "PLW", # pylint warning +src = [ + "src", +] +format.docstring-code-format = true +lint.select = [ + "B", # bugbear + "D", # pydocstyle + "E", # pycodestyle + "F", # pyflakes + "I", # isort + "PGH004", # pygrep-hooks - Use specific rule codes when using noqa + "PIE", # flake8-pie + "PLE", # pylint error "PLR1714", # Consider merging multiple comparisons - "T100", # flake8-debugger + "PLW", # pylint warning + "PYI", # flake8-pyi + "RUF", # ruff + "T100", # flake8-debugger + "UP", # pyupgrade + "W", # pycodestyle ] -ignore = [ +lint.ignore = [ # bugbear ignore "B004", # Using `hasattr(x, "__call__")` to test if x is callable is unreliable. "B007", # Loop control variable `i` not used within loop body @@ -118,10 +118,6 @@ ignore = [ "B010", # [*] Do not call `setattr` with a constant attribute value. "B011", # Do not `assert False` (`python -O` removes these calls) "B028", # No explicit `stacklevel` keyword argument found - # pycodestyle ignore - # pytest can do weird low-level things, and we usually know - # what we're doing when we use type(..) is ... - "E721", # Do not compare types, use `isinstance()` # pydocstyle ignore "D100", # Missing docstring in public module "D101", # Missing docstring in public class @@ -131,46 +127,51 @@ ignore = [ "D105", # Missing docstring in magic method "D106", # Missing docstring in public nested class "D107", # Missing docstring in `__init__` - "D209", # [*] Multi-line docstring closing quotes should be on a separate line "D205", # 1 blank line required between summary line and description + "D209", # [*] Multi-line docstring closing quotes should be on a separate line "D400", # First line should end with a period "D401", # First line of docstring should be in imperative mood "D402", # First line should not be the function's signature "D404", # First word of the docstring should not be "This" "D415", # First line should end with a period, question mark, or exclamation point + # pytest can do weird low-level things, and we usually know + # what we're doing when we use type(..) is ... + "E721", # Do not compare types, use `isinstance()` + # pylint ignore + "PLR5501", # Use `elif` instead of `else` then `if` + "PLW0120", # remove the else and dedent its contents + "PLW0603", # Using the global statement + "PLW2901", # for loop variable overwritten by assignment target # ruff ignore "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` - # pylint ignore - "PLW0603", # Using the global statement - "PLW0120", # remove the else and dedent its contents - "PLW2901", # for loop variable overwritten by assignment target - "PLR5501", # Use `elif` instead of `else` then `if` ] - -[tool.ruff.lint.pycodestyle] +lint.per-file-ignores."src/_pytest/_py/**/*.py" = [ + "B", + "PYI", +] +lint.per-file-ignores."src/_pytest/_version.py" = [ + "I001", +] +lint.per-file-ignores."testing/python/approx.py" = [ + "B015", +] +lint.isort.combine-as-imports = true +lint.isort.force-single-line = true +lint.isort.force-sort-within-sections = true +lint.isort.known-local-folder = [ + "pytest", + "_pytest", +] +lint.isort.lines-after-imports = 2 +lint.isort.order-by-type = false # In order to be able to format for 88 char in ruff format -max-line-length = 120 - -[tool.ruff.lint.pydocstyle] -convention = "pep257" - -[tool.ruff.lint.isort] -force-single-line = true -combine-as-imports = true -force-sort-within-sections = true -order-by-type = false -known-local-folder = ["pytest", "_pytest"] -lines-after-imports = 2 - -[tool.ruff.lint.per-file-ignores] -"src/_pytest/_py/**/*.py" = ["B", "PYI"] -"src/_pytest/_version.py" = ["I001"] -"testing/python/approx.py" = ["B015"] +lint.pycodestyle.max-line-length = 120 +lint.pydocstyle.convention = "pep257" [tool.pylint.main] # Maximum number of characters on a single line. max-line-length = 120 -disable= [ +disable = [ "abstract-method", "arguments-differ", "arguments-renamed", @@ -291,16 +292,27 @@ indent = 4 [tool.pytest.ini_options] minversion = "2.0" addopts = "-rfEX -p pytester --strict-markers" -python_files = ["test_*.py", "*_test.py", "testing/python/*.py"] -python_classes = ["Test", "Acceptance"] -python_functions = ["test"] +python_files = [ + "test_*.py", + "*_test.py", + "testing/python/*.py", +] +python_classes = [ + "Test", + "Acceptance", +] +python_functions = [ + "test", +] # NOTE: "doc" is not included here, but gets tested explicitly via "doctesting". -testpaths = ["testing"] +testpaths = [ + "testing", +] norecursedirs = [ - "testing/example_scripts", - ".*", - "build", - "dist", + "testing/example_scripts", + ".*", + "build", + "dist", ] xfail_strict = true filterwarnings = [ @@ -355,49 +367,55 @@ directory = "changelog/" title_format = "pytest {version} ({project_date})" template = "changelog/_template.rst" - [[tool.towncrier.type]] - directory = "breaking" - name = "Breaking Changes" - showcontent = true +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking Changes" +showcontent = true - [[tool.towncrier.type]] - directory = "deprecation" - name = "Deprecations" - showcontent = true +[[tool.towncrier.type]] +directory = "deprecation" +name = "Deprecations" +showcontent = true - [[tool.towncrier.type]] - directory = "feature" - name = "Features" - showcontent = true +[[tool.towncrier.type]] +directory = "feature" +name = "Features" +showcontent = true - [[tool.towncrier.type]] - directory = "improvement" - name = "Improvements" - showcontent = true +[[tool.towncrier.type]] +directory = "improvement" +name = "Improvements" +showcontent = true - [[tool.towncrier.type]] - directory = "bugfix" - name = "Bug Fixes" - showcontent = true +[[tool.towncrier.type]] +directory = "bugfix" +name = "Bug Fixes" +showcontent = true - [[tool.towncrier.type]] - directory = "vendor" - name = "Vendored Libraries" - showcontent = true +[[tool.towncrier.type]] +directory = "vendor" +name = "Vendored Libraries" +showcontent = true - [[tool.towncrier.type]] - directory = "doc" - name = "Improved Documentation" - showcontent = true +[[tool.towncrier.type]] +directory = "doc" +name = "Improved Documentation" +showcontent = true - [[tool.towncrier.type]] - directory = "trivial" - name = "Trivial/Internal Changes" - showcontent = true +[[tool.towncrier.type]] +directory = "trivial" +name = "Trivial/Internal Changes" +showcontent = true [tool.mypy] -files = ["src", "testing", "scripts"] -mypy_path = ["src"] +files = [ + "src", + "testing", + "scripts", +] +mypy_path = [ + "src", +] check_untyped_defs = true disallow_any_generics = true disallow_untyped_defs = true From cbf6bd9dd28d8464f7eebe32e51b398e2a5e3039 Mon Sep 17 00:00:00 2001 From: Sam Jirovec <47160720+samjirovec@users.noreply.github.com> Date: Tue, 21 May 2024 08:56:18 -0500 Subject: [PATCH 045/199] Issue #12290 - Docs using Furo Theme W/ Dark Mode (#12326) * furo theme for docs site * removing duplicate tocs from deprecations and reference pages * removing pallets references in code and config * reverting trainings to sidebar * removed sphinx style and unpinned packaging version * updated styles --- AUTHORS | 1 + CONTRIBUTING.rst | 4 ---- changelog/12290.doc.rst | 1 + doc/en/{img => _static}/pytest1.png | Bin doc/en/_templates/sidebar/brand.html | 7 ++++++ doc/en/_templates/slim_searchbox.html | 14 ------------ doc/en/_templates/style.html | 7 ++++++ doc/en/conf.py | 30 ++++++++++---------------- doc/en/deprecations.rst | 4 ---- doc/en/index.rst | 17 +++++++-------- doc/en/reference/reference.rst | 3 --- doc/en/requirements.txt | 4 ++-- 12 files changed, 37 insertions(+), 55 deletions(-) create mode 100644 changelog/12290.doc.rst rename doc/en/{img => _static}/pytest1.png (100%) create mode 100644 doc/en/_templates/sidebar/brand.html delete mode 100644 doc/en/_templates/slim_searchbox.html create mode 100644 doc/en/_templates/style.html diff --git a/AUTHORS b/AUTHORS index 54ed85fc732..cc53ce10d4f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -358,6 +358,7 @@ Sadra Barikbin Saiprasad Kale Samuel Colvin Samuel Dion-Girardeau +Samuel Jirovec Samuel Searles-Bryant Samuel Therrien (Avasam) Samuele Pedroni diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index d7da59c812d..0bf440da261 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -5,10 +5,6 @@ Contribution getting started Contributions are highly welcomed and appreciated. Every little bit of help counts, so do not hesitate! -.. contents:: - :depth: 2 - :backlinks: none - .. _submitfeedback: diff --git a/changelog/12290.doc.rst b/changelog/12290.doc.rst new file mode 100644 index 00000000000..07fe3babc66 --- /dev/null +++ b/changelog/12290.doc.rst @@ -0,0 +1 @@ +Updated Sphinx theme to use Furo instead of Flask, enabling Dark mode theme. diff --git a/doc/en/img/pytest1.png b/doc/en/_static/pytest1.png similarity index 100% rename from doc/en/img/pytest1.png rename to doc/en/_static/pytest1.png diff --git a/doc/en/_templates/sidebar/brand.html b/doc/en/_templates/sidebar/brand.html new file mode 100644 index 00000000000..f997c4cca5f --- /dev/null +++ b/doc/en/_templates/sidebar/brand.html @@ -0,0 +1,7 @@ + + + diff --git a/doc/en/_templates/slim_searchbox.html b/doc/en/_templates/slim_searchbox.html deleted file mode 100644 index f088ff8d312..00000000000 --- a/doc/en/_templates/slim_searchbox.html +++ /dev/null @@ -1,14 +0,0 @@ -{# - basic/searchbox.html with heading removed. -#} -{%- if pagename != "search" and builder != "singlehtml" %} - - -{%- endif %} diff --git a/doc/en/_templates/style.html b/doc/en/_templates/style.html new file mode 100644 index 00000000000..400cb75ff97 --- /dev/null +++ b/doc/en/_templates/style.html @@ -0,0 +1,7 @@ + diff --git a/doc/en/conf.py b/doc/en/conf.py index af54b468996..e64931ae5c8 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -15,9 +15,7 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. -import os import shutil -import sys from textwrap import dedent from typing import TYPE_CHECKING @@ -65,7 +63,6 @@ # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = [ - "pallets_sphinx_themes", "pygments_pytest", "sphinx.ext.autodoc", "sphinx.ext.autosummary", @@ -140,10 +137,6 @@ # output. They are ignored by default. # show_authors = False -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - - # A list of ignored prefixes for module index sorting. # modindex_common_prefix = [] @@ -216,12 +209,9 @@ # -- Options for HTML output --------------------------------------------------- -sys.path.append(os.path.abspath("_themes")) -html_theme_path = ["_themes"] - # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = "flask" +html_theme = "furo" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -266,18 +256,24 @@ html_sidebars = { "index": [ - "slim_searchbox.html", + "sidebar/brand.html", + "sidebar/search.html", + "sidebar/scroll-start.html", "sidebarintro.html", "globaltoc.html", "links.html", - "sourcelink.html", + "sidebar/scroll-end.html", + "style.html", ], "**": [ - "slim_searchbox.html", + "sidebar/brand.html", + "sidebar/search.html", + "sidebar/scroll-start.html", "globaltoc.html", "relations.html", "links.html", - "sourcelink.html", + "sidebar/scroll-end.html", + "style.html", ], } @@ -337,10 +333,6 @@ ) ] -# The name of an image file (relative to this directory) to place at the top of -# the title page. -latex_logo = "img/pytest1.png" - # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst index a65ea331663..bf6268a4980 100644 --- a/doc/en/deprecations.rst +++ b/doc/en/deprecations.rst @@ -7,10 +7,6 @@ This page lists all pytest features that are currently deprecated or have been r The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives should be used instead. -.. contents:: - :depth: 3 - :local: - Deprecated Features ------------------- diff --git a/doc/en/index.rst b/doc/en/index.rst index 83eb27b0a53..2aebb0d6b9c 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,16 +1,16 @@ :orphan: -.. sidebar:: Next Open Trainings and Events +.. _features: - - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): - * **June 11th to 13th 2024**, Remote - * **March 4th to 6th 2025**, Leipzig, Germany / Remote - - `pytest development sprint `_, **June 17th -- 22nd 2024** - - pytest tips and tricks for a better testsuite, `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague +.. sidebar:: **Next Open Trainings and Events** - Also see :doc:`previous talks and blogposts `. + - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): + * **June 11th to 13th 2024**, Remote + * **March 4th to 6th 2025**, Leipzig, Germany / Remote + - `pytest development sprint `_, **June 17th -- 22nd 2024** + - pytest tips and tricks for a better testsuite, `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague -.. _features: + Also see :doc:`previous talks and blogposts ` pytest: helps you write better programs ======================================= @@ -25,7 +25,6 @@ scale to support complex functional testing for applications and libraries. **PyPI package name**: :pypi:`pytest` - A quick example --------------- diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 4036b7d9912..3675c7cb2ee 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -7,9 +7,6 @@ API Reference This page contains the full reference to pytest's API. -.. contents:: - :depth: 3 - :local: Constants --------- diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index 974988c8cf4..6e7221d645a 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,4 +1,3 @@ -pallets-sphinx-themes pluggy>=1.5.0 pygments-pytest>=2.3.0 sphinx-removed-in>=0.2.0 @@ -8,4 +7,5 @@ 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 +packaging +furo From 889d9b28d786c75b66f4d1acb80123bdb341639c Mon Sep 17 00:00:00 2001 From: Nathan Goldbaum Date: Fri, 24 May 2024 05:16:44 -0600 Subject: [PATCH 046/199] Add thread safety section to flaky test docs (#12359) Closes #12356 --- AUTHORS | 1 + changelog/12356.doc.rst | 2 ++ doc/en/explanation/flaky.rst | 22 +++++++++++++++++++--- 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 changelog/12356.doc.rst diff --git a/AUTHORS b/AUTHORS index cc53ce10d4f..18c60750e30 100644 --- a/AUTHORS +++ b/AUTHORS @@ -289,6 +289,7 @@ Mike Lundy Milan Lesnek Miro Hrončok mrbean-bremen +Nathan Goldbaum Nathaniel Compton Nathaniel Waisbrot Ned Batchelder diff --git a/changelog/12356.doc.rst b/changelog/12356.doc.rst new file mode 100644 index 00000000000..312c26d3298 --- /dev/null +++ b/changelog/12356.doc.rst @@ -0,0 +1,2 @@ +Added a subsection to the documentation for debugging flaky tests to mention +lack of thread safety in pytest as a possible source of flakyness. diff --git a/doc/en/explanation/flaky.rst b/doc/en/explanation/flaky.rst index 41cbe847989..cb6c3983424 100644 --- a/doc/en/explanation/flaky.rst +++ b/doc/en/explanation/flaky.rst @@ -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. -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. - 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. +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 ~~~~~~~~~~~~ @@ -123,3 +136,6 @@ Resources * `Flaky Tests at Google and How We Mitigate Them `_ by John Micco, 2016 * `Where do Google's flaky tests come from? `_ by Jeff Listfield, 2017 + + +.. _pytest-xdist: https://github.com/pytest-dev/pytest-xdist From b83dd34ce19da60486155ea9be81bfeb5c1b86b9 Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 26 May 2024 00:21:28 +0000 Subject: [PATCH 047/199] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 196 +++++++++++++++++-------------- 1 file changed, 110 insertions(+), 86 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 43c4748ea03..5d2a9349320 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =7.0.0 + :pypi:`pytest-asyncio` Pytest support for asyncio May 19, 2024 4 - Beta pytest<9,>=7.0.0 :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Feb 25, 2024 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) @@ -127,7 +127,7 @@ This list contains 1461 plugins. :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-apigateway` pytest plugin for AWS ApiGateway May 18, 2024 4 - Beta pytest + :pypi:`pytest-aws-apigateway` pytest plugin for AWS ApiGateway May 24, 2024 4 - Beta pytest :pypi:`pytest-aws-config` Protect your AWS credentials in unit tests May 28, 2021 N/A N/A :pypi:`pytest-aws-fixtures` A series of fixtures to use in integration tests involving actual AWS services. Feb 02, 2024 N/A pytest (>=8.0.0,<9.0.0) :pypi:`pytest-axe` pytest plugin for axe-selenium-python Nov 12, 2018 N/A pytest (>=3.0.0) @@ -142,13 +142,13 @@ This list contains 1461 plugins. :pypi:`pytest-bdd` BDD for pytest Mar 17, 2024 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 Dec 31, 2023 4 - Beta pytest >=5.0 - :pypi:`pytest-bdd-report` A pytest-bdd plugin for generating useful and informative BDD test reports Feb 19, 2024 N/A pytest >=7.1.3 + :pypi:`pytest-bdd-report` A pytest-bdd plugin for generating useful and informative BDD test reports May 20, 2024 N/A pytest >=7.1.3 :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 17, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 21, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -233,7 +233,7 @@ This list contains 1461 plugins. :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-cleanslate` Collects and executes pytest tests separately May 15, 2024 N/A pytest + :pypi:`pytest-cleanslate` Collects and executes pytest tests separately May 20, 2024 N/A pytest :pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A :pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -258,6 +258,7 @@ This list contains 1461 plugins. :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-collect-interface-info-plugin` Get executed interface information in pytest interface automation framework Sep 25, 2023 4 - Beta N/A + :pypi:`pytest-collect-jmeter-report-tests` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1 :pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0) :pypi:`pytest-collect-pytest-interinfo` A simple plugin to use with pytest Sep 26, 2023 4 - Beta N/A :pypi:`pytest-colordots` Colorizes the progress indicators Oct 06, 2017 5 - Production/Stable N/A @@ -309,7 +310,7 @@ This list contains 1461 plugins. :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A :pypi:`pytest-dashboard` Apr 22, 2024 N/A pytest<8.0.0,>=7.4.3 :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Apr 21, 2024 4 - Beta pytest + :pypi:`pytest-databases` Reusable database fixtures for any and all databases. May 25, 2024 4 - Beta pytest :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 03, 2023 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) @@ -368,7 +369,7 @@ This list contains 1461 plugins. :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-class` A pytest plugin for running django in class-scoped fixtures Aug 08, 2023 4 - Beta N/A - :pypi:`pytest-django-docker-pg` Jan 30, 2024 5 - Production/Stable pytest <8.0.0,>=7.0.0 + :pypi:`pytest-django-docker-pg` May 21, 2024 5 - Production/Stable pytest<9.0.0,>=7.0.0 :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 @@ -443,14 +444,14 @@ This list contains 1461 plugins. :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 Apr 04, 2024 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. Apr 30, 2024 5 - Production/Stable pytest>=7.0 - :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. Apr 30, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. Apr 30, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. Apr 30, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. Apr 30, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. Apr 30, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. Apr 30, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. Apr 30, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. May 23, 2024 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. May 23, 2024 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) @@ -645,7 +646,7 @@ This list contains 1461 plugins. :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Jan 17, 2024 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` Oct 23, 2023 N/A N/A + :pypi:`pytest-html-report-merger` May 22, 2024 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' @@ -680,7 +681,7 @@ This list contains 1461 plugins. :pypi:`pytest-initry` Plugin for sending automation test data from Pytest to the initry Apr 30, 2024 N/A pytest<9.0.0,>=8.1.1 :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest - :pypi:`pytest-inmanta-extensions` Inmanta tests package Apr 02, 2024 5 - Production/Stable N/A + :pypi:`pytest-inmanta-extensions` Inmanta tests package May 24, 2024 5 - Production/Stable N/A :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules May 10, 2024 5 - Production/Stable N/A :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Feb 22, 2024 4 - Beta pytest :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A @@ -716,11 +717,13 @@ This list contains 1461 plugins. :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-ctrf` Pytest plugin to generate json report in CTRF (Common Test Report Format) May 21, 2024 N/A pytest>6.0.0 :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-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0 :pypi:`pytest-jsonschema` A pytest plugin to perform JSONSchema validations Mar 27, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-jtl-collector` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1 :pypi:`pytest-jtr` pytest plugin supporting json test report output Apr 15, 2024 N/A pytest<8.0.0,>=7.1.2 :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Apr 04, 2024 4 - Beta pytest>=7.0 :pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest @@ -766,7 +769,7 @@ This list contains 1461 plugins. :pypi:`pytest-litter` Pytest plugin which verifies that tests do not modify file trees. Nov 23, 2023 4 - Beta pytest >=6.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 May 17, 2024 5 - Production/Stable pytest + :pypi:`pytest-localftpserver` A PyTest plugin which provides an FTP fixture for your tests May 19, 2024 5 - Production/Stable pytest :pypi:`pytest-localserver` pytest plugin to test server connections locally. Oct 12, 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-lock` pytest-lock is a pytest plugin that allows you to "lock" the results of unit tests, storing them in a local cache. This is particularly useful for tests that are resource-intensive or don't need to be run every time. When the tests are run subsequently, pytest-lock will compare the current results with the locked results and issue a warning if there are any discrepancies. Feb 03, 2024 N/A pytest (>=7.4.3,<8.0.0) @@ -779,11 +782,11 @@ This list contains 1461 plugins. :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Mar 10, 2024 5 - Production/Stable 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 Mar 30, 2024 5 - Production/Stable pytest==8.1.1 + :pypi:`pytest-logikal` Common testing environment May 23, 2024 5 - Production/Stable pytest==8.2.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 Mar 20, 2024 5 - Production/Stable pytest; extra == "test" :pypi:`pytest-loop` pytest plugin for looping tests Mar 30, 2024 5 - Production/Stable pytest - :pypi:`pytest-lsp` A pytest plugin for end-to-end testing of language servers Feb 07, 2024 3 - Alpha pytest + :pypi:`pytest-lsp` A pytest plugin for end-to-end testing of language servers May 22, 2024 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) @@ -818,7 +821,7 @@ This list contains 1461 plugins. :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-minio-mock` A pytest plugin for mocking Minio S3 interactions Apr 27, 2024 N/A pytest>=5.0.0 + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions May 19, 2024 N/A pytest>=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) :pypi:`pytest-mitmproxy` pytest plugin for mitmproxy tests Mar 07, 2024 N/A pytest >=7.0 :pypi:`pytest-ml` Test your machine learning! May 04, 2019 4 - Beta N/A @@ -864,7 +867,7 @@ This list contains 1461 plugins. :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-runner` Run the mypy static type checker as a pytest test case Apr 23, 2024 N/A pytest>=8.0 :pypi:`pytest-mypy-testing` Pytest plugin to check mypy output. Mar 04, 2024 N/A pytest>=7,<9 - :pypi:`pytest-mysql` MySQL process and client fixtures for pytest Oct 30, 2023 5 - Production/Stable pytest >=6.2 + :pypi:`pytest-mysql` MySQL process and client fixtures for pytest May 23, 2024 5 - Production/Stable pytest>=6.2 :pypi:`pytest-ndb` pytest notebook debugger Apr 28, 2024 N/A pytest :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) @@ -877,7 +880,7 @@ This list contains 1461 plugins. :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 May 17, 2024 N/A pytest<9.0.0,>=8.2.0 + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies May 20, 2024 N/A pytest<9.0.0,>=8.2.0 :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 Apr 11, 2024 N/A N/A @@ -929,7 +932,6 @@ This list contains 1461 plugins. :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-parameterize-from-files` A pytest plugin that parameterizes tests from data files. Feb 15, 2024 4 - Beta pytest>=7.2.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. Nov 03, 2023 5 - Production/Stable pytest @@ -949,12 +951,12 @@ This list contains 1461 plugins. :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-percents` Mar 16, 2024 N/A N/A - :pypi:`pytest-perf` Run performance tests against the mainline code. Jan 28, 2024 5 - Production/Stable pytest >=6 ; extra == 'testing' + :pypi:`pytest-perf` Run performance tests against the mainline code. May 20, 2024 5 - Production/Stable pytest!=8.1.*,>=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-performancetotal` A performance plugin for pytest Mar 19, 2024 4 - Beta N/A - :pypi:`pytest-persistence` Pytest tool for persistent objects Jul 04, 2023 N/A N/A + :pypi:`pytest-persistence` Pytest tool for persistent objects May 23, 2024 N/A N/A :pypi:`pytest-pexpect` Pytest pexpect plugin. Mar 27, 2024 4 - Beta pytest>=6.2.0 - :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker Apr 03, 2024 5 - Production/Stable pytest>=6.0.0 + :pypi:`pytest-pg` A tiny plugin for pytest which runs PostgreSQL in Docker May 21, 2024 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 Jul 27, 2023 N/A pytest (>=3.7.0) @@ -971,7 +973,7 @@ This list contains 1461 plugins. :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 May 06, 2024 N/A N/A - :pypi:`pytest_playwright_async` ASYNC Pytest plugin for Playwright May 04, 2024 N/A N/A + :pypi:`pytest_playwright_async` ASYNC Pytest plugin for Playwright May 24, 2024 N/A N/A :pypi:`pytest-playwright-asyncio` Aug 29, 2023 N/A N/A :pypi:`pytest-playwright-enhanced` A pytest plugin for playwright python Mar 24, 2024 N/A pytest<9.0.0,>=8.0.0 :pypi:`pytest-playwrights` A pytest wrapper with fixtures for Playwright to automate web browsers Dec 02, 2021 N/A N/A @@ -982,7 +984,7 @@ This list contains 1461 plugins. :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 26, 2024 5 - Production/Stable pytest>=7.4.2 :pypi:`pytest-pmisc` Mar 21, 2019 5 - Production/Stable N/A - :pypi:`pytest-pogo` Pytest plugin for pogo-migrate Mar 11, 2024 1 - Planning pytest (>=7,<9) + :pypi:`pytest-pogo` Pytest plugin for pogo-migrate May 22, 2024 1 - Planning pytest<9,>=7 :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 Oct 19, 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 @@ -1046,7 +1048,7 @@ This list contains 1461 plugins. :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-pyvenv` A package for create venv in tests Feb 27, 2024 N/A pytest ; extra == 'test' :pypi:`pytest-pyvista` Pytest-pyvista package Sep 29, 2023 4 - Beta pytest>=3.5.0 - :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration Sep 12, 2023 4 - Beta pytest (>=7.2.2,<8.0.0) + :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 22, 2024 4 - Beta pytest<9.0.0,>=7.2.2 :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 Nov 29, 2023 5 - Production/Stable pytest >=6.0 @@ -1119,7 +1121,7 @@ This list contains 1461 plugins. :pypi:`pytest-rerunclassfailures` pytest rerun class failures plugin Apr 24, 2024 5 - Production/Stable pytest>=7.2 :pypi:`pytest-rerunfailures` pytest plugin to re-run tests to eliminate flaky failures Mar 13, 2024 5 - Production/Stable pytest >=7.2 :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. Feb 08, 2024 4 - Beta pytest + :pypi:`pytest-reserial` Pytest fixture for recording and replaying serial port traffic. May 23, 2024 4 - Beta pytest :pypi:`pytest-resilient-circuits` Resilient Circuits fixtures for PyTest May 17, 2024 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) @@ -1144,7 +1146,7 @@ This list contains 1461 plugins. :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_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 18, 2024 N/A pytest<9,>=7 + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 24, 2024 N/A pytest<9,>=7 :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) @@ -1171,6 +1173,7 @@ This list contains 1461 plugins. :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. May 16, 2024 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A + :pypi:`pytest-scenario-files` A pytest plugin that generates unit test scenarios from data files. May 19, 2024 5 - Production/Stable pytest>=7.2.0 :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 Feb 16, 2024 5 - Production/Stable pytest >=3.5.0 :pypi:`pytest-screenshot-on-failure` Saves a screenshot when a test case from a pytest execution fails Jul 21, 2023 4 - Beta N/A @@ -1260,7 +1263,7 @@ This list contains 1461 plugins. :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-mock` pytest sqlalchemy plugin for mock May 21, 2024 3 - Alpha pytest>=7.0.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) @@ -1312,7 +1315,7 @@ This list contains 1461 plugins. :pypi:`pytest-telegram-notifier` Telegram notification plugin for Pytest Jun 27, 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 Jun 20, 2023 N/A pytest (>=6.0) + :pypi:`pytest-terraform` A pytest plugin for using terraform fixtures May 21, 2024 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) @@ -1469,7 +1472,7 @@ This list contains 1461 plugins. :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) Apr 23, 2024 N/A pytest~=7.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. Mar 31, 2024 4 - Beta pytest>=2.8 + :pypi:`pytest-xprocess` A pytest plugin for managing processes across test runs. May 19, 2024 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) @@ -2026,9 +2029,9 @@ This list contains 1461 plugins. Pytest fixtures for async generators :pypi:`pytest-asyncio` - *last release*: Mar 19, 2024, + *last release*: May 19, 2024, *status*: 4 - Beta, - *requires*: pytest <9,>=7.0.0 + *requires*: pytest<9,>=7.0.0 Pytest support for asyncio @@ -2152,7 +2155,7 @@ This list contains 1461 plugins. pytest plugin for testing AWS resource configurations :pypi:`pytest-aws-apigateway` - *last release*: May 18, 2024, + *last release*: May 24, 2024, *status*: 4 - Beta, *requires*: pytest @@ -2257,7 +2260,7 @@ This list contains 1461 plugins. BDD for pytest :pypi:`pytest-bdd-report` - *last release*: Feb 19, 2024, + *last release*: May 20, 2024, *status*: N/A, *requires*: pytest >=7.1.3 @@ -2299,7 +2302,7 @@ This list contains 1461 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: May 17, 2024, + *last release*: May 21, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2894,7 +2897,7 @@ This list contains 1461 plugins. Easy quality control for CLDF datasets using pytest :pypi:`pytest-cleanslate` - *last release*: May 15, 2024, + *last release*: May 20, 2024, *status*: N/A, *requires*: pytest @@ -3068,6 +3071,13 @@ This list contains 1461 plugins. Get executed interface information in pytest interface automation framework + :pypi:`pytest-collect-jmeter-report-tests` + *last release*: May 20, 2024, + *status*: 4 - Beta, + *requires*: pytest>=7.2.1 + + A simple plugin to use with pytest + :pypi:`pytest-collector` *last release*: Aug 02, 2022, *status*: N/A, @@ -3426,7 +3436,7 @@ This list contains 1461 plugins. Useful functions for managing data for pytest fixtures :pypi:`pytest-databases` - *last release*: Apr 21, 2024, + *last release*: May 25, 2024, *status*: 4 - Beta, *requires*: pytest @@ -3839,9 +3849,9 @@ This list contains 1461 plugins. A pytest plugin for running django in class-scoped fixtures :pypi:`pytest-django-docker-pg` - *last release*: Jan 30, 2024, + *last release*: May 21, 2024, *status*: 5 - Production/Stable, - *requires*: pytest <8.0.0,>=7.0.0 + *requires*: pytest<9.0.0,>=7.0.0 @@ -4364,56 +4374,56 @@ This list contains 1461 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 A pytest plugin that designed for embedded testing. :pypi:`pytest-embedded-arduino` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Arduino. :pypi:`pytest-embedded-idf` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with ESP-IDF. :pypi:`pytest-embedded-jtag` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with JTAG. :pypi:`pytest-embedded-qemu` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Espressif target boards. :pypi:`pytest-embedded-wokwi` - *last release*: Apr 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -5778,7 +5788,7 @@ This list contains 1461 plugins. Generates a static html report based on pytest framework :pypi:`pytest-html-report-merger` - *last release*: Oct 23, 2023, + *last release*: May 22, 2024, *status*: N/A, *requires*: N/A @@ -6023,7 +6033,7 @@ This list contains 1461 plugins. A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: Apr 02, 2024, + *last release*: May 24, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -6274,6 +6284,13 @@ This list contains 1461 plugins. Generate JSON test reports + :pypi:`pytest-json-ctrf` + *last release*: May 21, 2024, + *status*: N/A, + *requires*: pytest>6.0.0 + + Pytest plugin to generate json report in CTRF (Common Test Report Format) + :pypi:`pytest-json-fixtures` *last release*: Mar 14, 2023, *status*: 4 - Beta, @@ -6309,6 +6326,13 @@ This list contains 1461 plugins. A pytest plugin to perform JSONSchema validations + :pypi:`pytest-jtl-collector` + *last release*: May 20, 2024, + *status*: 4 - Beta, + *requires*: pytest>=7.2.1 + + A simple plugin to use with pytest + :pypi:`pytest-jtr` *last release*: Apr 15, 2024, *status*: N/A, @@ -6625,7 +6649,7 @@ This list contains 1461 plugins. Generate local badges (shields) reporting your test suite status. :pypi:`pytest-localftpserver` - *last release*: May 17, 2024, + *last release*: May 19, 2024, *status*: 5 - Production/Stable, *requires*: pytest @@ -6716,9 +6740,9 @@ This list contains 1461 plugins. :pypi:`pytest-logikal` - *last release*: Mar 30, 2024, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, - *requires*: pytest==8.1.1 + *requires*: pytest==8.2.1 Common testing environment @@ -6744,7 +6768,7 @@ This list contains 1461 plugins. pytest plugin for looping tests :pypi:`pytest-lsp` - *last release*: Feb 07, 2024, + *last release*: May 22, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -6989,7 +7013,7 @@ This list contains 1461 plugins. A plugin to test mp :pypi:`pytest-minio-mock` - *last release*: Apr 27, 2024, + *last release*: May 19, 2024, *status*: N/A, *requires*: pytest>=5.0.0 @@ -7311,9 +7335,9 @@ This list contains 1461 plugins. Pytest plugin to check mypy output. :pypi:`pytest-mysql` - *last release*: Oct 30, 2023, + *last release*: May 23, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=6.2 + *requires*: pytest>=6.2 MySQL process and client fixtures for pytest @@ -7402,7 +7426,7 @@ This list contains 1461 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: May 17, 2024, + *last release*: May 20, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.2.0 @@ -7765,13 +7789,6 @@ This list contains 1461 plugins. Configure pytest fixtures using a combination of"parametrize" and markers - :pypi:`pytest-parameterize-from-files` - *last release*: Feb 15, 2024, - *status*: 4 - Beta, - *requires*: pytest>=7.2.0 - - A pytest plugin that parameterizes tests from data files. - :pypi:`pytest-parametrization` *last release*: May 22, 2022, *status*: 5 - Production/Stable, @@ -7906,9 +7923,9 @@ This list contains 1461 plugins. :pypi:`pytest-perf` - *last release*: Jan 28, 2024, + *last release*: May 20, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=6 ; extra == 'testing' + *requires*: pytest!=8.1.*,>=6; extra == "testing" Run performance tests against the mainline code. @@ -7927,7 +7944,7 @@ This list contains 1461 plugins. A performance plugin for pytest :pypi:`pytest-persistence` - *last release*: Jul 04, 2023, + *last release*: May 23, 2024, *status*: N/A, *requires*: N/A @@ -7941,7 +7958,7 @@ This list contains 1461 plugins. Pytest pexpect plugin. :pypi:`pytest-pg` - *last release*: Apr 03, 2024, + *last release*: May 21, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=6.0.0 @@ -8060,7 +8077,7 @@ This list contains 1461 plugins. A pytest wrapper with fixtures for Playwright to automate web browsers :pypi:`pytest_playwright_async` - *last release*: May 04, 2024, + *last release*: May 24, 2024, *status*: N/A, *requires*: N/A @@ -8137,9 +8154,9 @@ This list contains 1461 plugins. :pypi:`pytest-pogo` - *last release*: Mar 11, 2024, + *last release*: May 22, 2024, *status*: 1 - Planning, - *requires*: pytest (>=7,<9) + *requires*: pytest<9,>=7 Pytest plugin for pogo-migrate @@ -8585,9 +8602,9 @@ This list contains 1461 plugins. Pytest-pyvista package :pypi:`pytest-qaseio` - *last release*: Sep 12, 2023, + *last release*: May 22, 2024, *status*: 4 - Beta, - *requires*: pytest (>=7.2.2,<8.0.0) + *requires*: pytest<9.0.0,>=7.2.2 Pytest plugin for Qase.io integration @@ -9096,7 +9113,7 @@ This list contains 1461 plugins. pytest plugin to re-run tests to eliminate flaky failures :pypi:`pytest-reserial` - *last release*: Feb 08, 2024, + *last release*: May 23, 2024, *status*: 4 - Beta, *requires*: pytest @@ -9271,7 +9288,7 @@ This list contains 1461 plugins. pytest plugin for ROAST configuration override and fixtures :pypi:`pytest_robotframework` - *last release*: May 18, 2024, + *last release*: May 24, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -9459,6 +9476,13 @@ This list contains 1461 plugins. pytest plugin for test scenarios + :pypi:`pytest-scenario-files` + *last release*: May 19, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=7.2.0 + + A pytest plugin that generates unit test scenarios from data files. + :pypi:`pytest-schedule` *last release*: Jan 07, 2023, *status*: 5 - Production/Stable, @@ -10083,9 +10107,9 @@ This list contains 1461 plugins. pytest plugin with sqlalchemy related fixtures :pypi:`pytest-sqlalchemy-mock` - *last release*: Mar 15, 2023, + *last release*: May 21, 2024, *status*: 3 - Alpha, - *requires*: pytest (>=2.0) + *requires*: pytest>=7.0.0 pytest sqlalchemy plugin for mock @@ -10447,9 +10471,9 @@ This list contains 1461 plugins. Terraform and Terragrunt fixtures for pytest :pypi:`pytest-terraform` - *last release*: Jun 20, 2023, + *last release*: May 21, 2024, *status*: N/A, - *requires*: pytest (>=6.0) + *requires*: pytest>=6.0 A pytest plugin for using terraform fixtures @@ -11546,7 +11570,7 @@ This list contains 1461 plugins. An extended parametrizing plugin of pytest. :pypi:`pytest-xprocess` - *last release*: Mar 31, 2024, + *last release*: May 19, 2024, *status*: 4 - Beta, *requires*: pytest>=2.8 From c3c51037f0a18e65d49aac093d9bd7a2e924f0a2 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 25 May 2024 22:02:28 +0300 Subject: [PATCH 048/199] unittest: fix class instances no longer released on test teardown since pytest 8.2.0 Fix #12367. --- changelog/12367.bugfix.rst | 1 + src/_pytest/unittest.py | 3 ++- testing/test_unittest.py | 36 ++++++++++++++++++++---------------- 3 files changed, 23 insertions(+), 17 deletions(-) create mode 100644 changelog/12367.bugfix.rst diff --git a/changelog/12367.bugfix.rst b/changelog/12367.bugfix.rst new file mode 100644 index 00000000000..e8bf2e4f155 --- /dev/null +++ b/changelog/12367.bugfix.rst @@ -0,0 +1 @@ +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. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 919b497c295..643443f08c6 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -218,11 +218,12 @@ def setup(self) -> None: super().setup() def teardown(self) -> None: - super().teardown() if self._explicit_tearDown is not None: self._explicit_tearDown() self._explicit_tearDown = None self._obj = None + self._instance = None + super().teardown() def startTest(self, testcase: "unittest.TestCase") -> None: pass diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 003a74d3849..c359715dc2e 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,5 +1,4 @@ # mypy: allow-untyped-defs -import gc import sys from typing import List @@ -192,30 +191,35 @@ def test_check(self): def test_teardown_issue1649(pytester: Pytester) -> None: """ 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 would then exist in the stackframe. + + Regression test for #1649 (see also #12367). """ - testpath = pytester.makepyfile( + pytester.makepyfile( """ import unittest - class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase): - def setUp(self): - self.an_expensive_object = 1 - def test_demo(self): - pass + import gc - """ + class TestCaseObjectsShouldBeCleanedUp(unittest.TestCase): + def test_expensive(self): + self.an_expensive_obj = object() + + def test_is_it_still_alive(self): + gc.collect() + for obj in gc.get_objects(): + if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp": + assert not hasattr(obj, "an_expensive_obj") + break + else: + assert False, "Could not find TestCaseObjectsShouldBeCleanedUp instance" + """ ) - pytester.inline_run("-s", testpath) - gc.collect() - - # Either already destroyed, or didn't run setUp. - for obj in gc.get_objects(): - if type(obj).__name__ == "TestCaseObjectsShouldBeCleanedUp": - assert not hasattr(obj, "an_expensive_obj") + result = pytester.runpytest() + assert result.ret == ExitCode.OK def test_unittest_skip_issue148(pytester: Pytester) -> None: From 020db7ec046a7e84c431db7dbe5163887732602e Mon Sep 17 00:00:00 2001 From: James Frost Date: Sun, 26 May 2024 11:21:30 +0100 Subject: [PATCH 049/199] Add html_baseurl to sphinx conf.py (#12364) This is used to set the tag that points to the canonical version of the webpage. Including this indicates to search engines which version to include in their indexes, and should prevent older versions showing up. Fixes #12363 --- AUTHORS | 1 + changelog/12363.doc.rst | 1 + doc/en/conf.py | 3 +++ 3 files changed, 5 insertions(+) create mode 100644 changelog/12363.doc.rst diff --git a/AUTHORS b/AUTHORS index 18c60750e30..748b9bae2c8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -192,6 +192,7 @@ Jake VanderPlas Jakob van Santen Jakub Mitoraj James Bourbeau +James Frost Jan Balster Janne Vanhala Jason R. Coombs diff --git a/changelog/12363.doc.rst b/changelog/12363.doc.rst new file mode 100644 index 00000000000..c657281babf --- /dev/null +++ b/changelog/12363.doc.rst @@ -0,0 +1 @@ +The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results. diff --git a/doc/en/conf.py b/doc/en/conf.py index e64931ae5c8..738d07dc2f1 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -312,6 +312,9 @@ # Output file base name for HTML help builder. 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 -------------------------------------------------- From 24abe4eb03f64bfed301e6d70dfcf4f0c6a8cc0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 06:43:30 +0200 Subject: [PATCH 050/199] build(deps): Bump anyio[curio,trio] in /testing/plugins_integration (#12374) Bumps [anyio[curio,trio]](https://github.com/agronholm/anyio) from 4.3.0 to 4.4.0. - [Release notes](https://github.com/agronholm/anyio/releases) - [Changelog](https://github.com/agronholm/anyio/blob/master/docs/versionhistory.rst) - [Commits](https://github.com/agronholm/anyio/compare/4.3.0...4.4.0) --- updated-dependencies: - dependency-name: anyio[curio,trio] dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 458cc2f9743..cf6e4763dc5 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,4 +1,4 @@ -anyio[curio,trio]==4.3.0 +anyio[curio,trio]==4.4.0 django==5.0.6 pytest-asyncio==0.23.7 pytest-bdd==7.1.2 From 88fae23bdddef9db9dba771080e220cfea65356a Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 May 2024 21:18:03 +0200 Subject: [PATCH 051/199] [pylint] Fixes all ``use-maxplit-args``, ``consider-using-enumerate`` (#12172) * [pylint 'use-maxsplit-arg'] Do not split more than necessary when using the first element * [pylint 'consider-using-enumerate'] Use zip when iterating on two iterators * [pylint] 'cell-var-from-loop' and 'disallowed-name' permanent disable * [pylint] Disable 'possibly-used-before-assignment' following 3.2.0 release --- pyproject.toml | 7 +++---- src/_pytest/fixtures.py | 9 +++++++-- testing/test_reports.py | 12 ++++++------ testing/test_warnings.py | 6 ++---- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e3c64b3e9d8..11590a90e9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,13 +181,12 @@ disable = [ "bad-mcs-method-argument", "broad-exception-caught", "broad-exception-raised", - "cell-var-from-loop", + "cell-var-from-loop", # B023 from ruff / flake8-bugbear "comparison-of-constants", "comparison-with-callable", "comparison-with-itself", "condition-evals-to-constant", "consider-using-dict-items", - "consider-using-enumerate", "consider-using-from-import", "consider-using-f-string", "consider-using-in", @@ -195,7 +194,7 @@ disable = [ "consider-using-ternary", "consider-using-with", "cyclic-import", - "disallowed-name", + "disallowed-name", # foo / bar are used often in tests "duplicate-code", "eval-used", "exec-used", @@ -229,6 +228,7 @@ disable = [ "pointless-exception-statement", "pointless-statement", "pointless-string-statement", + "possibly-used-before-assignment", "protected-access", "raise-missing-from", "redefined-argument-from-local", @@ -276,7 +276,6 @@ disable = [ "useless-else-on-loop", "useless-import-alias", "useless-return", - "use-maxsplit-arg", "using-constant-test", "wrong-import-order", ] diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 06da52aeda3..b7a58081ba8 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1821,7 +1821,10 @@ def write_fixture(fixture_def: FixtureDef[object]) -> None: fixture_doc = inspect.getdoc(fixture_def.func) if fixture_doc: 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: tw.line(" no docstring available", red=True) @@ -1903,7 +1906,9 @@ def _showfixtures_main(config: Config, session: "Session") -> None: tw.write("\n") doc = inspect.getdoc(fixturedef.func) 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: tw.line(" no docstring available", red=True) tw.line() diff --git a/testing/test_reports.py b/testing/test_reports.py index c6baeebc9dd..7987b401771 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -100,14 +100,13 @@ def test_repr_entry(): rep_entries = rep.longrepr.reprtraceback.reprentries a_entries = a.longrepr.reprtraceback.reprentries - for i in range(len(a_entries)): - rep_entry = rep_entries[i] + assert len(rep_entries) == len(a_entries) # python < 3.10 zip(strict=True) + for a_entry, rep_entry in zip(a_entries, rep_entries): assert isinstance(rep_entry, ReprEntry) assert rep_entry.reprfileloc is not None assert rep_entry.reprfuncargs is not None assert rep_entry.reprlocals is not None - a_entry = a_entries[i] assert isinstance(a_entry, ReprEntry) assert a_entry.reprfileloc is not None assert a_entry.reprfuncargs is not None @@ -146,9 +145,10 @@ def test_repr_entry_native(): rep_entries = rep.longrepr.reprtraceback.reprentries a_entries = a.longrepr.reprtraceback.reprentries - for i in range(len(a_entries)): - assert isinstance(rep_entries[i], ReprEntryNative) - assert rep_entries[i].lines == a_entries[i].lines + assert len(rep_entries) == len(a_entries) # python < 3.10 zip(strict=True) + for rep_entry, a_entry in zip(rep_entries, a_entries): + assert isinstance(rep_entry, ReprEntryNative) + assert rep_entry.lines == a_entry.lines def test_itemreport_outcomes(self, pytester: Pytester) -> None: # This test came originally from test_remote.py in xdist (ca03269). diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 770454e83df..73c8c1b3231 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -280,10 +280,8 @@ def pytest_warning_recorded(self, warning_message, when, nodeid, location): ("call 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)): - collected_result = collected[index] - expected_result = expected[index] - + assert len(collected) == len(expected) # python < 3.10 zip(strict=True) + for collected_result, expected_result in zip(collected, expected): assert collected_result[0] == expected_result[0], str(collected) assert collected_result[1] == expected_result[1], str(collected) assert collected_result[2] == expected_result[2], str(collected) From 48cb8a2b329d0e8beaf30804c503eddb5e531385 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 22:31:18 +0000 Subject: [PATCH 052/199] [pre-commit.ci] pre-commit autoupdate (#12380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.4 → v0.4.5](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.4...v0.4.5) * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- src/_pytest/_code/code.py | 4 ++-- src/_pytest/config/__init__.py | 2 +- src/_pytest/doctest.py | 2 +- src/_pytest/threadexception.py | 4 ++-- src/_pytest/unraisableexception.py | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f20f61761e0..64bdbb36a94 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.4" + rev: "v0.4.5" hooks: - id: ruff args: ["--fix"] diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index cfa226bb749..4fb686a86e5 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -199,8 +199,8 @@ def __init__( rawentry: TracebackType, repr_style: Optional['Literal["short", "long"]'] = None, ) -> None: - self._rawentry: "Final" = rawentry - self._repr_style: "Final" = repr_style + self._rawentry: Final = rawentry + self._repr_style: Final = repr_style def with_repr_style( self, repr_style: Optional['Literal["short", "long"]'] diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c698ea9a777..f3d3b3062b5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1913,7 +1913,7 @@ def parse_warning_filter( parts.append("") action_, message, category_, module, lineno_ = (s.strip() for s in parts) 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: raise UsageError(error_template.format(error=str(e))) from None try: diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 35a13676222..2d7453b4ee0 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -298,7 +298,7 @@ def setup(self) -> None: def runtest(self) -> None: _check_all_skipped(self.dtest) 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 # doctest expects. self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 09faf661b91..603a1777c92 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -34,8 +34,8 @@ class catch_threading_exception: """ def __init__(self) -> None: - self.args: Optional["threading.ExceptHookArgs"] = None - self._old_hook: Optional[Callable[["threading.ExceptHookArgs"], Any]] = None + self.args: Optional[threading.ExceptHookArgs] = None + self._old_hook: Optional[Callable[[threading.ExceptHookArgs], Any]] = None def _hook(self, args: "threading.ExceptHookArgs") -> None: self.args = args diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py index f649267abf1..50b121e8811 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -34,8 +34,8 @@ class catch_unraisable_exception: """ def __init__(self) -> None: - self.unraisable: Optional["sys.UnraisableHookArgs"] = None - self._old_hook: Optional[Callable[["sys.UnraisableHookArgs"], Any]] = None + self.unraisable: Optional[sys.UnraisableHookArgs] = None + self._old_hook: Optional[Callable[[sys.UnraisableHookArgs], Any]] = None def _hook(self, unraisable: "sys.UnraisableHookArgs") -> None: # Storing unraisable.object can resurrect an object which is being From 9f121e85a78310e50acde71082dc0df3e5ce895c Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Tue, 28 May 2024 13:47:00 -0300 Subject: [PATCH 053/199] Clarify pytest_ignore_collect docs (#12385) Fixes #12383 Co-authored-by: Ran Benita --- src/_pytest/hookspec.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 8a38ff4fb43..c7f9d036c33 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -311,7 +311,12 @@ def pytest_collection_finish(session: "Session") -> None: def pytest_ignore_collect( collection_path: Path, path: "LEGACY_PATH", config: "Config" ) -> 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 more specific hooks. From 383659d0be02e8ce5cb47f70961877dcec943018 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 13:39:29 +0200 Subject: [PATCH 054/199] build(deps): Bump hynek/build-and-inspect-python-package (#12373) Bumps [hynek/build-and-inspect-python-package](https://github.com/hynek/build-and-inspect-python-package) from 2.5.0 to 2.6.0. - [Release notes](https://github.com/hynek/build-and-inspect-python-package/releases) - [Changelog](https://github.com/hynek/build-and-inspect-python-package/blob/main/CHANGELOG.md) - [Commits](https://github.com/hynek/build-and-inspect-python-package/compare/v2.5.0...v2.6.0) --- updated-dependencies: - dependency-name: hynek/build-and-inspect-python-package dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- .github/workflows/test.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 20a72270fde..640a0f1c281 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -31,7 +31,7 @@ jobs: persist-credentials: false - 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: attest-build-provenance-github: 'true' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 09d37aaa2c8..45e9c918a57 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: fetch-depth: 0 persist-credentials: false - 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: needs: [package] From db67d5c8749273ebed23788526ad511e342d8b8d Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 May 2024 22:18:59 +0200 Subject: [PATCH 055/199] [pylint 'use-yield-from'] Fix all occurences in existing code --- src/_pytest/_py/path.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py index bcb05ac34cd..c7ab1182f4a 100644 --- a/src/_pytest/_py/path.py +++ b/src/_pytest/_py/path.py @@ -161,15 +161,13 @@ def gen(self, path): ) if not self.breadthfirst: for subdir in dirs: - for p in self.gen(subdir): - yield p + yield from self.gen(subdir) 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 + yield from self.gen(subdir) class FNMatcher: From c45afde35d3a81ff27b6ae6c31c515b12d499338 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 May 2024 22:34:02 +0200 Subject: [PATCH 056/199] [pylint 'consider-using-sys-exit'] Fix all occurences in existing code --- extra/get_issues.py | 3 ++- pyproject.toml | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extra/get_issues.py b/extra/get_issues.py index a0c2f19adfa..64e859e0c12 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -1,5 +1,6 @@ import json from pathlib import Path +import sys import requests @@ -17,7 +18,7 @@ def get_issues(): if r.status_code == 403: # API request limit exceeded print(data["message"]) - exit(1) + sys.exit(1) issues.extend(data) # Look for next page diff --git a/pyproject.toml b/pyproject.toml index 11590a90e9d..4594595e2fb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -190,7 +190,6 @@ disable = [ "consider-using-from-import", "consider-using-f-string", "consider-using-in", - "consider-using-sys-exit", "consider-using-ternary", "consider-using-with", "cyclic-import", From 0d33cdf02a2b742a63be75cbb2309803ea51c525 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Mon, 27 May 2024 23:12:29 +0200 Subject: [PATCH 057/199] [pylint] Disable the only 'misplaced-bare-raise' --- pyproject.toml | 3 +-- src/_pytest/logging.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4594595e2fb..3e01d048e0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -211,10 +211,9 @@ disable = [ "keyword-arg-before-vararg", "line-too-long", "method-hidden", - "misplaced-bare-raise", "missing-docstring", "missing-timeout", - "multiple-statements", + "multiple-statements", # multiple-statements-on-one-line-colon (E701) from ruff "no-else-break", "no-else-continue", "no-else-raise", diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index af5e443ced1..c9139d369ef 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -401,7 +401,7 @@ def handleError(self, record: logging.LogRecord) -> None: # The default behavior of logging is to print "Logging error" # to stderr with the call stack and some extra details. # pytest wants to make such mistakes visible during testing. - raise + raise # pylint: disable=misplaced-bare-raise @final From 908e112999c62307c891603bb509cf691ea66f66 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 31 Mar 2024 23:59:08 +0200 Subject: [PATCH 058/199] [pylint 'implicit-str-concat'] fix existing unwanted implicit str concat --- pyproject.toml | 1 - testing/_py/test_local.py | 2 +- testing/test_cacheprovider.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3e01d048e0c..0627c94c732 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -200,7 +200,6 @@ disable = [ "expression-not-assigned", "fixme", "global-statement", - "implicit-str-concat", "import-error", "import-outside-toplevel", "inconsistent-return-statements", diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index ea5794b251b..0215aba9695 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -207,7 +207,7 @@ def test_visit_norecurse(self, path1): @pytest.mark.parametrize( "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): lst = [] diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 8728ae84fdc..08158f6191a 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1163,7 +1163,7 @@ def test_1(): assert 1 ) 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))) From 10c6db2df2f6c40b212785ef00fc834cc5eb3288 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Thu, 30 May 2024 16:48:37 +0200 Subject: [PATCH 059/199] doc: Update trainings/events (#12401) --- doc/en/index.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 2aebb0d6b9c..58527ea7331 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -4,11 +4,10 @@ .. sidebar:: **Next Open Trainings and Events** - - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training): - * **June 11th to 13th 2024**, Remote - * **March 4th to 6th 2025**, Leipzig, Germany / Remote - - `pytest development sprint `_, **June 17th -- 22nd 2024** - - pytest tips and tricks for a better testsuite, `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague + - `pytest development sprint `_, **June 17th -- 22nd 2024**, Klaus (AT) / Remote + - `pytest tips and tricks for a better testsuite `_, at `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague (CZ) + - `pytest: Professionelles Testen (nicht nur) für Python `_, at `CH Open Workshoptage `_, **September 2nd 2024**, HSLU Rotkreuz (CH) + - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training), **March 4th -- 6th 2025**, Leipzig (DE) / Remote Also see :doc:`previous talks and blogposts ` From 98021838fd97e10e1c9095fcbd0abbb6432a83cb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 2 Jun 2024 09:31:40 -0300 Subject: [PATCH 060/199] [automated] Update plugin list (#12405) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 192 ++++++++++++++++++++----------- 1 file changed, 124 insertions(+), 68 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 5d2a9349320..0f116277997 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =7; extra == "pytest" - :pypi:`nuts` Network Unit Testing System Aug 11, 2023 N/A pytest (>=7.3.0,<8.0.0) + :pypi:`nuts` Network Unit Testing System May 28, 2024 N/A pytest<8,>=7 :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 Feb 10, 2024 N/A pytest (>=6) @@ -124,6 +124,7 @@ This list contains 1464 plugins. :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-aux` templates/examples and aux for pytest May 31, 2024 N/A N/A :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 @@ -148,7 +149,7 @@ This list contains 1464 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 21, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 31, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -171,6 +172,7 @@ This list contains 1464 plugins. :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-boto-mock` Thin-wrapper around the mock package for easier use with pytest May 27, 2024 5 - Production/Stable pytest>=8.2.0 :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-bq` BigQuery fixtures and fixture factories for Pytest. May 08, 2024 5 - Production/Stable pytest>=6.2 :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Feb 15, 2022 N/A N/A @@ -233,7 +235,7 @@ This list contains 1464 plugins. :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-cleanslate` Collects and executes pytest tests separately May 20, 2024 N/A pytest + :pypi:`pytest-cleanslate` Collects and executes pytest tests separately May 30, 2024 N/A pytest :pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A :pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -243,7 +245,7 @@ This list contains 1464 plugins. :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 May 12, 2024 N/A pytest<9,>=4 + :pypi:`pytest-cmake` Provide CMake module for Pytest May 31, 2024 N/A pytest<9,>=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_codeblocks` Test code blocks in your READMEs Sep 17, 2023 5 - Production/Stable pytest >= 7.0.0 @@ -302,13 +304,14 @@ This list contains 1464 plugins. :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-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail May 30, 2024 4 - Beta pytest>=6.2.0 :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 Apr 05, 2024 5 - Production/Stable pytest>=8 :pypi:`pytest-cython-collect` Jun 17, 2022 N/A pytest :pypi:`pytest-darker` A pytest plugin for checking of modified code using Darker Feb 25, 2024 N/A pytest <7,>=6.0.1 :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A - :pypi:`pytest-dashboard` Apr 22, 2024 N/A pytest<8.0.0,>=7.4.3 + :pypi:`pytest-dashboard` May 30, 2024 N/A pytest<8.0.0,>=7.4.3 :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A :pypi:`pytest-databases` Reusable database fixtures for any and all databases. May 25, 2024 4 - Beta pytest :pypi:`pytest-databricks` Pytest plugin for remote Databricks notebooks testing Jul 29, 2020 N/A pytest @@ -362,7 +365,8 @@ This list contains 1464 plugins. :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. May 11, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 :pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest - :pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. May 07, 2024 4 - Beta pytest>=3.5.0 + :pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. May 29, 2024 4 - Beta pytest>=3.5.0 + :pypi:`pytest-ditto-pandas` pytest-ditto plugin for pandas snapshots. May 29, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.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 @@ -444,13 +448,13 @@ This list contains 1464 plugins. :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 Apr 04, 2024 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. May 23, 2024 5 - Production/Stable pytest>=7.0 + :pypi:`pytest-embedded` A pytest plugin that designed for embedded testing. May 31, 2024 5 - Production/Stable pytest>=7.0 :pypi:`pytest-embedded-arduino` Make pytest-embedded plugin work with Arduino. May 23, 2024 5 - Production/Stable N/A :pypi:`pytest-embedded-idf` Make pytest-embedded plugin work with ESP-IDF. May 23, 2024 5 - Production/Stable N/A :pypi:`pytest-embedded-jtag` Make pytest-embedded plugin work with JTAG. May 23, 2024 5 - Production/Stable N/A :pypi:`pytest-embedded-qemu` Make pytest-embedded plugin work with QEMU. May 23, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. May 23, 2024 5 - Production/Stable N/A - :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. May 23, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial` Make pytest-embedded plugin work with Serial. May 31, 2024 5 - Production/Stable N/A + :pypi:`pytest-embedded-serial-esp` Make pytest-embedded plugin work with Espressif target boards. May 31, 2024 5 - Production/Stable N/A :pypi:`pytest-embedded-wokwi` Make pytest-embedded plugin work with the Wokwi CLI. May 23, 2024 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) @@ -480,6 +484,7 @@ This list contains 1464 plugins. :pypi:`pytest_exact_fixtures` Parse queries in Lucene and Elasticsearch syntaxes Feb 04, 2019 N/A N/A :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7 :pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9) + :pypi:`pytest-exasol-saas` May 27, 2024 N/A pytest<9,>=7 :pypi:`pytest-excel` pytest plugin for generating excel reports Sep 14, 2023 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 @@ -520,7 +525,7 @@ This list contains 1464 plugins. :pypi:`pytest-fastest` Use SCM and coverage to run only needed tests Oct 04, 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-fauna` A collection of helpful test fixtures for Fauna DB. Apr 22, 2024 N/A N/A + :pypi:`pytest-fauna` A collection of helpful test fixtures for Fauna DB. May 30, 2024 N/A N/A :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-file` Pytest File Mar 18, 2024 1 - Planning N/A @@ -616,7 +621,6 @@ This list contains 1464 plugins. :pypi:`pytest-hardware-test-report` A simple plugin to use with pytest Apr 01, 2024 4 - Beta pytest<9.0.0,>=8.0.0 :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. Mar 16, 2024 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. Feb 07, 2024 4 - Beta pytest (>=8.0.0,<9.0.0) :pypi:`pytest-helm-templates` Pytest fixtures for unit testing the output of helm templates May 08, 2024 N/A pytest~=7.4.0; extra == "dev" :pypi:`pytest-helper` Functions to help in using the pytest testing framework May 31, 2019 5 - Production/Stable N/A @@ -630,7 +634,7 @@ This list contains 1464 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 May 18, 2024 3 - Alpha pytest==8.1.1 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components May 31, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -743,7 +747,7 @@ This list contains 1464 plugins. :pypi:`pytest-kubernetes` Sep 14, 2023 N/A pytest (>=7.2.1,<8.0.0) :pypi:`pytest-kuunda` pytest plugin to help with test data setup for PySpark tests Feb 25, 2024 4 - Beta pytest >=6.2.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-lambda` Define pytest fixtures with lambda functions. May 27, 2024 5 - Production/Stable pytest<9,>=3.6 :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` Create fancy and clear HTML test reports. Nov 05, 2023 N/A N/A @@ -817,13 +821,13 @@ This list contains 1464 plugins. :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 Feb 12, 2024 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 Mar 14, 2024 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin May 28, 2024 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-minio-mock` A pytest plugin for mocking Minio S3 interactions May 19, 2024 N/A pytest>=5.0.0 + :pypi:`pytest-minio-mock` A pytest plugin for mocking Minio S3 interactions May 26, 2024 N/A pytest>=5.0.0 :pypi:`pytest-missing-fixtures` Pytest plugin that creates missing fixtures Oct 14, 2020 4 - Beta pytest (>=3.5.0) - :pypi:`pytest-mitmproxy` pytest plugin for mitmproxy tests Mar 07, 2024 N/A pytest >=7.0 + :pypi:`pytest-mitmproxy` pytest plugin for mitmproxy tests May 28, 2024 N/A pytest>=7.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 Mar 21, 2024 5 - Production/Stable pytest>=6.2.5 @@ -839,7 +843,6 @@ This list contains 1464 plugins. :pypi:`pytest-modalt` Massively distributed pytest runs using modal.com Feb 27, 2024 4 - Beta pytest >=6.2.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-molecule-JC` PyTest Molecule Plugin :: discover and run molecule tests Jul 18, 2023 5 - Production/Stable pytest (>=7.0.0) :pypi:`pytest-mongo` MongoDB process and client fixtures plugin for Pytest. Mar 13, 2024 5 - Production/Stable pytest >=6.2 @@ -907,7 +910,7 @@ This list contains 1464 plugins. :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 Mar 09, 2024 5 - Production/Stable pytest (<7.1) ; python_full_version <= "3.6.0" + :pypi:`pytest-only` Use @pytest.mark.only to run a single test May 27, 2024 5 - Production/Stable pytest<9,>=3.6.0 :pypi:`pytest-oof` A Pytest plugin providing structured, programmatic access to a test run's results Dec 11, 2023 4 - Beta N/A :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) @@ -1019,6 +1022,7 @@ This list contains 1464 plugins. :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) :pypi:`pytest-pt` pytest plugin to use \*.pt files as tests May 15, 2024 4 - Beta pytest :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) + :pypi:`pytest-publish` Jun 01, 2024 N/A pytest<9.0.0,>=8.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 @@ -1048,7 +1052,8 @@ This list contains 1464 plugins. :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-pyvenv` A package for create venv in tests Feb 27, 2024 N/A pytest ; extra == 'test' :pypi:`pytest-pyvista` Pytest-pyvista package Sep 29, 2023 4 - Beta pytest>=3.5.0 - :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 22, 2024 4 - Beta pytest<9.0.0,>=7.2.2 + :pypi:`pytest-qanova` A pytest plugin to collect test information May 26, 2024 3 - Alpha pytest + :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 30, 2024 4 - Beta pytest<9.0.0,>=7.2.2 :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 Nov 29, 2023 5 - Production/Stable pytest >=6.0 @@ -1146,7 +1151,7 @@ This list contains 1464 plugins. :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_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 24, 2024 N/A pytest<9,>=7 + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 29, 2024 N/A pytest<9,>=7 :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) @@ -1258,7 +1263,7 @@ This list contains 1464 plugins. :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 May 16, 2024 N/A pytest<8,>5.4.0 + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons May 27, 2024 N/A pytest<8,>5.4.0 :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX May 10, 2024 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 @@ -1281,7 +1286,7 @@ This list contains 1464 plugins. :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A :pypi:`pytest-store` Pytest plugin to store values from test runs Nov 16, 2023 3 - Alpha pytest (>=7.0.0) :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 Mar 13, 2024 N/A pytest + :pypi:`pytest-structlog` Structured logging assertions May 30, 2024 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) @@ -1323,7 +1328,7 @@ This list contains 1464 plugins. :pypi:`pytest-testdox` A testdox format reporter for pytest Jul 22, 2023 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 Feb 15, 2024 5 - Production/Stable pytest >=6 + :pypi:`pytest-testinfra` Test infrastructures May 26, 2024 5 - Production/Stable pytest>=6 :pypi:`pytest-testinfra-jpic` Test infrastructures Sep 21, 2023 5 - Production/Stable N/A :pypi:`pytest-testinfra-winrm-transport` Test infrastructures Sep 21, 2023 5 - Production/Stable N/A :pypi:`pytest-testlink-adaptor` pytest reporting plugin for testlink Dec 20, 2018 4 - Beta pytest (>=2.6) @@ -1352,6 +1357,7 @@ This list contains 1464 plugins. :pypi:`pytest-tesults` Tesults plugin for pytest Feb 15, 2024 5 - Production/Stable pytest >=3.5.0 :pypi:`pytest-textual-snapshot` Snapshot testing for Textual apps Aug 23, 2023 4 - Beta pytest (>=7.0.0) :pypi:`pytest-tezos` pytest-ligo Jan 16, 2020 4 - Beta N/A + :pypi:`pytest-tf` Test your OpenTofu and Terraform config using a PyTest plugin May 29, 2024 N/A pytest<9.0.0,>=8.2.1 :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-thread` Jul 07, 2023 N/A N/A @@ -1453,7 +1459,7 @@ This list contains 1464 plugins. :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-webtest-extras` Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. Nov 13, 2023 N/A pytest >= 7.0.0 :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 Mar 22, 2024 N/A pytest>=7.3.1 + :pypi:`pytest-when` Utility which makes mocking more readable and controllable May 28, 2024 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 @@ -1477,6 +1483,7 @@ This list contains 1464 plugins. :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 20, 2024 N/A N/A + :pypi:`pytest-xstress` Jun 01, 2024 N/A pytest<9.0.0,>=8.0.0 :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-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Oct 01, 2023 4 - Beta pytest >=7.1.0 :pypi:`pytest-yaml` This plugin is used to load yaml output to your test using pytest framework. Oct 05, 2018 N/A pytest @@ -1518,9 +1525,9 @@ This list contains 1464 plugins. Test whether your code is logging correctly 🪵 :pypi:`nuts` - *last release*: Aug 11, 2023, + *last release*: May 28, 2024, *status*: N/A, - *requires*: pytest (>=7.3.0,<8.0.0) + *requires*: pytest<8,>=7 Network Unit Testing System @@ -2133,6 +2140,13 @@ This list contains 1464 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. + :pypi:`pytest-aux` + *last release*: May 31, 2024, + *status*: N/A, + *requires*: N/A + + templates/examples and aux for pytest + :pypi:`pytest-aviator` *last release*: Nov 04, 2022, *status*: 4 - Beta, @@ -2302,7 +2316,7 @@ This list contains 1464 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: May 21, 2024, + *last release*: May 31, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2462,6 +2476,13 @@ This list contains 1464 plugins. + :pypi:`pytest-boto-mock` + *last release*: May 27, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest>=8.2.0 + + Thin-wrapper around the mock package for easier use with pytest + :pypi:`pytest-bpdb` *last release*: Jan 19, 2015, *status*: 2 - Pre-Alpha, @@ -2897,7 +2918,7 @@ This list contains 1464 plugins. Easy quality control for CLDF datasets using pytest :pypi:`pytest-cleanslate` - *last release*: May 20, 2024, + *last release*: May 30, 2024, *status*: N/A, *requires*: pytest @@ -2967,7 +2988,7 @@ This list contains 1464 plugins. Distribute tests to cloud machines without fuss :pypi:`pytest-cmake` - *last release*: May 12, 2024, + *last release*: May 31, 2024, *status*: N/A, *requires*: pytest<9,>=4 @@ -3379,6 +3400,13 @@ This list contains 1464 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report + :pypi:`pytest-custom-outputs` + *last release*: May 30, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail + :pypi:`pytest-custom-report` *last release*: Jan 30, 2019, *status*: N/A, @@ -3422,7 +3450,7 @@ This list contains 1464 plugins. pytest fixtures to run dash applications. :pypi:`pytest-dashboard` - *last release*: Apr 22, 2024, + *last release*: May 30, 2024, *status*: N/A, *requires*: pytest<8.0.0,>=7.4.3 @@ -3800,12 +3828,19 @@ This list contains 1464 plugins. Pytest plugin to record discovered tests in a file :pypi:`pytest-ditto` - *last release*: May 07, 2024, + *last release*: May 29, 2024, *status*: 4 - Beta, *requires*: pytest>=3.5.0 Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. + :pypi:`pytest-ditto-pandas` + *last release*: May 29, 2024, + *status*: 4 - Beta, + *requires*: pytest>=3.5.0 + + pytest-ditto plugin for pandas snapshots. + :pypi:`pytest-django` *last release*: Jan 30, 2024, *status*: 5 - Production/Stable, @@ -4374,7 +4409,7 @@ This list contains 1464 plugins. Send execution result email :pypi:`pytest-embedded` - *last release*: May 23, 2024, + *last release*: May 31, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=7.0 @@ -4409,14 +4444,14 @@ This list contains 1464 plugins. Make pytest-embedded plugin work with QEMU. :pypi:`pytest-embedded-serial` - *last release*: May 23, 2024, + *last release*: May 31, 2024, *status*: 5 - Production/Stable, *requires*: N/A Make pytest-embedded plugin work with Serial. :pypi:`pytest-embedded-serial-esp` - *last release*: May 23, 2024, + *last release*: May 31, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -4625,6 +4660,13 @@ This list contains 1464 plugins. + :pypi:`pytest-exasol-saas` + *last release*: May 27, 2024, + *status*: N/A, + *requires*: pytest<9,>=7 + + + :pypi:`pytest-excel` *last release*: Sep 14, 2023, *status*: 5 - Production/Stable, @@ -4906,7 +4948,7 @@ This list contains 1464 plugins. py.test plugin that activates the fault handler module for tests (dummy package) :pypi:`pytest-fauna` - *last release*: Apr 22, 2024, + *last release*: May 30, 2024, *status*: N/A, *requires*: N/A @@ -5577,13 +5619,6 @@ This list contains 1464 plugins. Store data created during your pytest tests execution, and retrieve it at the end of the session, e.g. for applicative benchmarking purposes. - :pypi:`pytest-helm-chart` - *last release*: Jun 15, 2020, - *status*: 4 - Beta, - *requires*: pytest (>=5.4.2,<6.0.0) - - A plugin to provide different types and configs of Kubernetes clusters that can be used for testing. - :pypi:`pytest-helm-charts` *last release*: Feb 07, 2024, *status*: 4 - Beta, @@ -5676,9 +5711,9 @@ This list contains 1464 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: May 18, 2024, + *last release*: May 31, 2024, *status*: 3 - Alpha, - *requires*: pytest==8.1.1 + *requires*: pytest==8.2.0 Experimental package to automatically extract test plugins for Home Assistant custom components @@ -6467,9 +6502,9 @@ This list contains 1464 plugins. Alternate syntax for @pytest.mark.parametrize with test cases as dictionaries and default value fallbacks :pypi:`pytest-lambda` - *last release*: Aug 20, 2022, - *status*: 3 - Alpha, - *requires*: pytest (>=3.6,<8) + *last release*: May 27, 2024, + *status*: 5 - Production/Stable, + *requires*: pytest<9,>=3.6 Define pytest fixtures with lambda functions. @@ -6985,7 +7020,7 @@ This list contains 1464 plugins. Custom metrics report for pytest :pypi:`pytest-mh` - *last release*: Mar 14, 2024, + *last release*: May 28, 2024, *status*: N/A, *requires*: pytest @@ -7013,7 +7048,7 @@ This list contains 1464 plugins. A plugin to test mp :pypi:`pytest-minio-mock` - *last release*: May 19, 2024, + *last release*: May 26, 2024, *status*: N/A, *requires*: pytest>=5.0.0 @@ -7027,9 +7062,9 @@ This list contains 1464 plugins. Pytest plugin that creates missing fixtures :pypi:`pytest-mitmproxy` - *last release*: Mar 07, 2024, + *last release*: May 28, 2024, *status*: N/A, - *requires*: pytest >=7.0 + *requires*: pytest>=7.0 pytest plugin for mitmproxy tests @@ -7138,13 +7173,6 @@ This list contains 1464 plugins. Utility for adding additional properties to junit xml for IDM QE - :pypi:`pytest-modifyscope` - *last release*: Apr 12, 2020, - *status*: N/A, - *requires*: pytest - - pytest plugin to modify fixture scope - :pypi:`pytest-molecule` *last release*: Mar 29, 2022, *status*: 5 - Production/Stable, @@ -7615,9 +7643,9 @@ This list contains 1464 plugins. The ultimate pytest output plugin :pypi:`pytest-only` - *last release*: Mar 09, 2024, + *last release*: May 27, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (<7.1) ; python_full_version <= "3.6.0" + *requires*: pytest<9,>=3.6.0 Use @pytest.mark.only to run a single test @@ -8398,6 +8426,13 @@ This list contains 1464 plugins. Use ptera probes in tests + :pypi:`pytest-publish` + *last release*: Jun 01, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.0.0 + + + :pypi:`pytest-pudb` *last release*: Oct 25, 2018, *status*: 3 - Alpha, @@ -8601,8 +8636,15 @@ This list contains 1464 plugins. Pytest-pyvista package + :pypi:`pytest-qanova` + *last release*: May 26, 2024, + *status*: 3 - Alpha, + *requires*: pytest + + A pytest plugin to collect test information + :pypi:`pytest-qaseio` - *last release*: May 22, 2024, + *last release*: May 30, 2024, *status*: 4 - Beta, *requires*: pytest<9.0.0,>=7.2.2 @@ -9288,7 +9330,7 @@ This list contains 1464 plugins. pytest plugin for ROAST configuration override and fixtures :pypi:`pytest_robotframework` - *last release*: May 24, 2024, + *last release*: May 29, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -10072,7 +10114,7 @@ This list contains 1464 plugins. :pypi:`pytest-splunk-addon` - *last release*: May 16, 2024, + *last release*: May 27, 2024, *status*: N/A, *requires*: pytest<8,>5.4.0 @@ -10233,7 +10275,7 @@ This list contains 1464 plugins. A Pytest plugin that allows you to loop tests for a user defined amount of time. :pypi:`pytest-structlog` - *last release*: Mar 13, 2024, + *last release*: May 30, 2024, *status*: N/A, *requires*: pytest @@ -10527,9 +10569,9 @@ This list contains 1464 plugins. A Pytest plugin for running a subset of your tests by splitting them in to equally sized groups. :pypi:`pytest-testinfra` - *last release*: Feb 15, 2024, + *last release*: May 26, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=6 + *requires*: pytest>=6 Test infrastructures @@ -10729,6 +10771,13 @@ This list contains 1464 plugins. pytest-ligo + :pypi:`pytest-tf` + *last release*: May 29, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.2.1 + + Test your OpenTofu and Terraform config using a PyTest plugin + :pypi:`pytest-th2-bdd` *last release*: May 13, 2022, *status*: N/A, @@ -11437,7 +11486,7 @@ This list contains 1464 plugins. Welian API Automation test framework pytest plugin :pypi:`pytest-when` - *last release*: Mar 22, 2024, + *last release*: May 28, 2024, *status*: N/A, *requires*: pytest>=7.3.1 @@ -11604,6 +11653,13 @@ This list contains 1464 plugins. A package to prevent Dependency Confusion attacks against Yandex. + :pypi:`pytest-xstress` + *last release*: Jun 01, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.0.0 + + + :pypi:`pytest-xvfb` *last release*: May 29, 2023, *status*: 4 - Beta, From 17065cb008caaf26a48128fec1721c7f3d92b4c2 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 2 Jun 2024 16:57:50 +0300 Subject: [PATCH 061/199] cacheprovider: fix "Directory not empty" crash from cache directory creation Fix #12381 Test plan: It's possible to write a deterministic test case for this, but somewhat of a hassle so I tested it manually. I reproduced by removing existing `.pytest_cache`, adding a sleep before the rename and running two pytests. I verified that it doesn't reproduce after the fix. --- changelog/12381.bugfix.rst | 1 + src/_pytest/cacheprovider.py | 27 +++++++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 changelog/12381.bugfix.rst diff --git a/changelog/12381.bugfix.rst b/changelog/12381.bugfix.rst new file mode 100644 index 00000000000..02233cd4a84 --- /dev/null +++ b/changelog/12381.bugfix.rst @@ -0,0 +1 @@ +Fix possible "Directory not empty" crashes arising from concurent cache dir (``.pytest_cache``) creation. Regressed in pytest 8.2.0. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 5aa8f483522..7e00135a4c1 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -4,6 +4,7 @@ # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. import dataclasses +import errno import json import os from pathlib import Path @@ -227,14 +228,24 @@ def _ensure_cache_dir_and_supporting_files(self) -> None: with open(path.joinpath("CACHEDIR.TAG"), "xb") as f: f.write(CACHEDIR_TAG_CONTENT) - path.rename(self._cachedir) - # 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 - # https://github.com/python/cpython/issues/74168. Note that passing delete=False would - # do the wrong thing in case of errors and isn't supported until python 3.12. - path.mkdir() + try: + path.rename(self._cachedir) + except OSError as e: + # 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 https://github.com/python/cpython/issues/74168. Note that passing + # delete=False would do the wrong thing in case of errors and isn't supported + # until python 3.12. + path.mkdir() class LFPluginCollWrapper: From 1eee63a891d3598e6d3178c3c793eecdd5940b5c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 27 May 2024 00:44:05 +0300 Subject: [PATCH 062/199] fixtures: minor cleanups to reorder_items This makes some minor clarity and performance improvements to the code. --- src/_pytest/fixtures.py | 125 +++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index b7a58081ba8..37fc44ff9c9 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -21,6 +21,7 @@ from typing import Iterable from typing import Iterator from typing import List +from typing import Mapping from typing import MutableMapping from typing import NoReturn from typing import Optional @@ -161,6 +162,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) class FixtureArgKey: argname: str @@ -169,97 +176,91 @@ class FixtureArgKey: 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 ) -> Iterator[FixtureArgKey]: """Return list of keys for all parametrized arguments which match the specified scope.""" assert scope is not Scope.Function + try: callspec: CallSpec2 = item.callspec # type: ignore[attr-defined] except AttributeError: return + + item_cls = None + if scope is Scope.Session: + scoped_item_path = None + elif scope is Scope.Package: + # Package key = module's directory. + scoped_item_path = item.path.parent + elif scope is Scope.Module: + scoped_item_path = item.path + elif scope is Scope.Class: + scoped_item_path = item.path + item_cls = item.cls # type: ignore[attr-defined] + else: + assert_never(scope) + for argname in callspec.indices: if callspec._arg2scope[argname] != scope: continue - - item_cls = None - if scope is Scope.Session: - scoped_item_path = None - elif scope is Scope.Package: - # Package key = module's directory. - scoped_item_path = item.path.parent - elif scope is Scope.Module: - scoped_item_path = item.path - elif scope is Scope.Class: - scoped_item_path = item.path - item_cls = item.cls # type: ignore[attr-defined] - else: - assert_never(scope) - param_index = callspec.indices[argname] 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]: - 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]]] = {} 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) for item in items: - keys = dict.fromkeys(get_parametrized_fixture_keys(item, scope), None) - if keys: - scoped_argkeys_cache[item] = keys - for key in keys: - scoped_items_by_argkey[key].append(item) - items_dict = dict.fromkeys(items, None) + argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope)) + if argkeys: + scoped_argkeys_by_item[item] = argkeys + for argkey in argkeys: + scoped_items_by_argkey[argkey].append(item) + + items_set = dict.fromkeys(items) 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( - items: Dict[nodes.Item, None], - argkeys_cache: Dict[Scope, Dict[nodes.Item, Dict[FixtureArgKey, None]]], - items_by_argkey: Dict[Scope, Dict[FixtureArgKey, "Deque[nodes.Item]"]], + items: OrderedSet[nodes.Item], + argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]], + items_by_argkey: Mapping[Scope, Mapping[FixtureArgKey, "Deque[nodes.Item]"]], scope: Scope, -) -> Dict[nodes.Item, None]: +) -> OrderedSet[nodes.Item]: if scope is Scope.Function or len(items) < 3: 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_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: - no_argkey_group: Dict[nodes.Item, None] = {} + no_argkey_items: OrderedSet[nodes.Item] = {} slicing_argkey = None while items_deque: 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 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: - no_argkey_group[item] = None + no_argkey_items[item] = None else: slicing_argkey, _ = argkeys.popitem() # We don't have to remove relevant items from later in the @@ -268,16 +269,20 @@ def reorder_items_atscope( i for i in scoped_items_by_argkey[slicing_argkey] if i in items ] for i in reversed(matching_items): - fix_cache_order(i, argkeys_cache, items_by_argkey) items_deque.appendleft(i) + # Fix items_by_argkey order. + for other_scope in HIGH_SCOPES: + other_scoped_items_by_argkey = items_by_argkey[other_scope] + for argkey in argkeys_by_item[other_scope].get(i, ()): + other_scoped_items_by_argkey[argkey].appendleft(i) break - if no_argkey_group: - no_argkey_group = reorder_items_atscope( - no_argkey_group, argkeys_cache, items_by_argkey, scope.next_lower() + if no_argkey_items: + reordered_no_argkey_items = reorder_items_atscope( + no_argkey_items, argkeys_by_item, items_by_argkey, scope.next_lower() ) - for item in no_argkey_group: - items_done[item] = None - ignore.add(slicing_argkey) + items_done.update(reordered_no_argkey_items) + if slicing_argkey is not None: + ignore.add(slicing_argkey) return items_done From 7be95f9b30ffd418483bdb57112f9339465d4695 Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Mon, 3 Jun 2024 14:11:41 +0200 Subject: [PATCH 063/199] code: do not truncate args when running with -vvv (#12241) Related to #2871. --- AUTHORS | 1 + changelog/2871.improvement.rst | 1 + src/_pytest/_code/code.py | 12 +++++++++++- src/_pytest/nodes.py | 3 +++ testing/code/test_excinfo.py | 24 ++++++++++++++++++++++++ testing/test_assertion.py | 33 +++++++++++++++++++++++++++++++++ 6 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 changelog/2871.improvement.rst diff --git a/AUTHORS b/AUTHORS index 748b9bae2c8..0560caf721b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -279,6 +279,7 @@ Michael Droettboom Michael Goerz Michael Krebs Michael Seifert +Michael Vogt Michal Wajszczuk Michał Górny Michał Zięba diff --git a/changelog/2871.improvement.rst b/changelog/2871.improvement.rst new file mode 100644 index 00000000000..1ba399550c7 --- /dev/null +++ b/changelog/2871.improvement.rst @@ -0,0 +1 @@ +Do not truncate arguments to functions in output when running with `-vvv`. diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 4fb686a86e5..b1ef9fe227c 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -635,6 +635,7 @@ def getrepr( ] = True, funcargs: bool = False, truncate_locals: bool = True, + truncate_args: bool = True, chain: bool = True, ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: """Return str()able representation of this exception info. @@ -665,6 +666,9 @@ def getrepr( :param bool truncate_locals: 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: If chained exceptions in Python 3 should be shown. @@ -691,6 +695,7 @@ def getrepr( tbfilter=tbfilter, funcargs=funcargs, truncate_locals=truncate_locals, + truncate_args=truncate_args, chain=chain, ) return fmt.repr_excinfo(self) @@ -809,6 +814,7 @@ class FormattedExcinfo: tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True funcargs: bool = False truncate_locals: bool = True + truncate_args: bool = True chain: bool = True astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( default_factory=dict, init=False, repr=False @@ -839,7 +845,11 @@ def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: if self.funcargs: args = [] 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 None diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index 974d756a2be..e731019715c 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -448,6 +448,8 @@ def _repr_failure_py( else: 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. # 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. @@ -466,6 +468,7 @@ def _repr_failure_py( style=style, tbfilter=tbfilter, truncate_locals=truncate_locals, + truncate_args=truncate_args, ) def repr_failure( diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index b5474512987..f7f780e98f8 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -11,6 +11,7 @@ import sys import textwrap from typing import Any +from typing import cast from typing import TYPE_CHECKING import _pytest._code @@ -712,6 +713,29 @@ def test_repr_local_truncated(self) -> None: assert full_reprlocals.lines 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: mod = importasmod( """ diff --git a/testing/test_assertion.py b/testing/test_assertion.py index a8960436b55..726235999b4 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -2045,3 +2045,36 @@ def test_long_text_fail(): 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) From 3433c7adf52a79592438d7af0637ae905657ee56 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 4 Jun 2024 09:06:33 +0200 Subject: [PATCH 064/199] [pre-commit.ci] pre-commit autoupdate (#12413) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.5 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.5...v0.4.7) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 64bdbb36a94..6a9fde6a511 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.5" + rev: "v0.4.7" hooks: - id: ruff args: ["--fix"] From e89d23b24741c001e8651a77303992cfa41c1664 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sun, 2 Jun 2024 17:57:38 +0300 Subject: [PATCH 065/199] fixtures: fix catastrophic performance problem in `reorder_items` Fix #12355. In the issue, it was reported that the `reorder_items` has quadratic (or worse...) behavior with certain simple parametrizations. After some debugging I found that the problem happens because the "Fix items_by_argkey order" loop keeps adding the same item to the deque, and it reaches epic sizes which causes the slowdown. I don't claim to understand how the `reorder_items` algorithm works, but if as far as I understand, if an item already exists in the deque, the correct thing to do is to move it to the front. Since a deque doesn't have such an (efficient) operation, this switches to `OrderedDict` which can efficiently append from both sides, deduplicate and move to front. --- changelog/12355.bugfix.rst | 1 + src/_pytest/fixtures.py | 20 +++++++++++++------- testing/python/fixtures.py | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 7 deletions(-) create mode 100644 changelog/12355.bugfix.rst diff --git a/changelog/12355.bugfix.rst b/changelog/12355.bugfix.rst new file mode 100644 index 00000000000..1ce43e60ebd --- /dev/null +++ b/changelog/12355.bugfix.rst @@ -0,0 +1 @@ +Fix possible catastrophic performance slowdown on a certain parametrization pattern involving many higher-scoped parameters. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 37fc44ff9c9..353082c17e1 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -25,6 +25,7 @@ from typing import MutableMapping from typing import NoReturn from typing import Optional +from typing import OrderedDict from typing import overload from typing import Sequence from typing import Set @@ -77,8 +78,6 @@ if TYPE_CHECKING: - from typing import Deque - from _pytest.main import Session from _pytest.python import CallSpec2 from _pytest.python import Function @@ -215,16 +214,18 @@ def get_parametrized_fixture_argkeys( def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: 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: 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: argkeys = dict.fromkeys(get_parametrized_fixture_argkeys(item, scope)) if argkeys: scoped_argkeys_by_item[item] = argkeys for argkey in argkeys: - scoped_items_by_argkey[argkey].append(item) + scoped_items_by_argkey[argkey][item] = None items_set = dict.fromkeys(items) return list( @@ -237,7 +238,9 @@ def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: def reorder_items_atscope( items: OrderedSet[nodes.Item], argkeys_by_item: Mapping[Scope, Mapping[nodes.Item, OrderedSet[FixtureArgKey]]], - items_by_argkey: Mapping[Scope, Mapping[FixtureArgKey, "Deque[nodes.Item]"]], + items_by_argkey: Mapping[ + Scope, Mapping[FixtureArgKey, OrderedDict[nodes.Item, None]] + ], scope: Scope, ) -> OrderedSet[nodes.Item]: if scope is Scope.Function or len(items) < 3: @@ -274,7 +277,10 @@ def reorder_items_atscope( for other_scope in HIGH_SCOPES: other_scoped_items_by_argkey = items_by_argkey[other_scope] for argkey in argkeys_by_item[other_scope].get(i, ()): - other_scoped_items_by_argkey[argkey].appendleft(i) + other_scoped_items_by_argkey[argkey][i] = None + other_scoped_items_by_argkey[argkey].move_to_end( + i, last=False + ) break if no_argkey_items: reordered_no_argkey_items = reorder_items_atscope( diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index d54bcc4d4eb..d3cff38f977 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -2219,6 +2219,25 @@ def test_check(): reprec = pytester.inline_run("-s") 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: def test_parametrize(self, pytester: Pytester) -> None: From 4cd80e19c51c9cc57470b51d0c1c05d355f79556 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 4 Jun 2024 16:51:13 +0300 Subject: [PATCH 066/199] Merge pull request #12415 from pytest-dev/release-8.2.2 Prepare release 8.2.2 (cherry picked from commit f3a494cca3bf0491f7277abff229a1331382ae26) --- changelog/12290.doc.rst | 1 - changelog/12355.bugfix.rst | 1 - changelog/12356.doc.rst | 2 -- changelog/12363.doc.rst | 1 - changelog/12367.bugfix.rst | 1 - changelog/12381.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-8.2.2.rst | 19 +++++++++++++++++++ doc/en/builtin.rst | 4 ++-- doc/en/changelog.rst | 29 +++++++++++++++++++++++++++++ doc/en/example/parametrize.rst | 6 +++--- doc/en/example/pythoncollection.rst | 4 ++-- doc/en/getting-started.rst | 2 +- doc/en/how-to/fixtures.rst | 2 +- 14 files changed, 58 insertions(+), 16 deletions(-) delete mode 100644 changelog/12290.doc.rst delete mode 100644 changelog/12355.bugfix.rst delete mode 100644 changelog/12356.doc.rst delete mode 100644 changelog/12363.doc.rst delete mode 100644 changelog/12367.bugfix.rst delete mode 100644 changelog/12381.bugfix.rst create mode 100644 doc/en/announce/release-8.2.2.rst diff --git a/changelog/12290.doc.rst b/changelog/12290.doc.rst deleted file mode 100644 index 07fe3babc66..00000000000 --- a/changelog/12290.doc.rst +++ /dev/null @@ -1 +0,0 @@ -Updated Sphinx theme to use Furo instead of Flask, enabling Dark mode theme. diff --git a/changelog/12355.bugfix.rst b/changelog/12355.bugfix.rst deleted file mode 100644 index 1ce43e60ebd..00000000000 --- a/changelog/12355.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix possible catastrophic performance slowdown on a certain parametrization pattern involving many higher-scoped parameters. diff --git a/changelog/12356.doc.rst b/changelog/12356.doc.rst deleted file mode 100644 index 312c26d3298..00000000000 --- a/changelog/12356.doc.rst +++ /dev/null @@ -1,2 +0,0 @@ -Added a subsection to the documentation for debugging flaky tests to mention -lack of thread safety in pytest as a possible source of flakyness. diff --git a/changelog/12363.doc.rst b/changelog/12363.doc.rst deleted file mode 100644 index c657281babf..00000000000 --- a/changelog/12363.doc.rst +++ /dev/null @@ -1 +0,0 @@ -The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results. diff --git a/changelog/12367.bugfix.rst b/changelog/12367.bugfix.rst deleted file mode 100644 index e8bf2e4f155..00000000000 --- a/changelog/12367.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -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. diff --git a/changelog/12381.bugfix.rst b/changelog/12381.bugfix.rst deleted file mode 100644 index 02233cd4a84..00000000000 --- a/changelog/12381.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix possible "Directory not empty" crashes arising from concurent cache dir (``.pytest_cache``) creation. Regressed in pytest 8.2.0. diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 8a33f7fb57d..c65eb5f3613 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-8.2.2 release-8.2.1 release-8.2.0 release-8.1.2 diff --git a/doc/en/announce/release-8.2.2.rst b/doc/en/announce/release-8.2.2.rst new file mode 100644 index 00000000000..3b1d93bd08b --- /dev/null +++ b/doc/en/announce/release-8.2.2.rst @@ -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 diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 458253fabbb..8dfffb0828a 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:549 + cache -- .../_pytest/cacheprovider.py:560 Return a cache object that can persist state between testing sessions. 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`. - 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` object. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index ca7f5b999bd..3ab2307ee5d 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,35 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 8.2.2 (2024-06-04) +========================= + +Bug Fixes +--------- + +- `#12355 `_: Fix possible catastrophic performance slowdown on a certain parametrization pattern involving many higher-scoped parameters. + + +- `#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 `_: Fix possible "Directory not empty" crashes arising from concurent cache dir (``.pytest_cache``) creation. Regressed in pytest 8.2.0. + + + +Improved Documentation +---------------------- + +- `#12290 `_: Updated Sphinx theme to use Furo instead of Flask, enabling Dark mode theme. + + +- `#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 `_: The documentation webpages now links to a canonical version to reduce outdated documentation in search engine results. + + pytest 8.2.1 (2024-05-19) ========================= diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index 03f6852e5c0..d540bf08337 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -162,7 +162,7 @@ objects, they are still using the default pytest representation: rootdir: /home/sweet/project collected 8 items - + @@ -239,7 +239,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia rootdir: /home/sweet/project collected 4 items - + @@ -318,7 +318,7 @@ Let's first see how it looks like at collection time: rootdir: /home/sweet/project collected 2 items - + diff --git a/doc/en/example/pythoncollection.rst b/doc/en/example/pythoncollection.rst index aa9d05d7227..39b799ed934 100644 --- a/doc/en/example/pythoncollection.rst +++ b/doc/en/example/pythoncollection.rst @@ -152,7 +152,7 @@ The test collection would look like this: configfile: pytest.ini collected 2 items - + @@ -215,7 +215,7 @@ You can always peek at the collection tree without running tests like this: configfile: pytest.ini collected 3 items - + diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 94e0d80e656..85bee729ba1 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 8.2.1 + pytest 8.2.2 .. _`simpletest`: diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 6cc20c8c3e4..35c3238dea7 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1418,7 +1418,7 @@ Running the above tests results in the following test IDs being used: rootdir: /home/sweet/project collected 12 items - + From c9d8765381d71197fc981a9636828aea5063dc7f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 3 Jun 2024 19:16:56 +0300 Subject: [PATCH 067/199] fixtures: change `register_fixture` to not accept `scope=None` There is no reason to allow this. --- src/_pytest/fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 353082c17e1..c078898dd66 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1636,7 +1636,7 @@ def _register_fixture( func: "_FixtureFunc[object]", nodeid: Optional[str], scope: Union[ - Scope, _ScopeName, Callable[[str, Config], _ScopeName], None + Scope, _ScopeName, Callable[[str, Config], _ScopeName] ] = "function", params: Optional[Sequence[object]] = None, ids: Optional[ From 043ff9abc657124db3504d3604e16ddebfe6e28f Mon Sep 17 00:00:00 2001 From: holger krekel Date: Thu, 6 Jun 2024 12:24:31 +0200 Subject: [PATCH 068/199] Remove hp42 contact details from the documentation (#12427) --- doc/en/contact.rst | 7 ------- 1 file changed, 7 deletions(-) diff --git a/doc/en/contact.rst b/doc/en/contact.rst index beed10d7f27..68efef522ff 100644 --- a/doc/en/contact.rst +++ b/doc/en/contact.rst @@ -26,19 +26,12 @@ Contact channels `_, or `via Matrix `_). -- 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 .. _`old issue tracker`: https://bitbucket.org/hpk42/py-trunk/issues/ .. _`pytest discussions`: https://github.com/pytest-dev/pytest/discussions -.. _`merlinux.eu`: https://merlinux.eu/ - .. _`get an account`: .. _tetamap: https://tetamap.wordpress.com/ From 6b2daaa2e9c8c325bdcb0886fc90d2df198af405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20K=C5=82oczko?= <31284574+kloczek@users.noreply.github.com> Date: Thu, 6 Jun 2024 22:52:29 +0100 Subject: [PATCH 069/199] [pre-commit] Add pyupgrade back as a manual stage (#12418) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Launchable with ``pre-commit run --hook-stage manual pyupgrade -a`` --------- Signed-off-by: Tomasz Kłoczko Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 5 +++++ testing/test_unittest.py | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6a9fde6a511..fb583b11126 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -43,6 +43,11 @@ repos: - id: pyproject-fmt # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version additional_dependencies: ["tox>=4.9"] +- repo: https://github.com/asottile/pyupgrade + rev: v3.15.2 + hooks: + - id: pyupgrade + stages: [manual] - repo: local hooks: - id: pylint diff --git a/testing/test_unittest.py b/testing/test_unittest.py index c359715dc2e..9561cad5eb5 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -384,7 +384,7 @@ def test_hello(self): @pytest.mark.parametrize("type", ["Error", "Failure"]) def test_testcase_custom_exception_info(pytester: Pytester, type: str) -> None: pytester.makepyfile( - """ + f""" from typing import Generic, TypeVar from unittest import TestCase import pytest, _pytest._code @@ -413,7 +413,7 @@ def from_exc_info(cls, *args, **kwargs): def test_hello(self): pass - """.format(**locals()) + """ ) result = pytester.runpytest() result.stdout.fnmatch_lines( From f94109937123ff5a9c780a74ed043e9467c4f1f2 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 7 Jun 2024 02:32:33 -0400 Subject: [PATCH 070/199] Cleanup MockAwareDocTestFinder. (#12431) * Only rely on _find_lineno on Python 3.10 and earlier. * Update docstring to reflect current expectation. * Only rely on _find on Python 3.9 and earlier. * Mark line as uncovered. * Remove empty else block (implicit is better than explicit). Closes #12430 Closes #12432 --- src/_pytest/doctest.py | 72 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 2d7453b4ee0..23ad7a7a979 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -505,43 +505,46 @@ def collect(self) -> Iterable[DoctestItem]: import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): - """A hackish doctest finder that overrides stdlib internals to fix a stdlib bug. - - https://github.com/pytest-dev/pytest/issues/3456 - https://bugs.python.org/issue25532 - """ - - def _find_lineno(self, obj, source_lines): - """Doctest code does not take into account `@property`, this - is a hackish way to fix it. https://bugs.python.org/issue17446 - - Wrapped Doctests will need to be unwrapped so the correct - line number is returned. This will be reported upstream. #8796 - """ - if isinstance(obj, property): - obj = getattr(obj, "fget", obj) - - if hasattr(obj, "__wrapped__"): - # Get the main obj in case of it being wrapped - obj = inspect.unwrap(obj) - - # Type ignored because this is a private function. - return super()._find_lineno( # type:ignore[misc] - obj, - source_lines, - ) + if sys.version_info < (3, 11): + + def _find_lineno(self, obj, source_lines): + """On older Pythons, doctest code does not take into account + `@property`. https://github.com/python/cpython/issues/61648 + + Moreover, wrapped Doctests need to be unwrapped so the correct + line number is returned. #8796 + """ + if isinstance(obj, property): + obj = getattr(obj, "fget", obj) + + if hasattr(obj, "__wrapped__"): + # Get the main obj in case of it being wrapped + obj = inspect.unwrap(obj) - def _find( - self, tests, obj, name, module, source_lines, globs, seen - ) -> None: - 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 + return super()._find_lineno( # type:ignore[misc] + obj, + source_lines, ) + if sys.version_info < (3, 10): + + def _find( + self, tests, obj, name, module, source_lines, globs, seen + ) -> 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): + return # pragma: no cover + 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 + ) + if sys.version_info < (3, 13): def _from_module(self, module, object): @@ -556,9 +559,6 @@ def _from_module(self, module, object): # Type ignored because this is a private function. return super()._from_module(module, object) # type: ignore[misc] - else: # pragma: no cover - pass - try: module = self.obj except Collector.CollectError: From c07bbdfa5bc39b20ddc529fed83f3a405354ca75 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 4 Jun 2024 23:29:14 +0300 Subject: [PATCH 071/199] Avoid some TYPE_CHECKING It's better not to use it when possible. --- src/_pytest/_code/code.py | 12 ++++++------ src/_pytest/config/__init__.py | 17 ++++++++--------- src/_pytest/debugging.py | 14 +++++--------- src/_pytest/fixtures.py | 2 +- src/_pytest/main.py | 5 +++-- src/_pytest/mark/structures.py | 2 +- src/_pytest/nodes.py | 6 +++--- src/_pytest/stepwise.py | 7 +------ src/_pytest/unittest.py | 17 +++++++++-------- testing/code/test_excinfo.py | 6 +++--- 10 files changed, 40 insertions(+), 48 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b1ef9fe227c..b6e06340dbe 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -55,7 +55,7 @@ if sys.version_info < (3, 11): from exceptiongroup import BaseExceptionGroup -_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] +TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] class Code: @@ -628,7 +628,7 @@ def _getreprcrash(self) -> Optional["ReprFileLocation"]: def getrepr( self, showlocals: bool = False, - style: _TracebackStyle = "long", + style: TracebackStyle = "long", abspath: bool = False, tbfilter: Union[ bool, Callable[["ExceptionInfo[BaseException]"], Traceback] @@ -809,7 +809,7 @@ class FormattedExcinfo: fail_marker: ClassVar = "E" showlocals: bool = False - style: _TracebackStyle = "long" + style: TracebackStyle = "long" abspath: bool = True tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True funcargs: bool = False @@ -1174,7 +1174,7 @@ def toterminal(self, tw: TerminalWriter) -> None: class ReprTraceback(TerminalRepr): reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] extraline: Optional[str] - style: _TracebackStyle + style: TracebackStyle entrysep: ClassVar = "_ " @@ -1208,7 +1208,7 @@ def __init__(self, tblines: Sequence[str]) -> None: class ReprEntryNative(TerminalRepr): lines: Sequence[str] - style: ClassVar[_TracebackStyle] = "native" + style: ClassVar[TracebackStyle] = "native" def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) @@ -1220,7 +1220,7 @@ class ReprEntry(TerminalRepr): reprfuncargs: Optional["ReprFuncArgs"] reprlocals: Optional["ReprLocals"] reprfileloc: Optional["ReprFileLocation"] - style: _TracebackStyle + style: TracebackStyle def _write_entry_lines(self, tw: TerminalWriter) -> None: """Write the source code portions of a list of traceback entries with syntax highlighting. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index f3d3b3062b5..287fac463cb 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -54,7 +54,10 @@ import _pytest._code from _pytest._code import ExceptionInfo from _pytest._code import filter_traceback +from _pytest._code.code import TracebackStyle from _pytest._io import TerminalWriter +from _pytest.config.argparsing import Argument +from _pytest.config.argparsing import Parser import _pytest.deprecated import _pytest.hookspec from _pytest.outcomes import fail @@ -71,9 +74,7 @@ if TYPE_CHECKING: - from .argparsing import Argument - from .argparsing import Parser - from _pytest._code.code import _TracebackStyle + from _pytest.cacheprovider import Cache from _pytest.terminal import TerminalReporter @@ -1030,6 +1031,9 @@ class ArgsSource(enum.Enum): #: 'testpaths' configuration value. TESTPATHS = enum.auto() + # Set by cacheprovider plugin. + cache: Optional["Cache"] + def __init__( self, pluginmanager: PytestPluginManager, @@ -1091,11 +1095,6 @@ def __init__( self.args_source = Config.ArgsSource.ARGS self.args: List[str] = [] - if TYPE_CHECKING: - from _pytest.cacheprovider import Cache - - self.cache: Optional[Cache] = None - @property def rootpath(self) -> Path: """The path to the :ref:`rootdir `. @@ -1175,7 +1174,7 @@ def notify_exception( option: Optional[argparse.Namespace] = None, ) -> None: if option and getattr(option, "fulltrace", False): - style: _TracebackStyle = "long" + style: TracebackStyle = "long" else: style = "native" excrepr = excinfo.getrepr( diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 1338ef9f22c..eacb2836d6c 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -13,12 +13,12 @@ from typing import Optional from typing import Tuple from typing import Type -from typing import TYPE_CHECKING from typing import Union import unittest from _pytest import outcomes from _pytest._code import ExceptionInfo +from _pytest.capture import CaptureManager from _pytest.config import Config from _pytest.config import ConftestImportFailure from _pytest.config import hookimpl @@ -27,11 +27,7 @@ from _pytest.config.exceptions import UsageError from _pytest.nodes import Node from _pytest.reports import BaseReport - - -if TYPE_CHECKING: - from _pytest.capture import CaptureManager - from _pytest.runner import CallInfo +from _pytest.runner import CallInfo def _validate_usepdb_cls(value: str) -> Tuple[str, str]: @@ -310,7 +306,7 @@ def pytest_pyfunc_call(self, pyfuncitem) -> Generator[None, object, object]: 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 wrapper which actually enters pdb before calling the python function 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 # an exception if one of the kwargs to testfunction was called `func`. @functools.wraps(testfunction) - def wrapper(*args, **kwargs): + def wrapper(*args, **kwargs) -> None: func = functools.partial(testfunction, *args, **kwargs) _pdb.runcall(func) 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 the command line.""" if pyfuncitem.config.getvalue("trace"): diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 353082c17e1..93c8bc6697c 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -60,6 +60,7 @@ from _pytest.deprecated import check_ispytest from _pytest.deprecated import MARKED_FIXTURE from _pytest.deprecated import YIELD_FIXTURE +from _pytest.main import Session from _pytest.mark import Mark from _pytest.mark import ParameterSet from _pytest.mark.structures import MarkDecorator @@ -78,7 +79,6 @@ if TYPE_CHECKING: - from _pytest.main import Session from _pytest.python import CallSpec2 from _pytest.python import Function from _pytest.python import Metafunc diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 716d5cf783b..d200a6877ff 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -38,7 +38,6 @@ from _pytest.config import UsageError from _pytest.config.argparsing import Parser from _pytest.config.compat import PathAwareHookProxy -from _pytest.fixtures import FixtureManager from _pytest.outcomes import exit from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath @@ -55,6 +54,8 @@ if TYPE_CHECKING: from typing import Self + from _pytest.fixtures import FixtureManager + def pytest_addoption(parser: Parser) -> None: parser.addini( @@ -551,7 +552,7 @@ class Session(nodes.Collector): # Set on the session by runner.pytest_sessionstart. _setupstate: SetupState # Set on the session by fixtures.pytest_sessionstart. - _fixturemanager: FixtureManager + _fixturemanager: "FixtureManager" exitstatus: Union[int, ExitCode] def __init__(self, config: Config) -> None: diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 3567142a6d9..456808063ad 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -31,6 +31,7 @@ from _pytest.deprecated import check_ispytest from _pytest.deprecated import MARKED_FIXTURE from _pytest.outcomes import fail +from _pytest.scope import _ScopeName 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 # special privilege, and breaks modularity. But practicality beats purity... if TYPE_CHECKING: - from _pytest.scope import _ScopeName class _SkipMarkDecorator(MarkDecorator): @overload # type: ignore[override,no-overload-impl] diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index e731019715c..aad54f8b325 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -30,6 +30,7 @@ from _pytest._code.code import ExceptionInfo from _pytest._code.code import TerminalRepr from _pytest._code.code import Traceback +from _pytest._code.code import TracebackStyle from _pytest.compat import LEGACY_PATH from _pytest.config import Config from _pytest.config import ConftestImportFailure @@ -49,7 +50,6 @@ from typing import Self # Imported here due to circular import. - from _pytest._code.code import _TracebackStyle from _pytest.main import Session @@ -416,7 +416,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[_TracebackStyle]" = None, + style: "Optional[TracebackStyle]" = None, ) -> TerminalRepr: from _pytest.fixtures import FixtureLookupError @@ -474,7 +474,7 @@ def _repr_failure_py( def repr_failure( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[_TracebackStyle]" = None, + style: "Optional[TracebackStyle]" = None, ) -> Union[str, TerminalRepr]: """Return a representation of a collection or test failure. diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 92d3a297e0d..1e3a09d9678 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -1,18 +1,14 @@ from typing import List from typing import Optional -from typing import TYPE_CHECKING from _pytest import nodes +from _pytest.cacheprovider import Cache from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.main import Session from _pytest.reports import TestReport -import pytest -if TYPE_CHECKING: - from _pytest.cacheprovider import Cache - STEPWISE_CACHE_DIR = "cache/stepwise" @@ -37,7 +33,6 @@ def pytest_addoption(parser: Parser) -> None: ) -@pytest.hookimpl def pytest_configure(config: Config) -> None: if config.option.stepwise_skip: # allow --stepwise-skip to work on its own merits. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 643443f08c6..56a9c5d0976 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -41,10 +41,11 @@ import twisted.trial.unittest - _SysExcInfoType = Union[ - Tuple[Type[BaseException], BaseException, types.TracebackType], - Tuple[None, None, None], - ] + +_SysExcInfoType = Union[ + Tuple[Type[BaseException], BaseException, types.TracebackType], + Tuple[None, None, None], +] def pytest_pycollect_makeitem( @@ -228,7 +229,7 @@ def teardown(self) -> None: def startTest(self, testcase: "unittest.TestCase") -> None: pass - def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: + def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: # Unwrap potential exception info (see twisted trial support below). rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) try: @@ -264,7 +265,7 @@ def _addexcinfo(self, rawexcinfo: "_SysExcInfoType") -> None: self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError( - self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType ) -> None: try: if isinstance(rawexcinfo[1], exit.Exception): @@ -274,7 +275,7 @@ def addError( self._addexcinfo(rawexcinfo) def addFailure( - self, testcase: "unittest.TestCase", rawexcinfo: "_SysExcInfoType" + self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType ) -> None: self._addexcinfo(rawexcinfo) @@ -287,7 +288,7 @@ def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: def addExpectedFailure( self, testcase: "unittest.TestCase", - rawexcinfo: "_SysExcInfoType", + rawexcinfo: _SysExcInfoType, reason: str = "", ) -> None: try: diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index f7f780e98f8..fc60ae9ac99 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -28,7 +28,7 @@ if TYPE_CHECKING: - from _pytest._code.code import _TracebackStyle + from _pytest._code.code import TracebackStyle if sys.version_info < (3, 11): from exceptiongroup import ExceptionGroup @@ -925,7 +925,7 @@ def entry(): ) excinfo = pytest.raises(ValueError, mod.entry) - styles: tuple[_TracebackStyle, ...] = ("long", "short") + styles: tuple[TracebackStyle, ...] = ("long", "short") for style in styles: p = FormattedExcinfo(style=style) reprtb = p.repr_traceback(excinfo) @@ -1052,7 +1052,7 @@ def entry(): ) excinfo = pytest.raises(ValueError, mod.entry) - styles: tuple[_TracebackStyle, ...] = ("short", "long", "no") + styles: tuple[TracebackStyle, ...] = ("short", "long", "no") for style in styles: for showlocals in (True, False): repr = excinfo.getrepr(style=style, showlocals=showlocals) From 13f97632c64a733bb8b11c96b423b8480e99246c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Jun 2024 07:03:09 +0000 Subject: [PATCH 072/199] build(deps): Bump pytest-trio in /testing/plugins_integration Bumps [pytest-trio](https://github.com/python-trio/pytest-trio) from 0.7.0 to 0.8.0. - [Release notes](https://github.com/python-trio/pytest-trio/releases) - [Commits](https://github.com/python-trio/pytest-trio/compare/v0.7.0...v0.8.0) --- updated-dependencies: - dependency-name: pytest-trio dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index cf6e4763dc5..f44a02915f2 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -9,7 +9,7 @@ pytest-html==4.1.1 pytest-mock==3.14.0 pytest-rerunfailures==14.0 pytest-sugar==1.0.0 -pytest-trio==0.7.0 +pytest-trio==0.8.0 pytest-twisted==1.14.1 twisted==24.3.0 pytest-xvfb==3.0.0 From de47b73520fd9b7e41272701d7fd4663357af046 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Fri, 7 Jun 2024 09:49:29 +0300 Subject: [PATCH 073/199] unittest: fix assertion errors on unittest reruns This fixes unittest test reruns when using plugins like pytest-rerunfailures. The `instance` property uses AttributeError to check if the instance needs to be initialized, so `del` is the correct way to clear it, not setting to `None`. Regressed in 8.2.2. --- changelog/12424.bugfix.rst | 1 + src/_pytest/unittest.py | 2 +- .../pytest_rerunfailures_integration.py | 11 +++++++++++ tox.ini | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 changelog/12424.bugfix.rst create mode 100644 testing/plugins_integration/pytest_rerunfailures_integration.py diff --git a/changelog/12424.bugfix.rst b/changelog/12424.bugfix.rst new file mode 100644 index 00000000000..7ad1126858b --- /dev/null +++ b/changelog/12424.bugfix.rst @@ -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. diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 643443f08c6..ca82ac5c14a 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -222,7 +222,7 @@ def teardown(self) -> None: self._explicit_tearDown() self._explicit_tearDown = None self._obj = None - self._instance = None + del self._instance super().teardown() def startTest(self, testcase: "unittest.TestCase") -> None: diff --git a/testing/plugins_integration/pytest_rerunfailures_integration.py b/testing/plugins_integration/pytest_rerunfailures_integration.py new file mode 100644 index 00000000000..9a13a3279a9 --- /dev/null +++ b/testing/plugins_integration/pytest_rerunfailures_integration.py @@ -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() diff --git a/tox.ini b/tox.ini index 0a3f0acf5b8..35b335a015d 100644 --- a/tox.ini +++ b/tox.ini @@ -141,7 +141,7 @@ commands = pytest --cov=. simple_integration.py pytest --ds=django_settings 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_asyncio_integration.py pytest pytest_mock_integration.py From 9bfcca6f4838c854e526afb784820ec2af4f8ca1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 9 Jun 2024 22:49:33 -0300 Subject: [PATCH 074/199] [automated] Update plugin list (#12441) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 158 +++++++++++++++++++------------ 1 file changed, 99 insertions(+), 59 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 0f116277997..ab7d28edf0f 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =7.4.2,<8.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-attributes` A plugin that allows users to add attributes to their tests. These attributes can then be referenced by fixtures or the test itself. Jun 06, 2024 4 - Beta pytest>=6.2.0 :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 @@ -140,7 +141,7 @@ This list contains 1471 plugins. :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 Jan 31, 2024 5 - Production/Stable pytest>=7.0.0 :pypi:`pytest-batch-regression` A pytest plugin to repeat the entire test suite in batches. May 08, 2024 N/A pytest>=6.0.0 - :pypi:`pytest-bdd` BDD for pytest Mar 17, 2024 6 - Mature pytest (>=6.2.0) + :pypi:`pytest-bdd` BDD for pytest Jun 04, 2024 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 Dec 31, 2023 4 - Beta pytest >=5.0 :pypi:`pytest-bdd-report` A pytest-bdd plugin for generating useful and informative BDD test reports May 20, 2024 N/A pytest >=7.1.3 @@ -149,7 +150,7 @@ This list contains 1471 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests May 31, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 07, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -172,7 +173,7 @@ This list contains 1471 plugins. :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-boto-mock` Thin-wrapper around the mock package for easier use with pytest May 27, 2024 5 - Production/Stable pytest>=8.2.0 + :pypi:`pytest-boto-mock` Thin-wrapper around the mock package for easier use with pytest Jun 05, 2024 5 - Production/Stable pytest>=8.2.0 :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-bq` BigQuery fixtures and fixture factories for Pytest. May 08, 2024 5 - Production/Stable pytest>=6.2 :pypi:`pytest-bravado` Pytest-bravado automatically generates from OpenAPI specification client fixtures. Feb 15, 2022 N/A N/A @@ -185,7 +186,7 @@ This list contains 1471 plugins. :pypi:`pytest_browserstack` Py.test plugin for BrowserStack Jan 27, 2016 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 Sep 23, 2023 5 - Production/Stable pytest >=7.1.0 + :pypi:`pytest-bug` Pytest plugin for marking tests as a bug Jun 05, 2024 5 - Production/Stable pytest>=8.0.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) @@ -265,7 +266,7 @@ This list contains 1471 plugins. :pypi:`pytest-collect-pytest-interinfo` A simple plugin to use with pytest Sep 26, 2023 4 - Beta N/A :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-common-subject` pytest framework for testing different aspects of a common method Jun 03, 2024 N/A pytest<9,>=3.6 :pypi:`pytest-compare` pytest plugin for comparing call arguments. Jun 22, 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 @@ -285,7 +286,7 @@ This list contains 1471 plugins. :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 Jun 28, 2023 4 - Beta N/A - :pypi:`pytest-coveragemarkers` Using pytest markers to track functional coverage and filtering of tests Apr 15, 2024 N/A pytest<8.0.0,>=7.1.2 + :pypi:`pytest-coveragemarkers` Using pytest markers to track functional coverage and filtering of tests Jun 04, 2024 N/A pytest<8.0.0,>=7.1.2 :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_covid` Too many faillure, less tests. Jun 24, 2020 N/A N/A :pypi:`pytest-cpp` Use pytest's runner to discover and execute C++ tests Nov 01, 2023 5 - Production/Stable pytest >=7.0 @@ -335,7 +336,7 @@ This list contains 1471 plugins. :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. Aug 25, 2023 N/A pytest >=6.2.5 ; extra == 'test' + :pypi:`pytest-dbt-core` Pytest extension for dbt. Jun 04, 2024 N/A pytest>=6.2.5; extra == "test" :pypi:`pytest-dbt-postgres` Pytest tooling to unittest DBT & Postgres models Jan 02, 2024 N/A pytest (>=7.4.3,<8.0.0) :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) @@ -442,6 +443,7 @@ This list contains 1471 plugins. :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 Dec 05, 2023 5 - Production/Stable pytest >=2.2 + :pypi:`pytest-edit` Edit the source code of a failed test with \`pytest --edit\`. Jun 07, 2024 N/A pytest :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 15, 2024 5 - Production/Stable pytest >=7.0 :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) @@ -484,7 +486,7 @@ This list contains 1471 plugins. :pypi:`pytest_exact_fixtures` Parse queries in Lucene and Elasticsearch syntaxes Feb 04, 2019 N/A N/A :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7 :pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9) - :pypi:`pytest-exasol-saas` May 27, 2024 N/A pytest<9,>=7 + :pypi:`pytest-exasol-saas` Jun 07, 2024 N/A pytest<9,>=7 :pypi:`pytest-excel` pytest plugin for generating excel reports Sep 14, 2023 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 @@ -515,7 +517,7 @@ This list contains 1471 plugins. :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 Feb 11, 2024 N/A pytest>=7.0 + :pypi:`pytest-fail-slow` Fail tests that take too long to run Jun 01, 2024 N/A pytest>=7.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` A package to prevent Dependency Confusion attacks against Yandex. Feb 21, 2024 N/A N/A @@ -561,7 +563,7 @@ This list contains 1471 plugins. :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 May 15, 2024 4 - Beta pytest>=7.0.0 + :pypi:`pytest-fluent` A pytest plugin in order to provide logs via fluentd Jun 05, 2024 4 - Beta pytest>=7.0.0 :pypi:`pytest-fluentbit` A pytest plugin in order to provide logs via fluentbit Jun 16, 2023 4 - Beta pytest (>=7.0.0) :pypi:`pytest-fly` pytest observer Apr 14, 2024 3 - Alpha pytest :pypi:`pytest-flyte` Pytest fixtures for simplifying Flyte integration testing May 03, 2021 N/A pytest @@ -628,13 +630,13 @@ This list contains 1471 plugins. :pypi:`pytest-helpers-namespace` Pytest Helpers Namespace Plugin Dec 29, 2021 5 - Production/Stable pytest (>=6.0.0) :pypi:`pytest-henry` Aug 29, 2023 N/A N/A :pypi:`pytest-hidecaptured` Hide captured output May 04, 2018 4 - Beta pytest (>=2.8.5) - :pypi:`pytest-himark` This plugin aims to create markers automatically based on a json configuration. May 15, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-himark` This plugin aims to create markers automatically based on a json configuration. Jun 05, 2024 4 - Beta pytest>=6.2.0 :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-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 May 31, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 08, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -703,7 +705,7 @@ This list contains 1471 plugins. :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-ipywidgets` Apr 08, 2024 N/A pytest + :pypi:`pytest-ipywidgets` Jun 06, 2024 N/A pytest :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest :pypi:`pytest-isort` py.test plugin to check import ordering using isort Mar 05, 2024 5 - Production/Stable pytest (>=5.0) :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 29, 2024 4 - Beta N/A @@ -715,7 +717,7 @@ This list contains 1471 plugins. :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 Apr 30, 2024 3 - Alpha N/A - :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked May 16, 2024 N/A pytest>=7.2.0 + :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jun 06, 2024 N/A pytest>=7.2.0 :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Mar 27, 2024 4 - Beta pytest>=6.2.4 :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 @@ -728,7 +730,7 @@ This list contains 1471 plugins. :pypi:`pytest-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0 :pypi:`pytest-jsonschema` A pytest plugin to perform JSONSchema validations Mar 27, 2024 4 - Beta pytest>=6.2.0 :pypi:`pytest-jtl-collector` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1 - :pypi:`pytest-jtr` pytest plugin supporting json test report output Apr 15, 2024 N/A pytest<8.0.0,>=7.1.2 + :pypi:`pytest-jtr` pytest plugin supporting json test report output Jun 04, 2024 N/A pytest<8.0.0,>=7.1.2 :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Apr 04, 2024 4 - Beta pytest>=7.0 :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 @@ -798,6 +800,7 @@ This list contains 1471 plugins. :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-manage` 用例标签化管理 Jun 07, 2024 N/A pytest :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` Easy way to match captured \`pytest\` output against expectations stored in files Mar 15, 2024 5 - Production/Stable pytest @@ -913,7 +916,7 @@ This list contains 1471 plugins. :pypi:`pytest-only` Use @pytest.mark.only to run a single test May 27, 2024 5 - Production/Stable pytest<9,>=3.6.0 :pypi:`pytest-oof` A Pytest plugin providing structured, programmatic access to a test run's results Dec 11, 2023 4 - Beta N/A :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-openfiles` Pytest plugin for detecting inadvertent open file handles Jun 05, 2024 3 - Alpha pytest>=4.6 :pypi:`pytest-opentelemetry` A pytest plugin for instrumenting test runs via OpenTelemetry Oct 01, 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 @@ -1022,7 +1025,7 @@ This list contains 1471 plugins. :pypi:`pytest-psqlgraph` pytest plugin for testing applications that use psqlgraph Oct 19, 2021 4 - Beta pytest (>=6.0) :pypi:`pytest-pt` pytest plugin to use \*.pt files as tests May 15, 2024 4 - Beta pytest :pypi:`pytest-ptera` Use ptera probes in tests Mar 01, 2022 N/A pytest (>=6.2.4,<7.0.0) - :pypi:`pytest-publish` Jun 01, 2024 N/A pytest<9.0.0,>=8.0.0 + :pypi:`pytest-publish` Jun 04, 2024 N/A pytest<9.0.0,>=8.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 @@ -1077,13 +1080,13 @@ This list contains 1471 plugins. :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 Jan 20, 2024 5 - Production/Stable pytest >=3.0.0 - :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection May 12, 2024 4 - Beta pytest>=7.4.3 + :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection Jun 07, 2024 4 - Beta pytest>=7.4.3 :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A :pypi:`pytest-reana` Pytest fixtures for REANA. Mar 14, 2024 3 - Alpha N/A :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 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-redis` Redis fixtures and fixture factories for Pytest. Jun 05, 2024 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) @@ -1151,7 +1154,7 @@ This list contains 1471 plugins. :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_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them May 29, 2024 N/A pytest<9,>=7 + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them Jun 07, 2024 N/A pytest<9,>=7 :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) @@ -1176,7 +1179,7 @@ This list contains 1471 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. May 16, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jun 07, 2024 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-scenario-files` A pytest plugin that generates unit test scenarios from data files. May 19, 2024 5 - Production/Stable pytest>=7.2.0 :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A @@ -1186,7 +1189,7 @@ This list contains 1471 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. May 16, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jun 07, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -1263,8 +1266,8 @@ This list contains 1471 plugins. :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 May 27, 2024 N/A pytest<8,>5.4.0 - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX May 10, 2024 N/A N/A + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jun 03, 2024 N/A pytest<8,>5.4.0 + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 05, 2024 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 @@ -1286,7 +1289,7 @@ This list contains 1471 plugins. :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A :pypi:`pytest-store` Pytest plugin to store values from test runs Nov 16, 2023 3 - Alpha pytest (>=7.0.0) :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 May 30, 2024 N/A pytest + :pypi:`pytest-structlog` Structured logging assertions Jun 08, 2024 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) @@ -1386,8 +1389,9 @@ This list contains 1471 plugins. :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-toolkit` Useful utils for testing Apr 13, 2024 N/A N/A + :pypi:`pytest-toolkit` Useful utils for testing Jun 07, 2024 N/A N/A :pypi:`pytest-tools` Pytest tools Oct 21, 2022 4 - Beta N/A + :pypi:`pytest-topo` Topological sorting for pytest Jun 05, 2024 N/A pytest>=7.0.0 :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 @@ -1498,6 +1502,7 @@ This list contains 1471 plugins. :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-zcc` eee Jun 02, 2024 N/A N/A :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jan 08, 2024 5 - Production/Stable pytest (>=4.5.0) :pypi:`pytest-zeebe` Pytest fixtures for testing Camunda 8 processes using a Zeebe test engine. Feb 01, 2024 N/A pytest (>=7.4.2,<8.0.0) :pypi:`pytest-zest` Zesty additions to pytest. Nov 17, 2022 N/A N/A @@ -2091,6 +2096,13 @@ This list contains 1471 plugins. pytest plugin to select tests based on attributes similar to the nose-attrib plugin + :pypi:`pytest-attributes` + *last release*: Jun 06, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin that allows users to add attributes to their tests. These attributes can then be referenced by fixtures or the test itself. + :pypi:`pytest-austin` *last release*: Oct 11, 2020, *status*: 4 - Beta, @@ -2253,9 +2265,9 @@ This list contains 1471 plugins. A pytest plugin to repeat the entire test suite in batches. :pypi:`pytest-bdd` - *last release*: Mar 17, 2024, + *last release*: Jun 04, 2024, *status*: 6 - Mature, - *requires*: pytest (>=6.2.0) + *requires*: pytest>=6.2.0 BDD for pytest @@ -2316,7 +2328,7 @@ This list contains 1471 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: May 31, 2024, + *last release*: Jun 07, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2477,7 +2489,7 @@ This list contains 1471 plugins. :pypi:`pytest-boto-mock` - *last release*: May 27, 2024, + *last release*: Jun 05, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=8.2.0 @@ -2568,9 +2580,9 @@ This list contains 1471 plugins. Budo Systems is a martial arts school management system. This module is the Budo Systems Pytest Plugin. :pypi:`pytest-bug` - *last release*: Sep 23, 2023, + *last release*: Jun 05, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=7.1.0 + *requires*: pytest>=8.0.0 Pytest plugin for marking tests as a bug @@ -3128,9 +3140,9 @@ This list contains 1471 plugins. An interactive GUI test runner for PyTest :pypi:`pytest-common-subject` - *last release*: May 15, 2022, + *last release*: Jun 03, 2024, *status*: N/A, - *requires*: pytest (>=3.6,<8) + *requires*: pytest<9,>=3.6 pytest framework for testing different aspects of a common method @@ -3268,7 +3280,7 @@ This list contains 1471 plugins. Coverage dynamic context support for PyTest, including sub-processes :pypi:`pytest-coveragemarkers` - *last release*: Apr 15, 2024, + *last release*: Jun 04, 2024, *status*: N/A, *requires*: pytest<8.0.0,>=7.1.2 @@ -3618,9 +3630,9 @@ This list contains 1471 plugins. A pytest plugin for linting a dbt project's conventions :pypi:`pytest-dbt-core` - *last release*: Aug 25, 2023, + *last release*: Jun 04, 2024, *status*: N/A, - *requires*: pytest >=6.2.5 ; extra == 'test' + *requires*: pytest>=6.2.5; extra == "test" Pytest extension for dbt. @@ -4366,6 +4378,13 @@ This list contains 1471 plugins. pytest plugin with mechanisms for echoing environment variables, package version and generic attributes + :pypi:`pytest-edit` + *last release*: Jun 07, 2024, + *status*: N/A, + *requires*: pytest + + Edit the source code of a failed test with \`pytest --edit\`. + :pypi:`pytest-ekstazi` *last release*: Sep 10, 2022, *status*: N/A, @@ -4661,7 +4680,7 @@ This list contains 1471 plugins. :pypi:`pytest-exasol-saas` - *last release*: May 27, 2024, + *last release*: Jun 07, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -4878,7 +4897,7 @@ This list contains 1471 plugins. A pytest plugin that helps better distinguishing real test failures from setup flakiness. :pypi:`pytest-fail-slow` - *last release*: Feb 11, 2024, + *last release*: Jun 01, 2024, *status*: N/A, *requires*: pytest>=7.0 @@ -5200,7 +5219,7 @@ This list contains 1471 plugins. :pypi:`pytest-fluent` - *last release*: May 15, 2024, + *last release*: Jun 05, 2024, *status*: 4 - Beta, *requires*: pytest>=7.0.0 @@ -5669,7 +5688,7 @@ This list contains 1471 plugins. Hide captured output :pypi:`pytest-himark` - *last release*: May 15, 2024, + *last release*: Jun 05, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 @@ -5711,7 +5730,7 @@ This list contains 1471 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: May 31, 2024, + *last release*: Jun 08, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 @@ -6194,7 +6213,7 @@ This list contains 1471 plugins. THIS PROJECT IS ABANDONED :pypi:`pytest-ipywidgets` - *last release*: Apr 08, 2024, + *last release*: Jun 06, 2024, *status*: N/A, *requires*: pytest @@ -6278,7 +6297,7 @@ This list contains 1471 plugins. py.test JIRA integration plugin, using markers :pypi:`pytest-jira-xfail` - *last release*: May 16, 2024, + *last release*: Jun 06, 2024, *status*: N/A, *requires*: pytest>=7.2.0 @@ -6369,7 +6388,7 @@ This list contains 1471 plugins. A simple plugin to use with pytest :pypi:`pytest-jtr` - *last release*: Apr 15, 2024, + *last release*: Jun 04, 2024, *status*: N/A, *requires*: pytest<8.0.0,>=7.1.2 @@ -6858,6 +6877,13 @@ This list contains 1471 plugins. UNKNOWN + :pypi:`pytest-mark-manage` + *last release*: Jun 07, 2024, + *status*: N/A, + *requires*: pytest + + 用例标签化管理 + :pypi:`pytest-mark-no-py3` *last release*: May 17, 2019, *status*: N/A, @@ -7664,9 +7690,9 @@ This list contains 1471 plugins. Run object-oriented tests in a simple format :pypi:`pytest-openfiles` - *last release*: Apr 16, 2020, + *last release*: Jun 05, 2024, *status*: 3 - Alpha, - *requires*: pytest (>=4.6) + *requires*: pytest>=4.6 Pytest plugin for detecting inadvertent open file handles @@ -8427,7 +8453,7 @@ This list contains 1471 plugins. Use ptera probes in tests :pypi:`pytest-publish` - *last release*: Jun 01, 2024, + *last release*: Jun 04, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.0.0 @@ -8812,7 +8838,7 @@ This list contains 1471 plugins. Randomise the order in which pytest tests are run with some control over the randomness :pypi:`pytest-ranking` - *last release*: May 12, 2024, + *last release*: Jun 07, 2024, *status*: 4 - Beta, *requires*: pytest>=7.4.3 @@ -8854,9 +8880,9 @@ This list contains 1471 plugins. Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal :pypi:`pytest-redis` - *last release*: Apr 19, 2023, + *last release*: Jun 05, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=6.2) + *requires*: pytest>=6.2 Redis fixtures and fixture factories for Pytest. @@ -9330,7 +9356,7 @@ This list contains 1471 plugins. pytest plugin for ROAST configuration override and fixtures :pypi:`pytest_robotframework` - *last release*: May 29, 2024, + *last release*: Jun 07, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -9505,7 +9531,7 @@ This list contains 1471 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: May 16, 2024, + *last release*: Jun 07, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9575,7 +9601,7 @@ This list contains 1471 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: May 16, 2024, + *last release*: Jun 07, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -10114,14 +10140,14 @@ This list contains 1471 plugins. :pypi:`pytest-splunk-addon` - *last release*: May 27, 2024, + *last release*: Jun 03, 2024, *status*: N/A, *requires*: pytest<8,>5.4.0 A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: May 10, 2024, + *last release*: Jun 05, 2024, *status*: N/A, *requires*: N/A @@ -10275,7 +10301,7 @@ This list contains 1471 plugins. A Pytest plugin that allows you to loop tests for a user defined amount of time. :pypi:`pytest-structlog` - *last release*: May 30, 2024, + *last release*: Jun 08, 2024, *status*: N/A, *requires*: pytest @@ -10975,7 +11001,7 @@ This list contains 1471 plugins. Numerous useful plugins for pytest. :pypi:`pytest-toolkit` - *last release*: Apr 13, 2024, + *last release*: Jun 07, 2024, *status*: N/A, *requires*: N/A @@ -10988,6 +11014,13 @@ This list contains 1471 plugins. Pytest tools + :pypi:`pytest-topo` + *last release*: Jun 05, 2024, + *status*: N/A, + *requires*: pytest>=7.0.0 + + Topological sorting for pytest + :pypi:`pytest-tornado` *last release*: Jun 17, 2020, *status*: 5 - Production/Stable, @@ -11758,6 +11791,13 @@ This list contains 1471 plugins. OWASP ZAP plugin for py.test. + :pypi:`pytest-zcc` + *last release*: Jun 02, 2024, + *status*: N/A, + *requires*: N/A + + eee + :pypi:`pytest-zebrunner` *last release*: Jan 08, 2024, *status*: 5 - Production/Stable, From 3d91e422293c9fa811348a9f4602632a66d758eb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 08:28:57 +0200 Subject: [PATCH 075/199] build(deps): Bump pytest-bdd in /testing/plugins_integration (#12442) Bumps [pytest-bdd](https://github.com/pytest-dev/pytest-bdd) from 7.1.2 to 7.2.0. - [Release notes](https://github.com/pytest-dev/pytest-bdd/releases) - [Changelog](https://github.com/pytest-dev/pytest-bdd/blob/master/CHANGES.rst) - [Commits](https://github.com/pytest-dev/pytest-bdd/compare/7.1.2...7.2.0) --- updated-dependencies: - dependency-name: pytest-bdd dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index f44a02915f2..b3ced6e5c9d 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,7 +1,7 @@ anyio[curio,trio]==4.4.0 django==5.0.6 pytest-asyncio==0.23.7 -pytest-bdd==7.1.2 +pytest-bdd==7.2.0 pytest-cov==5.0.0 pytest-django==4.8.0 pytest-flakes==4.0.5 From 67a570aea9e16ecf2694cea86bec326ce9056f9f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jun 2024 11:38:33 +0300 Subject: [PATCH 076/199] terminalwriter: factor out pygments lexer & formatter selection to own functions We intend to add some more logic here. --- src/_pytest/_io/terminalwriter.py | 116 ++++++++++++++++++------------ 1 file changed, 70 insertions(+), 46 deletions(-) diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 5bcd0592778..26344fd315a 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -8,11 +8,17 @@ from typing import Optional from typing import Sequence from typing import TextIO +from typing import TYPE_CHECKING from ..compat import assert_never from .wcwidth import wcswidth +if TYPE_CHECKING: + from pygments.formatter import Formatter + from pygments.lexer import Lexer + + # This code was initially copied from py 1.8.1, file _io/terminalwriter.py. @@ -194,58 +200,76 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No for indent, new_line in zip(indents, new_lines): self.line(indent + new_line) + def _get_pygments_lexer( + self, lexer: Literal["python", "diff"] + ) -> Optional["Lexer"]: + try: + if lexer == "python": + from pygments.lexers.python import PythonLexer + + return PythonLexer() + elif lexer == "diff": + from pygments.lexers.diff import DiffLexer + + return DiffLexer() + else: + assert_never(lexer) + except ModuleNotFoundError: + return None + + def _get_pygments_formatter(self) -> Optional["Formatter"]: + try: + import pygments.util + except ModuleNotFoundError: + return None + + from _pytest.config.exceptions import UsageError + + theme = os.getenv("PYTEST_THEME") + theme_mode = os.getenv("PYTEST_THEME_MODE", "dark") + + try: + from pygments.formatters.terminal import TerminalFormatter + + return TerminalFormatter(bg=theme_mode, style=theme) + + except pygments.util.ClassNotFound as e: + raise UsageError( + f"PYTEST_THEME environment variable had an invalid value: '{theme}'. " + "Only valid pygment styles are allowed." + ) from e + except pygments.util.OptionError as e: + raise UsageError( + f"PYTEST_THEME_MODE environment variable had an invalid value: '{theme_mode}'. " + "The only allowed values are 'dark' and 'light'." + ) from e + def _highlight( self, source: str, lexer: Literal["diff", "python"] = "python" ) -> str: """Highlight the given source if we have markup support.""" - from _pytest.config.exceptions import UsageError - if not source or not self.hasmarkup or not self.code_highlight: return source - try: - from pygments.formatters.terminal import TerminalFormatter + pygments_lexer = self._get_pygments_lexer(lexer) + if pygments_lexer is None: + return source - if lexer == "python": - from pygments.lexers.python import PythonLexer as Lexer - elif lexer == "diff": - from pygments.lexers.diff import DiffLexer as Lexer - else: - assert_never(lexer) - from pygments import highlight - import pygments.util - except ImportError: + pygments_formatter = self._get_pygments_formatter() + if pygments_formatter is None: return source - else: - try: - highlighted: str = highlight( - source, - Lexer(), - TerminalFormatter( - bg=os.getenv("PYTEST_THEME_MODE", "dark"), - style=os.getenv("PYTEST_THEME"), - ), - ) - # pygments terminal formatter may add a newline when there wasn't one. - # We don't want this, remove. - if highlighted[-1] == "\n" and source[-1] != "\n": - highlighted = highlighted[:-1] - - # Some lexers will not set the initial color explicitly - # which may lead to the previous color being propagated to the - # start of the expression, so reset first. - return "\x1b[0m" + highlighted - except pygments.util.ClassNotFound as e: - raise UsageError( - "PYTEST_THEME environment variable had an invalid value: '{}'. " - "Only valid pygment styles are allowed.".format( - os.getenv("PYTEST_THEME") - ) - ) from e - except pygments.util.OptionError as e: - raise UsageError( - "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " - "The only allowed values are 'dark' and 'light'.".format( - os.getenv("PYTEST_THEME_MODE") - ) - ) from e + + from pygments import highlight + + highlighted: str = highlight(source, pygments_lexer, pygments_formatter) + # pygments terminal formatter may add a newline when there wasn't one. + # We don't want this, remove. + if highlighted[-1] == "\n" and source[-1] != "\n": + highlighted = highlighted[:-1] + + # Some lexers will not set the initial color explicitly + # which may lead to the previous color being propagated to the + # start of the expression, so reset first. + highlighted = "\x1b[0m" + highlighted + + return highlighted From 7ef9da1f02f517d5db758117439edf289087cd7f Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 10 Jun 2024 11:41:10 +0300 Subject: [PATCH 077/199] terminalwriter: improve `PYTEST_THEME`, `PYTEST_THEME_MODE` usage errors --- src/_pytest/_io/terminalwriter.py | 8 ++++---- testing/test_terminal.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 26344fd315a..083c18232ff 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -235,13 +235,13 @@ def _get_pygments_formatter(self) -> Optional["Formatter"]: except pygments.util.ClassNotFound as e: raise UsageError( - f"PYTEST_THEME environment variable had an invalid value: '{theme}'. " - "Only valid pygment styles are allowed." + f"PYTEST_THEME environment variable has an invalid value: '{theme}'. " + "Hint: See available pygments styles with `pygmentize -L styles`." ) from e except pygments.util.OptionError as e: raise UsageError( - f"PYTEST_THEME_MODE environment variable had an invalid value: '{theme_mode}'. " - "The only allowed values are 'dark' and 'light'." + f"PYTEST_THEME_MODE environment variable has an invalid value: '{theme_mode}'. " + "The allowed values are 'dark' (default) and 'light'." ) from e def _highlight( diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 6cd4a1827d2..ce9fdc50c8a 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2609,8 +2609,8 @@ def test_foo(): monkeypatch.setenv("PYTEST_THEME", "invalid") result = pytester.runpytest_subprocess("--color=yes") result.stderr.fnmatch_lines( - "ERROR: PYTEST_THEME environment variable had an invalid value: 'invalid'. " - "Only valid pygment styles are allowed." + "ERROR: PYTEST_THEME environment variable has an invalid value: 'invalid'. " + "Hint: See available pygments styles with `pygmentize -L styles`." ) def test_code_highlight_invalid_theme_mode( @@ -2625,8 +2625,8 @@ def test_foo(): monkeypatch.setenv("PYTEST_THEME_MODE", "invalid") result = pytester.runpytest_subprocess("--color=yes") result.stderr.fnmatch_lines( - "ERROR: PYTEST_THEME_MODE environment variable had an invalid value: 'invalid'. " - "The only allowed values are 'dark' and 'light'." + "ERROR: PYTEST_THEME_MODE environment variable has an invalid value: 'invalid'. " + "The allowed values are 'dark' (default) and 'light'." ) From 23ca9798f7a119615017c177fc884e2f77c23abc Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 11 Jun 2024 18:36:20 +0300 Subject: [PATCH 078/199] doc: fix broken code blocks (#12449) Caused by 4588653b2497ed25976b7aaff225b889fb476756. The issue was fixed in https://github.com/astral-sh/ruff/issues/11577, so won't trigger again. Fix #12437. --- src/_pytest/capture.py | 4 ++++ src/_pytest/config/__init__.py | 1 + src/_pytest/monkeypatch.py | 1 + src/_pytest/pytester.py | 3 +++ 4 files changed, 9 insertions(+) diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 198d4195020..89a938d5416 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -983,6 +983,7 @@ def capsys(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: Returns an instance of :class:`CaptureFixture[str] `. Example: + .. code-block:: python def test_output(capsys): @@ -1010,6 +1011,7 @@ def capsysbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, Returns an instance of :class:`CaptureFixture[bytes] `. Example: + .. code-block:: python def test_output(capsysbinary): @@ -1037,6 +1039,7 @@ def capfd(request: SubRequest) -> Generator[CaptureFixture[str], None, None]: Returns an instance of :class:`CaptureFixture[str] `. Example: + .. code-block:: python def test_system_echo(capfd): @@ -1064,6 +1067,7 @@ def capfdbinary(request: SubRequest) -> Generator[CaptureFixture[bytes], None, N Returns an instance of :class:`CaptureFixture[bytes] `. Example: + .. code-block:: python def test_system_echo(capfdbinary): diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 287fac463cb..058aaa1ff30 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1735,6 +1735,7 @@ def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: can be used to explicitly use the global verbosity level. Example: + .. code-block:: ini # content of pytest.ini diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index 3f398df76b1..f498d60df14 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -142,6 +142,7 @@ def context(cls) -> Generator["MonkeyPatch", None, None]: which undoes any patching done inside the ``with`` block upon exit. Example: + .. code-block:: python import functools diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index f9ab007a4d1..42f50900ada 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -805,6 +805,7 @@ def makefile(self, ext: str, *args: str, **kwargs: str) -> Path: The first created file. Examples: + .. code-block:: python pytester.makefile(".txt", "line1", "line2") @@ -858,6 +859,7 @@ def makepyfile(self, *args, **kwargs) -> Path: existing files. Examples: + .. code-block:: python def test_something(pytester): @@ -877,6 +879,7 @@ def maketxtfile(self, *args, **kwargs) -> Path: existing files. Examples: + .. code-block:: python def test_something(pytester): From ff7598013581b5820dd83253df7d0cc46f785020 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 12:36:39 -0300 Subject: [PATCH 079/199] [pre-commit.ci] pre-commit autoupdate (#12447) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [pre-commit.ci] pre-commit autoupdate updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.7 → v0.4.8](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.7...v0.4.8) - [github.com/asottile/pyupgrade: v3.15.2 → v3.16.0](https://github.com/asottile/pyupgrade/compare/v3.15.2...v3.16.0) * Apply pyupgrade automatically --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 4 ++-- src/_pytest/cacheprovider.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fb583b11126..42fbc31ea12 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.7" + rev: "v0.4.8" hooks: - id: ruff args: ["--fix"] @@ -44,7 +44,7 @@ repos: # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version additional_dependencies: ["tox>=4.9"] - repo: https://github.com/asottile/pyupgrade - rev: v3.15.2 + rev: v3.16.0 hooks: - id: pyupgrade stages: [manual] diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 7e00135a4c1..8ad36f9b91c 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -221,9 +221,9 @@ def _ensure_cache_dir_and_supporting_files(self) -> None: os.umask(umask) path.chmod(0o777 - umask) - with open(path.joinpath("README.md"), "xt", encoding="UTF-8") as f: + with open(path.joinpath("README.md"), "x", encoding="UTF-8") as f: f.write(README_CONTENT) - with open(path.joinpath(".gitignore"), "xt", encoding="UTF-8") as f: + with open(path.joinpath(".gitignore"), "x", encoding="UTF-8") as f: f.write("# Created by pytest automatically.\n*\n") with open(path.joinpath("CACHEDIR.TAG"), "xb") as f: f.write(CACHEDIR_TAG_CONTENT) From 037aaa7d45e0ec8385676f8a48563e4d620691c2 Mon Sep 17 00:00:00 2001 From: Stavros Ntentos <133706+stdedos@users.noreply.github.com> Date: Thu, 13 Jun 2024 11:18:40 +0300 Subject: [PATCH 080/199] Update `contact.rst`: Update Matrix link --- doc/en/contact.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/en/contact.rst b/doc/en/contact.rst index 68efef522ff..44957a0d4a8 100644 --- a/doc/en/contact.rst +++ b/doc/en/contact.rst @@ -22,9 +22,9 @@ Contact channels requests to GitHub. - ``#pytest`` `on irc.libera.chat `_ IRC - channel for random questions (using an IRC client, `via webchat - `_, or `via Matrix - `_). + channel for random questions (using an IRC client, or `via webchat + `) +- ``#pytest`` `on Matrix https://matrix.to/#/#pytest:matrix.org>`. .. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues From 2effd8cb2c91abf4782201c431f19d0b22517920 Mon Sep 17 00:00:00 2001 From: neutraljump <162650677+neutraljump@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:39:02 +0930 Subject: [PATCH 081/199] Docs: clean up various documentation pages (#12451) * Change Contribution doc title to match sidebar * Rearrange sentence for clarity * Update backwards-compatibility.rst some minor grammar changes * Update pythonpath.rst fixed some gramatical errors * Update AUTHORS * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Update doc/en/explanation/pythonpath.rst From a quick overview it looks like lowercase is more consistent, although some pages do use `pytest` in code blocks Co-authored-by: Bruno Oliveira --------- Co-authored-by: Mackerello <82668740+Mackerello@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Bruno Oliveira --- AUTHORS | 2 ++ CONTRIBUTING.rst | 4 ++-- doc/en/backwards-compatibility.rst | 28 ++++++++++++---------------- doc/en/explanation/pythonpath.rst | 16 ++++++++-------- 4 files changed, 24 insertions(+), 26 deletions(-) diff --git a/AUTHORS b/AUTHORS index 0560caf721b..80b6d5157cd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -259,6 +259,7 @@ Marc Bresson Marco Gorelli Mark Abramowitz Mark Dickinson +Mark Vong Marko Pacak Markus Unterwaditzer Martijn Faassen @@ -301,6 +302,7 @@ Nicholas Devenish Nicholas Murphy Niclas Olofsson Nicolas Delaby +Nico Vidal Nikolay Kondratyev Nipunn Koorapati Oleg Pidsadnyi diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 0bf440da261..12e2b18bb52 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -1,5 +1,5 @@ ============================ -Contribution getting started +Contributing ============================ Contributions are highly welcomed and appreciated. Every little bit of help counts, @@ -124,7 +124,7 @@ For example: Submitting Plugins to pytest-dev -------------------------------- -Pytest development of the core, some plugins and support code happens +Development of the pytest core, support code, and some plugins happens in repositories living under the ``pytest-dev`` organisations: - `pytest-dev on GitHub `_ diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index e04e64a76f9..c0feb833ce1 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -5,30 +5,26 @@ Backwards Compatibility Policy .. versionadded: 6.0 -pytest is actively evolving and is a project that has been decades in the making, -we keep learning about new and better structures to express different details about testing. +Pytest is an actively evolving project that has been decades in the making. +We keep learning about new and better structures to express different details about testing. -While we implement those modifications we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors. +While we implement those modifications, we try to ensure an easy transition and don't want to impose unnecessary churn on our users and community/plugin authors. As of now, pytest considers multiple types of backward compatibility transitions: -a) trivial: APIs which trivially translate to the new mechanism, - and do not cause problematic changes. +a) trivial: APIs that trivially translate to the new mechanism and do not cause problematic changes. - We try to support those indefinitely while encouraging users to switch to newer/better mechanisms through documentation. + We try to support those indefinitely while encouraging users to switch to newer or better mechanisms through documentation. -b) transitional: the old and new API don't conflict - and we can help users transition by using warnings, while supporting both for a prolonged time. +b) transitional: the old and new APIs don't conflict, and we can help users transition by using warnings while supporting both for a prolonged period of time. - We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). + We will only start the removal of deprecated functionality in major releases (e.g., if we deprecate something in 3.0, we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g., if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). A deprecated feature scheduled to be removed in major version X will use the warning class `PytestRemovedInXWarning` (a subclass of :class:`~pytest.PytestDeprecationWarning`). - When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g. `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed. + When the deprecation expires (e.g., 4.0 is released), we won't remove the deprecated functionality immediately but will use the standard warning filters to turn `PytestRemovedInXWarning` (e.g., `PytestRemovedIn4Warning`) into **errors** by default. This approach makes it explicit that removal is imminent and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g., 4.1), the feature will be effectively removed. - -c) true breakage: should only be considered when normal transition is unreasonably unsustainable and would offset important development/features by years. - In addition, they should be limited to APIs where the number of actual users is very small (for example only impacting some plugins), and can be coordinated with the community in advance. +c) True breakage should only be considered when a normal transition is unreasonably unsustainable and would offset important developments or features by years. In addition, they should be limited to APIs where the number of actual users is very small (for example, only impacting some plugins) and can be coordinated with the community in advance. Examples for such upcoming changes: @@ -62,11 +58,11 @@ Focus primary on smooth transition - stance (pre 6.0) Keeping backwards compatibility has a very high priority in the pytest project. Although we have deprecated functionality over the years, most of it is still supported. All deprecations in pytest were done because simpler or more efficient ways of accomplishing the same tasks have emerged, making the old way of doing things unnecessary. -With the pytest 3.0 release we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around. +With the pytest 3.0 release, we introduced a clear communication scheme for when we will actually remove the old busted joint and politely ask you to use the new hotness instead, while giving you enough time to adjust your tests or raise concerns if there are valid reasons to keep deprecated functionality around. -To communicate changes we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible. +To communicate changes, we issue deprecation warnings using a custom warning hierarchy (see :ref:`internal-warnings`). These warnings may be suppressed using the standard means: ``-W`` command-line flag or ``filterwarnings`` ini options (see :ref:`warnings`), but we suggest to use these sparingly and temporarily, and heed the warnings when possible. -We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0 we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). +We will only start the removal of deprecated functionality in major releases (e.g. if we deprecate something in 3.0, we will start to remove it in 4.0), and keep it around for at least two minor releases (e.g. if we deprecate something in 3.9 and 4.0 is the next release, we start to remove it in 5.0, not in 4.0). When the deprecation expires (e.g. 4.0 is released), we won't remove the deprecated functionality immediately, but will use the standard warning filters to turn them into **errors** by default. This approach makes it explicit that removal is imminent, and still gives you time to turn the deprecated feature into a warning instead of an error so it can be dealt with in your own time. In the next minor release (e.g. 4.1), the feature will be effectively removed. diff --git a/doc/en/explanation/pythonpath.rst b/doc/en/explanation/pythonpath.rst index 33eba86b57a..d0314a6dbcd 100644 --- a/doc/en/explanation/pythonpath.rst +++ b/doc/en/explanation/pythonpath.rst @@ -8,15 +8,15 @@ pytest import mechanisms and ``sys.path``/``PYTHONPATH`` Import modes ------------ -pytest as a testing framework needs to import test modules and ``conftest.py`` files for execution. +pytest as a testing framework that needs to import test modules and ``conftest.py`` files for execution. -Importing files in Python is a non-trivial processes, so aspects of the +Importing files in Python is a non-trivial process, so aspects of the import process can be controlled through the ``--import-mode`` command-line flag, which can assume these values: .. _`import-mode-prepend`: -* ``prepend`` (default): the directory path containing each module will be inserted into the *beginning* +* ``prepend`` (default): The directory path containing each module will be inserted into the *beginning* of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module ` function. @@ -34,7 +34,7 @@ these values: * ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already there, and imported with :func:`importlib.import_module `. - This better allows to run test modules against installed versions of a package even if the + This better allows users to run test modules against installed versions of a package even if the package under test has the same import root. For example: :: @@ -45,7 +45,7 @@ these values: the tests will run against the installed version of ``pkg_under_test`` when ``--import-mode=append`` is used whereas - with ``prepend`` they would pick up the local version. This kind of confusion is why + with ``prepend``, they would pick up the local version. This kind of confusion is why we advocate for using :ref:`src-layouts `. Same as ``prepend``, requires test module names to be unique when the test directory tree is @@ -67,7 +67,7 @@ these values: are not importable. The recommendation in this case it to place testing utility modules together with the application/library code, for example ``app.testing.helpers``. - Important: by "test utility modules" we mean functions/classes which are imported by + Important: by "test utility modules", we mean functions/classes which are imported by other tests directly; this does not include fixtures, which should be placed in ``conftest.py`` files, along with the test modules, and are discovered automatically by pytest. @@ -76,8 +76,8 @@ these values: 1. Given a certain module path, for example ``tests/core/test_models.py``, derives a canonical name like ``tests.core.test_models`` and tries to import it. - For non-test modules this will work if they are accessible via :py:data:`sys.path`, so - for example ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``. + For non-test modules, this will work if they are accessible via :py:data:`sys.path`. So + for example, ``.env/lib/site-packages/app/core.py`` will be importable as ``app.core``. This is happens when plugins import non-test modules (for example doctesting). If this step succeeds, the module is returned. From 5037f8d1145b87b0008902487ace44653692374b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 05:24:06 +0000 Subject: [PATCH 082/199] [automated] Update plugin list (#12462) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 126 ++++++++++++++++--------------- 1 file changed, 67 insertions(+), 59 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index ab7d28edf0f..83d074e7f03 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =3.5.0) :pypi:`pytest-aws` pytest plugin for testing AWS resource configurations Oct 04, 2017 4 - Beta N/A @@ -150,7 +150,7 @@ This list contains 1476 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 07, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 12, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -160,7 +160,7 @@ This list contains 1476 plugins. :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-bisect-tests` Find tests leaking state and affecting other Mar 25, 2024 N/A N/A + :pypi:`pytest-bisect-tests` Find tests leaking state and affecting other Jun 09, 2024 N/A N/A :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) @@ -222,7 +222,7 @@ This list contains 1476 plugins. :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<9,>=7.0 - :pypi:`pytest-checklist` Pytest plugin to track and report unit/function coverage. Mar 12, 2024 N/A N/A + :pypi:`pytest-checklist` Pytest plugin to track and report unit/function coverage. Jun 10, 2024 N/A N/A :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 20, 2024 N/A N/A :pypi:`pytest-ch-framework` My pytest framework Apr 17, 2024 N/A pytest==8.0.1 @@ -261,12 +261,11 @@ This list contains 1476 plugins. :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-collect-interface-info-plugin` Get executed interface information in pytest interface automation framework Sep 25, 2023 4 - Beta N/A - :pypi:`pytest-collect-jmeter-report-tests` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1 :pypi:`pytest-collector` Python package for collecting pytest. Aug 02, 2022 N/A pytest (>=7.0,<8.0) :pypi:`pytest-collect-pytest-interinfo` A simple plugin to use with pytest Sep 26, 2023 4 - Beta N/A :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 Jun 03, 2024 N/A pytest<9,>=3.6 + :pypi:`pytest-common-subject` pytest framework for testing different aspects of a common method Jun 12, 2024 N/A pytest<9,>=3.6 :pypi:`pytest-compare` pytest plugin for comparing call arguments. Jun 22, 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 @@ -314,7 +313,7 @@ This list contains 1476 plugins. :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A :pypi:`pytest-dashboard` May 30, 2024 N/A pytest<8.0.0,>=7.4.3 :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databases` Reusable database fixtures for any and all databases. May 25, 2024 4 - Beta pytest + :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Jun 11, 2024 4 - Beta pytest :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 03, 2023 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) @@ -366,15 +365,16 @@ This list contains 1476 plugins. :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. May 11, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 :pypi:`pytest-discover` Pytest plugin to record discovered tests in a file Mar 26, 2024 N/A pytest - :pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. May 29, 2024 4 - Beta pytest>=3.5.0 + :pypi:`pytest-ditto` Snapshot testing pytest plugin with minimal ceremony and flexible persistence formats. Jun 09, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-ditto-pandas` pytest-ditto plugin for pandas snapshots. May 29, 2024 4 - Beta pytest>=3.5.0 + :pypi:`pytest-ditto-pyarrow` pytest-ditto plugin for pyarrow tables. Jun 09, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-django` A Django plugin for pytest. Jan 30, 2024 5 - Production/Stable pytest >=7.0.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-class` A pytest plugin for running django in class-scoped fixtures Aug 08, 2023 4 - Beta N/A - :pypi:`pytest-django-docker-pg` May 21, 2024 5 - Production/Stable pytest<9.0.0,>=7.0.0 + :pypi:`pytest-django-docker-pg` Jun 13, 2024 5 - Production/Stable pytest<9.0.0,>=7.0.0 :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 @@ -443,7 +443,7 @@ This list contains 1476 plugins. :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 Dec 05, 2023 5 - Production/Stable pytest >=2.2 - :pypi:`pytest-edit` Edit the source code of a failed test with \`pytest --edit\`. Jun 07, 2024 N/A pytest + :pypi:`pytest-edit` Edit the source code of a failed test with \`pytest --edit\`. Jun 09, 2024 N/A pytest :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 15, 2024 5 - Production/Stable pytest >=7.0 :pypi:`pytest-elements` Tool to help automate user interfaces Jan 13, 2021 N/A pytest (>=5.4,<6.0) @@ -636,7 +636,7 @@ This list contains 1476 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 08, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 12, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -678,6 +678,7 @@ This list contains 1476 plugins. :pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0 :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A + :pypi:`pytest-infinity` Jun 09, 2024 N/A pytest<9.0.0,>=8.0.0 :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-info-plugin` Get executed interface information in pytest interface automation framework Sep 14, 2023 N/A N/A @@ -729,7 +730,6 @@ This list contains 1476 plugins. :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-json-report-wip` A pytest plugin to report test results as JSON files Oct 28, 2023 4 - Beta pytest >=3.8.0 :pypi:`pytest-jsonschema` A pytest plugin to perform JSONSchema validations Mar 27, 2024 4 - Beta pytest>=6.2.0 - :pypi:`pytest-jtl-collector` A simple plugin to use with pytest May 20, 2024 4 - Beta pytest>=7.2.1 :pypi:`pytest-jtr` pytest plugin supporting json test report output Jun 04, 2024 N/A pytest<8.0.0,>=7.1.2 :pypi:`pytest-jupyter` A pytest plugin for testing Jupyter libraries and extensions. Apr 04, 2024 4 - Beta pytest>=7.0 :pypi:`pytest-jupyterhub` A reusable JupyterHub pytest plugin Apr 25, 2023 5 - Production/Stable pytest @@ -824,7 +824,7 @@ This list contains 1476 plugins. :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 Feb 12, 2024 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 May 28, 2024 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin Jun 13, 2024 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) @@ -877,7 +877,7 @@ This list contains 1476 plugins. :pypi:`pytest-ndb` pytest notebook debugger Apr 28, 2024 N/A pytest :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-neos` Pytest plugin for neos May 01, 2024 1 - Planning N/A + :pypi:`pytest-neos` Pytest plugin for neos Jun 11, 2024 1 - Planning N/A :pypi:`pytest-netdut` "Automated software testing for switches using pytest" Mar 07, 2024 N/A pytest <7.3,>=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 @@ -886,7 +886,7 @@ This list contains 1476 plugins. :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 May 20, 2024 N/A pytest<9.0.0,>=8.2.0 + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Jun 11, 2024 N/A pytest<9.0.0,>=8.2.0 :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 Apr 11, 2024 N/A N/A @@ -950,7 +950,7 @@ This list contains 1476 plugins. :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-patterns` pytest plugin to make testing complicated long string output easy to write and easy to debug Nov 17, 2023 4 - Beta N/A + :pypi:`pytest-patterns` pytest plugin to make testing complicated long string output easy to write and easy to debug Jun 14, 2024 4 - Beta N/A :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 @@ -1037,7 +1037,7 @@ This list contains 1476 plugins. :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 Oct 06, 2023 5 - Production/Stable pytest >=7.0 :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 Apr 30, 2024 N/A pytest + :pypi:`pytest-pyodide` Pytest plugin for testing applications that use Pyodide Jun 12, 2024 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) @@ -1059,7 +1059,7 @@ This list contains 1476 plugins. :pypi:`pytest-qaseio` Pytest plugin for Qase.io integration May 30, 2024 4 - Beta pytest<9.0.0,>=7.2.2 :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 Nov 29, 2023 5 - Production/Stable pytest >=6.0 + :pypi:`pytest-qgis` A pytest plugin for testing QGIS python plugins Jun 14, 2024 5 - Production/Stable pytest>=6.0 :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 Feb 07, 2024 5 - Production/Stable pytest @@ -1086,7 +1086,7 @@ This list contains 1476 plugins. :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 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. Jun 05, 2024 5 - Production/Stable pytest>=6.2 + :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Jun 10, 2024 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) @@ -1267,7 +1267,7 @@ This list contains 1476 plugins. :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 Jun 03, 2024 N/A pytest<8,>5.4.0 - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 05, 2024 N/A N/A + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 14, 2024 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 @@ -1289,7 +1289,7 @@ This list contains 1476 plugins. :pypi:`pytest-stoq` A plugin to pytest stoq Feb 09, 2021 4 - Beta N/A :pypi:`pytest-store` Pytest plugin to store values from test runs Nov 16, 2023 3 - Alpha pytest (>=7.0.0) :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 Jun 08, 2024 N/A pytest + :pypi:`pytest-structlog` Structured logging assertions Jun 09, 2024 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) @@ -1461,7 +1461,7 @@ This list contains 1476 plugins. :pypi:`pytest-wdl` Pytest plugin for testing WDL workflows. Nov 17, 2020 5 - Production/Stable N/A :pypi:`pytest-web3-data` A pytest plugin to fetch test data from IPFS HTTP gateways during pytest execution. Oct 04, 2023 4 - Beta pytest :pypi:`pytest-webdriver` Selenium webdriver fixture for py.test May 28, 2019 5 - Production/Stable pytest - :pypi:`pytest-webtest-extras` Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. Nov 13, 2023 N/A pytest >= 7.0.0 + :pypi:`pytest-webtest-extras` Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. Jun 08, 2024 N/A pytest>=7.0.0 :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 May 28, 2024 N/A pytest>=7.3.1 :pypi:`pytest-whirlwind` Testing Tornado. Jun 12, 2020 N/A N/A @@ -1499,6 +1499,7 @@ This list contains 1476 plugins. :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 30, 2024 N/A pytest<8.0.0,>=7.2.2 + :pypi:`pytest-youqu-playwright` pytest-youqu-playwright Jun 12, 2024 N/A pytest :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 @@ -2153,7 +2154,7 @@ This list contains 1476 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. :pypi:`pytest-aux` - *last release*: May 31, 2024, + *last release*: Jun 10, 2024, *status*: N/A, *requires*: N/A @@ -2328,7 +2329,7 @@ This list contains 1476 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Jun 07, 2024, + *last release*: Jun 12, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2398,7 +2399,7 @@ This list contains 1476 plugins. Provides a mock fixture for python bigquery client :pypi:`pytest-bisect-tests` - *last release*: Mar 25, 2024, + *last release*: Jun 09, 2024, *status*: N/A, *requires*: N/A @@ -2832,7 +2833,7 @@ This list contains 1476 plugins. Check links in files :pypi:`pytest-checklist` - *last release*: Mar 12, 2024, + *last release*: Jun 10, 2024, *status*: N/A, *requires*: N/A @@ -3104,13 +3105,6 @@ This list contains 1476 plugins. Get executed interface information in pytest interface automation framework - :pypi:`pytest-collect-jmeter-report-tests` - *last release*: May 20, 2024, - *status*: 4 - Beta, - *requires*: pytest>=7.2.1 - - A simple plugin to use with pytest - :pypi:`pytest-collector` *last release*: Aug 02, 2022, *status*: N/A, @@ -3140,7 +3134,7 @@ This list contains 1476 plugins. An interactive GUI test runner for PyTest :pypi:`pytest-common-subject` - *last release*: Jun 03, 2024, + *last release*: Jun 12, 2024, *status*: N/A, *requires*: pytest<9,>=3.6 @@ -3476,7 +3470,7 @@ This list contains 1476 plugins. Useful functions for managing data for pytest fixtures :pypi:`pytest-databases` - *last release*: May 25, 2024, + *last release*: Jun 11, 2024, *status*: 4 - Beta, *requires*: pytest @@ -3840,7 +3834,7 @@ This list contains 1476 plugins. Pytest plugin to record discovered tests in a file :pypi:`pytest-ditto` - *last release*: May 29, 2024, + *last release*: Jun 09, 2024, *status*: 4 - Beta, *requires*: pytest>=3.5.0 @@ -3853,6 +3847,13 @@ This list contains 1476 plugins. pytest-ditto plugin for pandas snapshots. + :pypi:`pytest-ditto-pyarrow` + *last release*: Jun 09, 2024, + *status*: 4 - Beta, + *requires*: pytest>=3.5.0 + + pytest-ditto plugin for pyarrow tables. + :pypi:`pytest-django` *last release*: Jan 30, 2024, *status*: 5 - Production/Stable, @@ -3896,7 +3897,7 @@ This list contains 1476 plugins. A pytest plugin for running django in class-scoped fixtures :pypi:`pytest-django-docker-pg` - *last release*: May 21, 2024, + *last release*: Jun 13, 2024, *status*: 5 - Production/Stable, *requires*: pytest<9.0.0,>=7.0.0 @@ -4379,7 +4380,7 @@ This list contains 1476 plugins. pytest plugin with mechanisms for echoing environment variables, package version and generic attributes :pypi:`pytest-edit` - *last release*: Jun 07, 2024, + *last release*: Jun 09, 2024, *status*: N/A, *requires*: pytest @@ -5730,7 +5731,7 @@ This list contains 1476 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Jun 08, 2024, + *last release*: Jun 12, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 @@ -6023,6 +6024,13 @@ This list contains 1476 plugins. an incremental test runner (pytest plugin) + :pypi:`pytest-infinity` + *last release*: Jun 09, 2024, + *status*: N/A, + *requires*: pytest<9.0.0,>=8.0.0 + + + :pypi:`pytest-influxdb` *last release*: Apr 20, 2021, *status*: N/A, @@ -6380,13 +6388,6 @@ This list contains 1476 plugins. A pytest plugin to perform JSONSchema validations - :pypi:`pytest-jtl-collector` - *last release*: May 20, 2024, - *status*: 4 - Beta, - *requires*: pytest>=7.2.1 - - A simple plugin to use with pytest - :pypi:`pytest-jtr` *last release*: Jun 04, 2024, *status*: N/A, @@ -7046,7 +7047,7 @@ This list contains 1476 plugins. Custom metrics report for pytest :pypi:`pytest-mh` - *last release*: May 28, 2024, + *last release*: Jun 13, 2024, *status*: N/A, *requires*: pytest @@ -7417,7 +7418,7 @@ This list contains 1476 plugins. pytest-neo is a plugin for pytest that shows tests like screen of Matrix. :pypi:`pytest-neos` - *last release*: May 01, 2024, + *last release*: Jun 11, 2024, *status*: 1 - Planning, *requires*: N/A @@ -7480,7 +7481,7 @@ This list contains 1476 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: May 20, 2024, + *last release*: Jun 11, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.2.0 @@ -7928,7 +7929,7 @@ This list contains 1476 plugins. A contextmanager pytest fixture for handling multiple mock patches :pypi:`pytest-patterns` - *last release*: Nov 17, 2023, + *last release*: Jun 14, 2024, *status*: 4 - Beta, *requires*: N/A @@ -8537,7 +8538,7 @@ This list contains 1476 plugins. Record PyMySQL queries and mock with the stored data. :pypi:`pytest-pyodide` - *last release*: Apr 30, 2024, + *last release*: Jun 12, 2024, *status*: N/A, *requires*: pytest @@ -8691,9 +8692,9 @@ This list contains 1476 plugins. Pytest plugin for uploading test results to your QA Touch Testrun. :pypi:`pytest-qgis` - *last release*: Nov 29, 2023, + *last release*: Jun 14, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=6.0 + *requires*: pytest>=6.0 A pytest plugin for testing QGIS python plugins @@ -8880,7 +8881,7 @@ This list contains 1476 plugins. Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal :pypi:`pytest-redis` - *last release*: Jun 05, 2024, + *last release*: Jun 10, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=6.2 @@ -10147,7 +10148,7 @@ This list contains 1476 plugins. A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Jun 05, 2024, + *last release*: Jun 14, 2024, *status*: N/A, *requires*: N/A @@ -10301,7 +10302,7 @@ This list contains 1476 plugins. A Pytest plugin that allows you to loop tests for a user defined amount of time. :pypi:`pytest-structlog` - *last release*: Jun 08, 2024, + *last release*: Jun 09, 2024, *status*: N/A, *requires*: pytest @@ -11505,9 +11506,9 @@ This list contains 1476 plugins. Selenium webdriver fixture for py.test :pypi:`pytest-webtest-extras` - *last release*: Nov 13, 2023, + *last release*: Jun 08, 2024, *status*: N/A, - *requires*: pytest >= 7.0.0 + *requires*: pytest>=7.0.0 Pytest plugin to enhance pytest-html and allure reports of webtest projects by adding screenshots, comments and webpage sources. @@ -11770,6 +11771,13 @@ This list contains 1476 plugins. Pytest plugin to test the YLS as a whole. + :pypi:`pytest-youqu-playwright` + *last release*: Jun 12, 2024, + *status*: N/A, + *requires*: pytest + + pytest-youqu-playwright + :pypi:`pytest-yuk` *last release*: Mar 26, 2021, *status*: N/A, From 80b7657b2a1255d29978b7ead4006821ce8e3b65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 09:58:23 +0200 Subject: [PATCH 083/199] build(deps): Bump pypa/gh-action-pypi-publish from 1.8.14 to 1.9.0 (#12464) Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.14 to 1.9.0. - [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases) - [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.14...v1.9.0) --- updated-dependencies: - dependency-name: pypa/gh-action-pypi-publish dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 640a0f1c281..f5ea4d39764 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -54,7 +54,7 @@ jobs: path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@v1.8.14 + uses: pypa/gh-action-pypi-publish@v1.9.0 - name: Push tag run: | From fe4961afae91f3c259b30690eb39cefe867acea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 17 Jun 2024 17:44:14 +0200 Subject: [PATCH 084/199] Modernize the skipped `test_issue_9765` regression test (#12468) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚑 Explicitly set encoding @ `subprocess.run()` This invocation is present in the `test_issue_9765` regression test that is always skipped unconditionally, resulting in the `EncodingWarning` never manifesting itself in CI or development environments of the contributors. * Change `setup.py` call w/ `pip install` @ tests Using this CLI interface has been deprecated in `setuptools` [[1]]. [1]: https://blog.ganssle.io/articles/2021/10/setup-py-deprecated.html --- testing/acceptance_test.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index ac7fab3d27a..0f3b35036d7 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1464,14 +1464,21 @@ def my_fixture(self, request): } ) - subprocess.run([sys.executable, "setup.py", "develop"], check=True) + subprocess.run( + [sys.executable, "-Im", "pip", "install", "-e", "."], + check=True, + ) try: # We are using subprocess.run rather than pytester.run on purpose. # pytester.run is adding the current directory to PYTHONPATH which avoids # the bug. We also use pytest rather than python -m pytest for the same # PYTHONPATH reason. subprocess.run( - ["pytest", "my_package"], capture_output=True, check=True, text=True + ["pytest", "my_package"], + capture_output=True, + check=True, + encoding="utf-8", + text=True, ) except subprocess.CalledProcessError as exc: raise AssertionError( From 49374ec7a0a0cc945e0d6b34e34ad493b837125b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Tue, 18 Jun 2024 11:21:25 +0200 Subject: [PATCH 085/199] =?UTF-8?q?=F0=9F=9A=91=20Clarify=20patch=20condit?= =?UTF-8?q?ion=20for=20doctest=20finder=20hack=20(#12471)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was a regression caused by #12431 that excluded the patch on broken CPython patch version ranges 3.11.0–3.11.8 and 3.12.0–3.12.2. This change fixes that by adjusting the patching conditional clause to include said versions. Closes #12430. Co-authored-by: Florian Bruhin --- src/_pytest/doctest.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index 23ad7a7a979..e61694d553f 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -505,7 +505,13 @@ def collect(self) -> Iterable[DoctestItem]: import doctest class MockAwareDocTestFinder(doctest.DocTestFinder): - if sys.version_info < (3, 11): + py_ver_info_minor = sys.version_info[:2] + is_find_lineno_broken = ( + py_ver_info_minor < (3, 11) + or (py_ver_info_minor == (3, 11) and sys.version_info.micro < 9) + or (py_ver_info_minor == (3, 12) and sys.version_info.micro < 3) + ) + if is_find_lineno_broken: def _find_lineno(self, obj, source_lines): """On older Pythons, doctest code does not take into account From 807d16abfdc62443201c23318ee196377e249fc5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 08:29:53 -0300 Subject: [PATCH 086/199] [pre-commit.ci] pre-commit autoupdate (#12470) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 42fbc31ea12..5a3d75ff874 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.8" + rev: "v0.4.9" hooks: - id: ruff args: ["--fix"] From 76f3f3da00b67ff5fc8b84f111eae7bfa4436dc1 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 15 Jan 2024 12:20:23 +0100 Subject: [PATCH 087/199] fix #11797: be more lenient on SequenceLike approx this needs a validation as it allows partially implemented sequences --- changelog/11797.bugfix.rst | 1 + src/_pytest/python_api.py | 16 ++++++++++----- testing/python/approx.py | 40 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 changelog/11797.bugfix.rst diff --git a/changelog/11797.bugfix.rst b/changelog/11797.bugfix.rst new file mode 100644 index 00000000000..94b72da00fd --- /dev/null +++ b/changelog/11797.bugfix.rst @@ -0,0 +1 @@ +:func:`pytest.approx` now correctly handles :class:`Sequence `-like objects. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 9b17e5cb786..bfc8dc33445 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -129,6 +129,8 @@ def _recursive_sequence_map(f, x): if isinstance(x, (list, tuple)): seq_type = type(x) return seq_type(_recursive_sequence_map(f, xi) for xi in x) + elif _is_sequence_like(x): + return [_recursive_sequence_map(f, xi) for xi in x] else: return f(x) @@ -721,11 +723,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: elif _is_numpy_array(expected): expected = _as_numpy_array(expected) cls = ApproxNumpy - elif ( - hasattr(expected, "__getitem__") - and isinstance(expected, Sized) - and not isinstance(expected, (str, bytes)) - ): + elif _is_sequence_like(expected): cls = ApproxSequenceLike elif isinstance(expected, Collection) and not isinstance(expected, (str, bytes)): msg = f"pytest.approx() only supports ordered sequences, but got: {expected!r}" @@ -736,6 +734,14 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: return cls(expected, rel, abs, nan_ok) +def _is_sequence_like(expected: object) -> bool: + return ( + hasattr(expected, "__getitem__") + and isinstance(expected, Sized) + and not isinstance(expected, (str, bytes)) + ) + + def _is_numpy_array(obj: object) -> bool: """ Return true if the given object is implicitly convertible to ndarray, diff --git a/testing/python/approx.py b/testing/python/approx.py index 968e8828512..31e64c4df3d 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -954,6 +954,43 @@ def test_allow_ordered_sequences_only(self) -> None: with pytest.raises(TypeError, match="only supports ordered sequences"): assert {1, 2, 3} == approx({1, 2, 3}) + def test_strange_sequence(self): + """https://github.com/pytest-dev/pytest/issues/11797""" + a = MyVec3(1, 2, 3) + b = MyVec3(0, 1, 2) + + # this would trigger the error inside the test + pytest.approx(a, abs=0.5)._repr_compare(b) + + assert b == pytest.approx(a, abs=2) + assert b != pytest.approx(a, abs=0.5) + + +class MyVec3: # incomplete + """sequence like""" + + _x: int + _y: int + _z: int + + def __init__(self, x: int, y: int, z: int): + self._x, self._y, self._z = x, y, z + + def __repr__(self) -> str: + return f"" + + def __len__(self) -> int: + return 3 + + def __getitem__(self, key: int) -> int: + if key == 0: + return self._x + if key == 1: + return self._y + if key == 2: + return self._z + raise IndexError(key) + class TestRecursiveSequenceMap: def test_map_over_scalar(self): @@ -981,3 +1018,6 @@ def test_map_over_mixed_sequence(self): (5, 8), [(7)], ] + + def test_map_over_sequence_like(self): + assert _recursive_sequence_map(int, MyVec3(1, 2, 3)) == [1, 2, 3] From 9e98b6db5b2a39c94b31ce9567ea647196c0ecce Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 18 Jun 2024 18:05:45 +0200 Subject: [PATCH 088/199] =?UTF-8?q?=F0=9F=A7=AA=20Bump=20RTD=20env=20to=20?= =?UTF-8?q?the=20latest=20LTS=20Ubuntu=20&=20Python?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yml b/.readthedocs.yml index 266d4e07aea..04716fd2a6f 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -14,9 +14,10 @@ sphinx: fail_on_warning: true build: - os: ubuntu-20.04 + os: ubuntu-24.04 tools: - python: "3.9" + python: >- + 3.12 apt_packages: - inkscape From f479afc5c0a3d4a6dd062f30738458637c873df8 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 18 Jun 2024 23:32:18 +0200 Subject: [PATCH 089/199] =?UTF-8?q?=F0=9F=92=85=20Add=20a=20config=20for?= =?UTF-8?q?=20the=20Chronographer=20GitHub=20App?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This app allows requiring changelog fragments to be included with each pull request. --- .github/chronographer.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/chronographer.yml diff --git a/.github/chronographer.yml b/.github/chronographer.yml new file mode 100644 index 00000000000..803db1e3417 --- /dev/null +++ b/.github/chronographer.yml @@ -0,0 +1,20 @@ +--- + +branch-protection-check-name: Changelog entry +action-hints: + check-title-prefix: "Chronographer: " + external-docs-url: >- + https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests + inline-markdown: >- + See + https://docs.pytest.org/en/latest/contributing.html#preparing-pull-requests + for details. +enforce-name: + suffix: .rst +exclude: + humans: + - pyup-bot +labels: + skip-changelog: skip news + +... From d7b401063874141fcbcda78eaec3c28148e7dcd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Wed, 19 Jun 2024 02:41:33 +0200 Subject: [PATCH 090/199] =?UTF-8?q?=F0=9F=92=85=20Add=20a=20config=20for?= =?UTF-8?q?=20the=20Patchback=20GitHub=20App=20(#12475)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch prepares the project's backporting process to start being handled by the Patchback GitHub App [[1]]. Ref #9384 Resolves #9385 Resolves #9553 Resolves #9554 Resolves #9555 [1]: https://github.com/apps/patchback --- .github/patchback.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/patchback.yml diff --git a/.github/patchback.yml b/.github/patchback.yml new file mode 100644 index 00000000000..5d62fca12fe --- /dev/null +++ b/.github/patchback.yml @@ -0,0 +1,7 @@ +--- + +backport_branch_prefix: patchback/backports/ +backport_label_prefix: 'backport ' # IMPORTANT: the labels are space-delimited +# target_branch_prefix: '' # The project's backport branches are non-prefixed + +... From 19715bf313680509c2ef5bcc7af7b304a000a199 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 19 Jun 2024 11:22:55 +0200 Subject: [PATCH 091/199] =?UTF-8?q?=F0=9F=94=A5=20Exterminate=20legacy=20`?= =?UTF-8?q?backport`=20GHA=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, this workflow was being used to cherry-pick PRs made against `main` into older stable branches. Now that #12475 integrated the Patchback GitHub App, it's no longer needed as it's making duplicate pull requests which don't even trigger CI runs automatically due to #10354. So this patch removes said workflow to address the problem. --- .github/workflows/backport.yml | 51 ---------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/backport.yml diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml deleted file mode 100644 index 38ce7260278..00000000000 --- a/.github/workflows/backport.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: backport - -on: - # Note that `pull_request_target` has security implications: - # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - # In particular: - # - Only allow triggers that can be used only be trusted users - # - Don't execute any code from the target branch - # - Don't use cache - pull_request_target: - types: [labeled] - -# Set permissions at the job level. -permissions: {} - -jobs: - backport: - if: startsWith(github.event.label.name, 'backport ') && github.event.pull_request.merged - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - persist-credentials: true - - - name: Create backport PR - run: | - set -eux - - git config --global user.name "pytest bot" - git config --global user.email "pytestbot@gmail.com" - - label='${{ github.event.label.name }}' - target_branch="${label#backport }" - backport_branch=backport-${{ github.event.number }}-to-"${target_branch}" - subject="[$target_branch] $(gh pr view --json title -q .title ${{ github.event.number }})" - - git checkout origin/"${target_branch}" -b "${backport_branch}" - git cherry-pick -x --mainline 1 ${{ github.event.pull_request.merge_commit_sha }} - git commit --amend --message "$subject" - git push --set-upstream origin --force-with-lease "${backport_branch}" - gh pr create \ - --base "${target_branch}" \ - --title "${subject}" \ - --body "Backport of PR #${{ github.event.number }} to $target_branch branch. PR created by backport workflow." - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From bab3c2c291464f38ddd12d15818e41d2a9bb5e25 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 19 Jun 2024 11:31:55 +0200 Subject: [PATCH 092/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20myself=20to=20the?= =?UTF-8?q?=20authors=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 80b6d5157cd..347efad57b3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -392,6 +392,7 @@ Stefano Taschini Steffen Allner Stephan Obermann Sven-Hendrik Haase +Sviatoslav Sydorenko Sylvain Marié Tadek Teleżyński Takafumi Arakaki From 6a95bcaa587a8903d190d9c6fe0bb8b76c9482c1 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 19 Jun 2024 12:10:20 +0200 Subject: [PATCH 093/199] =?UTF-8?q?=F0=9F=A7=AA=20Make=20required=20CI=20j?= =?UTF-8?q?obs=20match=20branch=20protection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 89af24b3936..d16ce0edf93 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -182,6 +182,26 @@ jobs: tox_env: "doctesting" use_coverage: true + continue-on-error: >- + ${{ + contains( + fromJSON( + '[ + "windows-py38-pluggy", + "windows-py313", + "ubuntu-py38-pluggy", + "ubuntu-py38-freeze", + "ubuntu-py313", + "macos-py38", + "macos-py313" + ]' + ), + matrix.name + ) + && true + || false + }} + steps: - uses: actions/checkout@v4 with: From 0470c387a99eab366415e8e920d30242d136647c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 10:22:17 +0200 Subject: [PATCH 094/199] =?UTF-8?q?=F0=9F=93=9D=20Rename=20the=20RTD=20con?= =?UTF-8?q?fig=20to=20canonical=20filename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, it was possible to use several different config file name variants. However, there is one canonical name now and the others are being deprecated [[1]]. [1]: https://docs.rtfd.io/en/stable/config-file/index.html --- .readthedocs.yml => .readthedocs.yaml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .readthedocs.yml => .readthedocs.yaml (100%) diff --git a/.readthedocs.yml b/.readthedocs.yaml similarity index 100% rename from .readthedocs.yml rename to .readthedocs.yaml From e702079fd5338c636a216aaa71525c2b2705872b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 19 Jun 2024 22:44:28 +0200 Subject: [PATCH 095/199] =?UTF-8?q?=F0=9F=93=9D=F0=9F=92=85=20Always=20ren?= =?UTF-8?q?der=20changelog=20draft=20@=20Sphinx=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The earlier implementation was generating a temporary file, when the docs site was being built with `tox`. However, this was not enabled in RTD and is hackish. This patch integrates the `sphinxcontrib-towncrier` extension to make it work in any environment where Sphinx docs are being built. --- .gitignore | 1 - .readthedocs.yaml | 4 +++ doc/en/changelog.rst | 11 ++++--- doc/en/conf.py | 46 +++++++++++++----------------- doc/en/requirements.txt | 1 + scripts/towncrier-draft-to-file.py | 18 ------------ tox.ini | 10 +++---- 7 files changed, 37 insertions(+), 54 deletions(-) delete mode 100644 scripts/towncrier-draft-to-file.py diff --git a/.gitignore b/.gitignore index 9fccf93f7c3..c4557b33a1c 100644 --- a/.gitignore +++ b/.gitignore @@ -25,7 +25,6 @@ src/_pytest/_version.py doc/*/_build doc/*/.doctrees -doc/*/_changelog_towncrier_draft.rst build/ dist/ *.egg-info diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 04716fd2a6f..f7370f1bb98 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -20,6 +20,10 @@ build: 3.12 apt_packages: - inkscape + jobs: + post_checkout: + - git fetch --unshallow || true + - git fetch --tags || true formats: - epub diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 3ab2307ee5d..7e32b943446 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -19,12 +19,15 @@ with advance notice in the **Deprecations** section of releases. we named the news folder changelog -.. only:: changelog_towncrier_draft +.. only:: not is_release - .. The 'changelog_towncrier_draft' tag is included by our 'tox -e docs', - but not on readthedocs. + To be included in v\ |release| (if present) + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - .. include:: _changelog_towncrier_draft.rst + .. towncrier-draft-entries:: |release| [UNRELEASED DRAFT] + + Released versions + ^^^^^^^^^^^^^^^^^ .. towncrier release notes start diff --git a/doc/en/conf.py b/doc/en/conf.py index 738d07dc2f1..3e87003c510 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -15,6 +15,8 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. +import os +from pathlib import Path import shutil from textwrap import dedent from typing import TYPE_CHECKING @@ -26,6 +28,16 @@ import sphinx.application +PROJECT_ROOT_DIR = Path(__file__).parents[2].resolve() +IS_RELEASE_ON_RTD = ( + os.getenv("READTHEDOCS", "False") == "True" + and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag" +) +if IS_RELEASE_ON_RTD: + tags: set[str] + # pylint: disable-next=used-before-assignment + tags.add("is_release") # noqa: F821 + release = ".".join(version.split(".")[:2]) # If extensions (or modules to document with autodoc) are in another directory, @@ -72,6 +84,7 @@ "sphinx.ext.viewcode", "sphinx_removed_in", "sphinxcontrib_trio", + "sphinxcontrib.towncrier.ext", # provides `towncrier-draft-entries` directive ] # Building PDF docs on readthedocs requires inkscape for svg to pdf @@ -422,6 +435,13 @@ ) ] +# -- Options for towncrier_draft extension ----------------------------------- + +towncrier_draft_autoversion_mode = "draft" # or: 'sphinx-version', 'sphinx-release' +towncrier_draft_include_empty = True +towncrier_draft_working_directory = PROJECT_ROOT_DIR +towncrier_draft_config_path = "pyproject.toml" # relative to cwd + intersphinx_mapping = { "pluggy": ("https://pluggy.readthedocs.io/en/stable", None), @@ -435,30 +455,6 @@ } -def configure_logging(app: "sphinx.application.Sphinx") -> None: - """Configure Sphinx's WarningHandler to handle (expected) missing include.""" - import logging - - import sphinx.util.logging - - class WarnLogFilter(logging.Filter): - def filter(self, record: logging.LogRecord) -> bool: - """Ignore warnings about missing include with "only" directive. - - Ref: https://github.com/sphinx-doc/sphinx/issues/2150.""" - if ( - record.msg.startswith('Problems with "include" directive path:') - and "_changelog_towncrier_draft.rst" in record.msg - ): - return False - return True - - logger = logging.getLogger(sphinx.util.logging.NAMESPACE) - warn_handler = [x for x in logger.handlers if x.level == logging.WARNING] - assert len(warn_handler) == 1, warn_handler - warn_handler[0].filters.insert(0, WarnLogFilter()) - - def setup(app: "sphinx.application.Sphinx") -> None: app.add_crossref_type( "fixture", @@ -488,8 +484,6 @@ def setup(app: "sphinx.application.Sphinx") -> None: indextemplate="pair: %s; hook", ) - configure_logging(app) - # legacypath.py monkey-patches pytest.Testdir in. Import the file so # that autodoc can discover references to it. import _pytest.legacypath # noqa: F401 diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index 6e7221d645a..e40a15f1f15 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -9,3 +9,4 @@ sphinxcontrib-svg2pdfconverter # See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045. packaging furo +sphinxcontrib-towncrier diff --git a/scripts/towncrier-draft-to-file.py b/scripts/towncrier-draft-to-file.py deleted file mode 100644 index f771295a01f..00000000000 --- a/scripts/towncrier-draft-to-file.py +++ /dev/null @@ -1,18 +0,0 @@ -# mypy: disallow-untyped-defs -from subprocess import call -import sys - - -def main() -> int: - """ - Platform-agnostic wrapper script for towncrier. - Fixes the issue (#7251) where Windows users are unable to natively run tox -e docs to build pytest docs. - """ - with open( - "doc/en/_changelog_towncrier_draft.rst", "w", encoding="utf-8" - ) as draft_file: - return call(("towncrier", "--draft"), stdout=draft_file) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tox.ini b/tox.ini index 35b335a015d..dae89975467 100644 --- a/tox.ini +++ b/tox.ini @@ -88,11 +88,11 @@ deps = # https://github.com/twisted/towncrier/issues/340 towncrier<21.3.0 commands = - python scripts/towncrier-draft-to-file.py - # the '-t changelog_towncrier_draft' tags makes sphinx include the draft - # changelog in the docs; this does not happen on ReadTheDocs because it uses - # the standard sphinx command so the 'changelog_towncrier_draft' is never set there - sphinx-build -W --keep-going -b html doc/en doc/en/_build/html -t changelog_towncrier_draft {posargs:} + # Retrieve possibly missing commits: + -git fetch --unshallow + -git fetch --tags + + sphinx-build -W --keep-going -b html doc/en doc/en/_build/html {posargs:} setenv = # Sphinx is not clean of this warning. PYTHONWARNDEFAULTENCODING= From 39b548e6ea0aa467969623b108d809604c0e12c0 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Mon, 17 Jun 2024 17:51:30 +0200 Subject: [PATCH 096/199] =?UTF-8?q?=F0=9F=93=9D=20Make=20"setuptools=20ent?= =?UTF-8?q?rypoint"=20term=20generic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This feature grew out of `setuptools` but the modern interface for extracting this information from the distribution package metadata is `importlib.metadata`. So the patch attempts to reflect this in the documentation messaging. Refs: * https://docs.python.org/3/library/importlib.metadata.html#entry-points * https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata * https://packaging.python.org/en/latest/specifications/entry-points/#entry-points --- doc/en/example/simple.rst | 6 +++--- doc/en/how-to/fixtures.rst | 2 +- doc/en/how-to/usage.rst | 2 +- doc/en/how-to/writing_plugins.rst | 15 +++++++++------ doc/en/reference/customize.rst | 2 +- doc/en/reference/reference.rst | 7 ++++--- src/_pytest/config/__init__.py | 7 ++++--- src/_pytest/helpconfig.py | 2 +- testing/test_assertion.py | 2 +- testing/test_helpconfig.py | 2 +- 10 files changed, 26 insertions(+), 21 deletions(-) diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst index dec1bed5f80..d4ace3f0413 100644 --- a/doc/en/example/simple.rst +++ b/doc/en/example/simple.rst @@ -212,7 +212,7 @@ the command line arguments before they get processed: .. code-block:: python - # setuptools plugin + # installable external plugin import sys @@ -1073,8 +1073,8 @@ Instead of freezing the pytest runner as a separate executable, you can make your frozen program work as the pytest runner by some clever argument handling during program startup. This allows you to have a single executable, which is usually more convenient. -Please note that the mechanism for plugin discovery used by pytest -(setuptools entry points) doesn't work with frozen executables so pytest +Please note that the mechanism for plugin discovery used by pytest (:ref:`entry +points `) doesn't work with frozen executables so pytest can't find any third party plugins automatically. To include third party plugins like ``pytest-timeout`` they must be imported explicitly and passed on to pytest.main. diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst index 35c3238dea7..ecd297867c5 100644 --- a/doc/en/how-to/fixtures.rst +++ b/doc/en/how-to/fixtures.rst @@ -1931,7 +1931,7 @@ The same applies for the test folder level obviously. Using fixtures from other projects ---------------------------------- -Usually projects that provide pytest support will use :ref:`entry points `, +Usually projects that provide pytest support will use :ref:`entry points `, so just installing those projects into an environment will make those fixtures available for use. In case you want to use fixtures from a project that does not use entry points, you can diff --git a/doc/en/how-to/usage.rst b/doc/en/how-to/usage.rst index fe46fad2db5..705fa009ed5 100644 --- a/doc/en/how-to/usage.rst +++ b/doc/en/how-to/usage.rst @@ -154,7 +154,7 @@ You can early-load plugins (internal and external) explicitly in the command-lin The option receives a ``name`` parameter, which can be: * A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable. -* The entry-point name of a plugin. This is the name passed to ``setuptools`` when the plugin is +* The entry-point name of a plugin. This is the name passed to ``importlib`` when the plugin is registered. For example to early-load the :pypi:`pytest-cov` plugin you can use:: pytest -p pytest_cov diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index 4bb6d183333..14e5194ce63 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -16,8 +16,8 @@ reporting by calling :ref:`well specified hooks ` of the followi * builtin plugins: loaded from pytest's internal ``_pytest`` directory. -* :ref:`external plugins `: modules discovered through - `setuptools entry points`_ +* :ref:`external plugins `: installed third-party modules discovered + through :ref:`entry points ` in their packaging metadata * `conftest.py plugins`_: modules auto-discovered in test directories @@ -42,7 +42,8 @@ Plugin discovery order at tool startup 3. by scanning the command line for the ``-p name`` option and loading the specified plugin. This happens before normal command-line parsing. -4. by loading all plugins registered through `setuptools entry points`_. +4. by loading all plugins registered through installed third-party package + :ref:`entry points `. 5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable. @@ -142,7 +143,8 @@ Making your plugin installable by others If you want to make your plugin externally available, you may define a so-called entry point for your distribution so that ``pytest`` finds your plugin module. Entry points are -a feature that is provided by :std:doc:`setuptools `. +a feature that is provided by :std:doc:`packaging tools +`. pytest looks up the ``pytest11`` entrypoint to discover its plugins, thus you can make your plugin available by defining @@ -265,8 +267,9 @@ of the variable will also be loaded as plugins, and so on. tests root directory is deprecated, and will raise a warning. This mechanism makes it easy to share fixtures within applications or even -external applications without the need to create external plugins using -the ``setuptools``'s entry point technique. +external applications without the need to create external plugins using the +:std:doc:`entry point packaging metadata +` technique. Plugins imported by :globalvar:`pytest_plugins` will also automatically be marked for assertion rewriting (see :func:`pytest.register_assert_rewrite`). diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst index cab1117266f..373223ec913 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -90,7 +90,7 @@ and can also be used to hold pytest configuration if they have a ``[pytest]`` se setup.cfg ~~~~~~~~~ -``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and `setuptools `__, and can also be used to hold pytest configuration +``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and :std:doc:`setuptools `, and can also be used to hold pytest configuration if they have a ``[tool:pytest]`` section. .. code-block:: ini diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 3675c7cb2ee..7c7b99d81c0 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -650,7 +650,7 @@ Reference to all hooks which can be implemented by :ref:`conftest.py files `. Only explicitly +specified plugins will be loaded. .. envvar:: PYTEST_PLUGINS diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 058aaa1ff30..39d79e475bf 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1290,7 +1290,8 @@ def _mark_plugins_for_rewrite(self, hook) -> None: self.pluginmanager.rewrite_hook = hook if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): - # We don't autoload from setuptools entry points, no need to continue. + # We don't autoload from distribution package entry points, + # no need to continue. return package_files = ( @@ -1381,8 +1382,8 @@ def _preparse(self, args: List[str], addopts: bool = True) -> None: self._consider_importhook(args) self.pluginmanager.consider_preparse(args, exclude_only=False) if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): - # Don't autoload from setuptools entry point. Only explicitly specified - # plugins are going to be loaded. + # Don't autoload from distribution package entry point. Only + # explicitly specified plugins are going to be loaded. self.pluginmanager.load_setuptools_entrypoints("pytest11") self.pluginmanager.consider_env() diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 68e0bd881a7..af6d3063974 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -243,7 +243,7 @@ def getpluginversioninfo(config: Config) -> List[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: - lines.append("setuptools registered plugins:") + lines.append("registered third-party plugins:") for plugin, dist in plugininfo: loc = getattr(plugin, "__file__", repr(plugin)) content = f"{dist.project_name}-{dist.version} at {loc}" diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 726235999b4..317dfa27d70 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -223,7 +223,7 @@ def test_installed_plugin_rewrite( ) -> None: monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) # Make sure the hook is installed early enough so that plugins - # installed via setuptools are rewritten. + # installed via distribution package are rewritten. pytester.mkdir("hampkg") contents = { "hampkg/__init__.py": """\ diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 4906ef5c8f0..967e01e9694 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -10,7 +10,7 @@ def test_version_verbose(pytester: Pytester, pytestconfig, monkeypatch) -> None: assert result.ret == 0 result.stdout.fnmatch_lines([f"*pytest*{pytest.__version__}*imported from*"]) if pytestconfig.pluginmanager.list_plugin_distinfo(): - result.stdout.fnmatch_lines(["*setuptools registered plugins:", "*at*"]) + result.stdout.fnmatch_lines(["*registered third-party plugins:", "*at*"]) def test_version_less_verbose(pytester: Pytester, pytestconfig, monkeypatch) -> None: From cb179472bb4d6fe3a164fcca7d6f4a9631e2a27e Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 10:18:11 +0200 Subject: [PATCH 097/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20change=20notes=20f?= =?UTF-8?q?or=20PR=20#12469?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12469.doc.rst | 6 ++++++ changelog/12469.improvement.rst | 4 ++++ 2 files changed, 10 insertions(+) create mode 100644 changelog/12469.doc.rst create mode 100644 changelog/12469.improvement.rst diff --git a/changelog/12469.doc.rst b/changelog/12469.doc.rst new file mode 100644 index 00000000000..2340315353c --- /dev/null +++ b/changelog/12469.doc.rst @@ -0,0 +1,6 @@ +The external plugin mentions in the documentation now avoid mentioning +:std:doc:`setuptools entry-points ` as the concept is +much more generic nowadays. Instead, the terminology of "external", +"installed", or "third-party" plugins (or packages) replaces that. + +-- by :user:`webknjaz` diff --git a/changelog/12469.improvement.rst b/changelog/12469.improvement.rst new file mode 100644 index 00000000000..a90fb1e6610 --- /dev/null +++ b/changelog/12469.improvement.rst @@ -0,0 +1,4 @@ +The console output now uses the "third-party plugins" terminology, +replacing the previously established but confusing and outdated +reference to :std:doc:`setuptools ` +-- by :user:`webknjaz`. From 9295f9ffffc11595cc4d7241008f60783f1f46c2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 17 Jun 2024 17:01:28 +0200 Subject: [PATCH 098/199] RFC: from __future__ import annotations + migrate --- bench/bench.py | 2 + bench/bench_argcomplete.py | 2 + bench/empty.py | 3 + bench/manyparam.py | 2 + bench/skip.py | 2 + bench/unit_test.py | 2 + bench/xunit.py | 3 + doc/en/conf.py | 4 +- doc/en/conftest.py | 3 + doc/en/example/assertion/failure_demo.py | 2 + .../global_testmodule_config/conftest.py | 2 + .../test_hello_world.py | 3 + doc/en/example/assertion/test_failures.py | 2 + .../assertion/test_setup_flow_example.py | 3 + doc/en/example/conftest.py | 3 + doc/en/example/customdirectory/conftest.py | 2 + .../customdirectory/tests/test_first.py | 3 + .../customdirectory/tests/test_second.py | 3 + .../customdirectory/tests/test_third.py | 3 + .../fixtures/test_fixtures_order_autouse.py | 2 + ..._fixtures_order_autouse_multiple_scopes.py | 2 + ...est_fixtures_order_autouse_temp_effects.py | 2 + .../test_fixtures_order_dependencies.py | 2 + .../fixtures/test_fixtures_order_scope.py | 2 + .../test_fixtures_request_different_scope.py | 2 + doc/en/example/multipython.py | 2 + doc/en/example/nonpython/conftest.py | 2 + doc/en/example/pythoncollection.py | 1 + doc/en/example/xfail_demo.py | 2 + extra/get_issues.py | 2 + pyproject.toml | 9 + scripts/generate-gh-release-notes.py | 2 + scripts/prepare-release-pr.py | 2 + scripts/release.py | 2 + scripts/update-plugin-list.py | 2 + src/_pytest/__init__.py | 3 + src/_pytest/_argcomplete.py | 8 +- src/_pytest/_code/__init__.py | 2 + src/_pytest/_code/code.py | 225 ++++++++---------- src/_pytest/_code/source.py | 36 ++- src/_pytest/_io/__init__.py | 2 + src/_pytest/_io/pprint.py | 69 +++--- src/_pytest/_io/saferepr.py | 7 +- src/_pytest/_io/terminalwriter.py | 17 +- src/_pytest/_io/wcwidth.py | 2 + src/_pytest/assertion/__init__.py | 14 +- src/_pytest/assertion/rewrite.py | 114 +++++---- src/_pytest/assertion/truncate.py | 17 +- src/_pytest/assertion/util.py | 48 ++-- src/_pytest/cacheprovider.py | 41 ++-- src/_pytest/capture.py | 59 ++--- src/_pytest/config/__init__.py | 150 ++++++------ src/_pytest/config/argparsing.py | 87 ++++--- src/_pytest/config/exceptions.py | 2 + src/_pytest/config/findpaths.py | 27 +-- src/_pytest/debugging.py | 29 +-- src/_pytest/deprecated.py | 2 + src/_pytest/doctest.py | 82 +++---- src/_pytest/faulthandler.py | 2 + src/_pytest/fixtures.py | 222 ++++++++--------- src/_pytest/freeze_support.py | 8 +- src/_pytest/helpconfig.py | 13 +- src/_pytest/hookspec.py | 173 +++++++------- src/_pytest/junitxml.py | 49 ++-- src/_pytest/legacypath.py | 29 +-- src/_pytest/logging.py | 60 +++-- src/_pytest/main.py | 115 +++++---- src/_pytest/mark/__init__.py | 24 +- src/_pytest/mark/expression.py | 7 +- src/_pytest/mark/structures.py | 108 ++++----- src/_pytest/monkeypatch.py | 32 ++- src/_pytest/nodes.py | 120 +++++----- src/_pytest/outcomes.py | 21 +- src/_pytest/pastebin.py | 5 +- src/_pytest/pathlib.py | 69 +++--- src/_pytest/pytester.py | 184 +++++++------- src/_pytest/pytester_assertions.py | 18 +- src/_pytest/python.py | 147 ++++++------ src/_pytest/python_api.py | 57 +++-- src/_pytest/python_path.py | 2 + src/_pytest/recwarn.py | 69 +++--- src/_pytest/reports.py | 118 +++++---- src/_pytest/runner.py | 67 +++--- src/_pytest/scope.py | 13 +- src/_pytest/setuponly.py | 6 +- src/_pytest/setupplan.py | 7 +- src/_pytest/skipping.py | 14 +- src/_pytest/stash.py | 8 +- src/_pytest/stepwise.py | 11 +- src/_pytest/terminal.py | 132 +++++----- src/_pytest/threadexception.py | 23 +- src/_pytest/timing.py | 2 + src/_pytest/tmpdir.py | 20 +- src/_pytest/unittest.py | 34 +-- src/_pytest/unraisableexception.py | 23 +- src/_pytest/warning_types.py | 7 +- src/_pytest/warnings.py | 7 +- src/py.py | 2 + src/pytest/__init__.py | 2 + src/pytest/__main__.py | 2 + testing/_py/test_local.py | 2 + testing/acceptance_test.py | 2 + testing/code/test_code.py | 2 + testing/conftest.py | 11 +- testing/deprecated_test.py | 2 + .../acceptance/fixture_mock_integration.py | 2 + .../collect_init_tests/tests/__init__.py | 3 + .../collect_init_tests/tests/test_foo.py | 3 + .../package_infinite_recursion/conftest.py | 3 + .../tests/test_basic.py | 3 + .../package_init_given_as_arg/pkg/__init__.py | 3 + .../package_init_given_as_arg/pkg/test_foo.py | 3 + .../config/collect_pytest_prefix/conftest.py | 3 + .../config/collect_pytest_prefix/test_foo.py | 3 + .../conftest_usageerror/conftest.py | 3 + .../customdirectory/conftest.py | 2 + .../customdirectory/tests/test_first.py | 3 + .../customdirectory/tests/test_second.py | 3 + .../customdirectory/tests/test_third.py | 3 + .../dataclasses/test_compare_dataclasses.py | 2 + ...ompare_dataclasses_field_comparison_off.py | 2 + .../test_compare_dataclasses_verbose.py | 2 + ...test_compare_dataclasses_with_custom_eq.py | 2 + .../dataclasses/test_compare_initvar.py | 2 + .../test_compare_recursive_dataclasses.py | 2 + .../test_compare_two_different_dataclasses.py | 2 + .../doctest/main_py/__main__.py | 3 + .../doctest/main_py/test_normal_module.py | 3 + .../fixtures/custom_item/conftest.py | 2 + .../fixtures/custom_item/foo/test_foo.py | 3 + .../sub1/conftest.py | 2 + .../sub1/test_in_sub1.py | 3 + .../sub2/conftest.py | 2 + .../sub2/test_in_sub2.py | 3 + .../test_detect_recursive_dependency_error.py | 2 + .../conftest.py | 2 + .../pkg/conftest.py | 2 + .../pkg/test_spam.py | 3 + .../conftest.py | 2 + .../test_extend_fixture_conftest_module.py | 2 + .../test_extend_fixture_module_class.py | 2 + .../fill_fixtures/test_funcarg_basic.py | 2 + .../test_funcarg_lookup_classlevel.py | 2 + .../test_funcarg_lookup_modulelevel.py | 2 + .../fill_fixtures/test_funcarg_lookupfails.py | 2 + .../fixtures/test_fixture_named_request.py | 2 + .../fixtures/test_getfixturevalue_dynamic.py | 2 + .../conftest.py | 2 + .../test_hello.py | 3 + testing/example_scripts/issue_519.py | 6 +- .../test_marks_as_keywords.py | 2 + .../collect_stats/generate_folders.py | 2 + .../collect_stats/template_test.py | 3 + .../tmpdir/tmp_path_fixture.py | 2 + ...test_parametrized_fixture_error_message.py | 2 + .../unittest/test_setup_skip.py | 2 + .../unittest/test_setup_skip_class.py | 2 + .../unittest/test_setup_skip_module.py | 2 + .../unittest/test_unittest_asyncio.py | 5 +- .../unittest/test_unittest_asynctest.py | 5 +- .../unittest/test_unittest_plain_async.py | 2 + .../test_group_warnings_by_message.py | 2 + .../test_1.py | 2 + .../test_2.py | 2 + testing/examples/test_issue519.py | 2 + testing/freeze/create_executable.py | 3 + testing/freeze/runtests_script.py | 3 + testing/freeze/tests/test_trivial.py | 3 + testing/freeze/tox_run.py | 3 + testing/io/test_pprint.py | 2 + testing/io/test_saferepr.py | 2 + testing/io/test_terminalwriter.py | 5 +- testing/io/test_wcwidth.py | 2 + testing/logging/test_fixture.py | 2 + testing/logging/test_formatter.py | 2 + testing/logging/test_reporting.py | 2 + testing/plugins_integration/bdd_wallet.py | 2 + .../plugins_integration/django_settings.py | 3 + .../pytest_anyio_integration.py | 2 + .../pytest_asyncio_integration.py | 2 + .../pytest_mock_integration.py | 3 + .../pytest_rerunfailures_integration.py | 2 + .../pytest_trio_integration.py | 2 + .../pytest_twisted_integration.py | 2 + .../plugins_integration/simple_integration.py | 2 + testing/python/approx.py | 7 +- testing/python/collect.py | 5 +- testing/python/fixtures.py | 4 +- testing/python/integration.py | 2 + testing/python/metafunc.py | 22 +- testing/python/raises.py | 2 + testing/python/show_fixtures_per_test.py | 2 + testing/test_argcomplete.py | 2 + testing/test_assertion.py | 14 +- testing/test_assertrewrite.py | 18 +- testing/test_cacheprovider.py | 8 +- testing/test_capture.py | 2 + testing/test_collection.py | 8 +- testing/test_compat.py | 5 +- testing/test_config.py | 17 +- testing/test_conftest.py | 11 +- testing/test_debugging.py | 9 +- testing/test_doctest.py | 5 +- testing/test_entry_points.py | 2 + testing/test_error_diffs.py | 2 + testing/test_faulthandler.py | 2 + testing/test_findpaths.py | 2 + testing/test_helpconfig.py | 2 + testing/test_junitxml.py | 18 +- testing/test_legacypath.py | 2 + testing/test_link_resolve.py | 2 + testing/test_main.py | 5 +- testing/test_mark.py | 16 +- testing/test_mark_expression.py | 2 + testing/test_meta.py | 5 +- testing/test_monkeypatch.py | 10 +- testing/test_nodes.py | 5 +- testing/test_parseopt.py | 2 + testing/test_pastebin.py | 8 +- testing/test_pathlib.py | 12 +- testing/test_pluginmanager.py | 5 +- testing/test_pytester.py | 7 +- testing/test_python_path.py | 8 +- testing/test_recwarn.py | 12 +- testing/test_reports.py | 7 +- testing/test_runner.py | 14 +- testing/test_runner_xunit.py | 4 +- testing/test_scope.py | 2 + testing/test_session.py | 2 + testing/test_setuponly.py | 2 + testing/test_setupplan.py | 2 + testing/test_skipping.py | 2 + testing/test_stash.py | 2 + testing/test_stepwise.py | 2 + testing/test_terminal.py | 9 +- testing/test_threadexception.py | 2 + testing/test_tmpdir.py | 8 +- testing/test_unittest.py | 9 +- testing/test_unraisableexception.py | 2 + testing/test_warning_types.py | 2 + testing/test_warnings.py | 13 +- testing/typing_checks.py | 2 + 242 files changed, 1961 insertions(+), 1777 deletions(-) diff --git a/bench/bench.py b/bench/bench.py index 0bb13c75a15..139c292ecd8 100644 --- a/bench/bench.py +++ b/bench/bench.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys diff --git a/bench/bench_argcomplete.py b/bench/bench_argcomplete.py index 459a12f9314..468c59217df 100644 --- a/bench/bench_argcomplete.py +++ b/bench/bench_argcomplete.py @@ -2,6 +2,8 @@ # 2.7.5 3.3.2 # FilesCompleter 75.1109 69.2116 # FastFilesCompleter 0.7383 1.0760 +from __future__ import annotations + import timeit diff --git a/bench/empty.py b/bench/empty.py index 4e7371b6f80..35abeef4140 100644 --- a/bench/empty.py +++ b/bench/empty.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + for i in range(1000): exec("def test_func_%d(): pass" % i) diff --git a/bench/manyparam.py b/bench/manyparam.py index 1226c73bd9c..579f7b2488d 100644 --- a/bench/manyparam.py +++ b/bench/manyparam.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/bench/skip.py b/bench/skip.py index fd5c292d92c..9145cc0ceed 100644 --- a/bench/skip.py +++ b/bench/skip.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/bench/unit_test.py b/bench/unit_test.py index d3db111e1ae..0f106e16b6c 100644 --- a/bench/unit_test.py +++ b/bench/unit_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from unittest import TestCase # noqa: F401 diff --git a/bench/xunit.py b/bench/xunit.py index 3a77dcdce42..31ab432441c 100644 --- a/bench/xunit.py +++ b/bench/xunit.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + for i in range(5000): exec( f""" diff --git a/doc/en/conf.py b/doc/en/conf.py index 3e87003c510..670d6adf74b 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -15,6 +15,8 @@ # # The full version, including alpha/beta/rc tags. # The short X.Y version. +from __future__ import annotations + import os from pathlib import Path import shutil @@ -455,7 +457,7 @@ } -def setup(app: "sphinx.application.Sphinx") -> None: +def setup(app: sphinx.application.Sphinx) -> None: app.add_crossref_type( "fixture", "fixture", diff --git a/doc/en/conftest.py b/doc/en/conftest.py index 1a62e1b5df5..50e43a0b544 100644 --- a/doc/en/conftest.py +++ b/doc/en/conftest.py @@ -1 +1,4 @@ +from __future__ import annotations + + collect_ignore = ["conf.py"] diff --git a/doc/en/example/assertion/failure_demo.py b/doc/en/example/assertion/failure_demo.py index f7a9c279426..dd1485b0b21 100644 --- a/doc/en/example/assertion/failure_demo.py +++ b/doc/en/example/assertion/failure_demo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from pytest import raises diff --git a/doc/en/example/assertion/global_testmodule_config/conftest.py b/doc/en/example/assertion/global_testmodule_config/conftest.py index 4aa7ec23bd1..835726473ba 100644 --- a/doc/en/example/assertion/global_testmodule_config/conftest.py +++ b/doc/en/example/assertion/global_testmodule_config/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import pytest diff --git a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py index a31a601a1ce..e3c927316f9 100644 --- a/doc/en/example/assertion/global_testmodule_config/test_hello_world.py +++ b/doc/en/example/assertion/global_testmodule_config/test_hello_world.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + hello = "world" diff --git a/doc/en/example/assertion/test_failures.py b/doc/en/example/assertion/test_failures.py index 19d862f60b7..17373f62213 100644 --- a/doc/en/example/assertion/test_failures.py +++ b/doc/en/example/assertion/test_failures.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os.path import shutil diff --git a/doc/en/example/assertion/test_setup_flow_example.py b/doc/en/example/assertion/test_setup_flow_example.py index 0e7eded06b6..fe11c2bf3f2 100644 --- a/doc/en/example/assertion/test_setup_flow_example.py +++ b/doc/en/example/assertion/test_setup_flow_example.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + def setup_module(module): module.TestStateFullThing.classcount = 0 diff --git a/doc/en/example/conftest.py b/doc/en/example/conftest.py index 66e70f14dd7..21c9a489961 100644 --- a/doc/en/example/conftest.py +++ b/doc/en/example/conftest.py @@ -1 +1,4 @@ +from __future__ import annotations + + collect_ignore = ["nonpython", "customdirectory"] diff --git a/doc/en/example/customdirectory/conftest.py b/doc/en/example/customdirectory/conftest.py index b2f68dba41a..ea922e04723 100644 --- a/doc/en/example/customdirectory/conftest.py +++ b/doc/en/example/customdirectory/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import json import pytest diff --git a/doc/en/example/customdirectory/tests/test_first.py b/doc/en/example/customdirectory/tests/test_first.py index 0a78de59945..9953dd37785 100644 --- a/doc/en/example/customdirectory/tests/test_first.py +++ b/doc/en/example/customdirectory/tests/test_first.py @@ -1,3 +1,6 @@ # content of test_first.py +from __future__ import annotations + + def test_1(): pass diff --git a/doc/en/example/customdirectory/tests/test_second.py b/doc/en/example/customdirectory/tests/test_second.py index eed724a7d96..df264f48b3b 100644 --- a/doc/en/example/customdirectory/tests/test_second.py +++ b/doc/en/example/customdirectory/tests/test_second.py @@ -1,3 +1,6 @@ # content of test_second.py +from __future__ import annotations + + def test_2(): pass diff --git a/doc/en/example/customdirectory/tests/test_third.py b/doc/en/example/customdirectory/tests/test_third.py index 61cf59dc16c..b8b072dd770 100644 --- a/doc/en/example/customdirectory/tests/test_third.py +++ b/doc/en/example/customdirectory/tests/test_third.py @@ -1,3 +1,6 @@ # content of test_third.py +from __future__ import annotations + + def test_3(): pass diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse.py b/doc/en/example/fixtures/test_fixtures_order_autouse.py index ec282ab4b2b..04cbc268b7f 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py index de0c2642793..828fa4cf6d6 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_multiple_scopes.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py index ba01ad32f57..ebd5d10f5bb 100644 --- a/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py +++ b/doc/en/example/fixtures/test_fixtures_order_autouse_temp_effects.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py index e76e3f93c62..1c59f010341 100644 --- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_order_scope.py b/doc/en/example/fixtures/test_fixtures_order_scope.py index 5d9487cab34..4b4260fbdcd 100644 --- a/doc/en/example/fixtures/test_fixtures_order_scope.py +++ b/doc/en/example/fixtures/test_fixtures_order_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/fixtures/test_fixtures_request_different_scope.py b/doc/en/example/fixtures/test_fixtures_request_different_scope.py index 00e2e46d845..dee61f8c4d7 100644 --- a/doc/en/example/fixtures/test_fixtures_request_different_scope.py +++ b/doc/en/example/fixtures/test_fixtures_request_different_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/doc/en/example/multipython.py b/doc/en/example/multipython.py index 861ae9e528d..f54524213bc 100644 --- a/doc/en/example/multipython.py +++ b/doc/en/example/multipython.py @@ -1,6 +1,8 @@ """Module containing a parametrized tests testing cross-python serialization via the pickle module.""" +from __future__ import annotations + import shutil import subprocess import textwrap diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index e969e3e2518..b7bdc77a004 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -1,4 +1,6 @@ # content of conftest.py +from __future__ import annotations + import pytest diff --git a/doc/en/example/pythoncollection.py b/doc/en/example/pythoncollection.py index 8742526a191..7595ee02ca4 100644 --- a/doc/en/example/pythoncollection.py +++ b/doc/en/example/pythoncollection.py @@ -1,5 +1,6 @@ # run this with $ pytest --collect-only test_collectonly.py # +from __future__ import annotations def test_function(): diff --git a/doc/en/example/xfail_demo.py b/doc/en/example/xfail_demo.py index 1040c89298d..4999e15f238 100644 --- a/doc/en/example/xfail_demo.py +++ b/doc/en/example/xfail_demo.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest diff --git a/extra/get_issues.py b/extra/get_issues.py index 64e859e0c12..851d2f6d7f3 100644 --- a/extra/get_issues.py +++ b/extra/get_issues.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json from pathlib import Path import sys diff --git a/pyproject.toml b/pyproject.toml index 0627c94c732..734c508ca70 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,7 @@ lint.select = [ "D", # pydocstyle "E", # pycodestyle "F", # pyflakes + "FA100", # add future annotations "I", # isort "PGH004", # pygrep-hooks - Use specific rule codes when using noqa "PIE", # flake8-pie @@ -155,6 +156,10 @@ lint.per-file-ignores."src/_pytest/_version.py" = [ lint.per-file-ignores."testing/python/approx.py" = [ "B015", ] +lint.extend-safe-fixes = [ + "UP006", + "UP007", +] lint.isort.combine-as-imports = true lint.isort.force-single-line = true lint.isort.force-sort-within-sections = true @@ -164,9 +169,13 @@ lint.isort.known-local-folder = [ ] lint.isort.lines-after-imports = 2 lint.isort.order-by-type = false +lint.isort.required-imports = [ + "from __future__ import annotations", +] # In order to be able to format for 88 char in ruff format lint.pycodestyle.max-line-length = 120 lint.pydocstyle.convention = "pep257" +lint.pyupgrade.keep-runtime-typing = false [tool.pylint.main] # Maximum number of characters on a single line. diff --git a/scripts/generate-gh-release-notes.py b/scripts/generate-gh-release-notes.py index 4222702d5d4..7f195ba1e0a 100644 --- a/scripts/generate-gh-release-notes.py +++ b/scripts/generate-gh-release-notes.py @@ -9,6 +9,8 @@ Requires Python3.6+. """ +from __future__ import annotations + from pathlib import Path import re import sys diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py index 7dabbd3b328..49cb2110639 100644 --- a/scripts/prepare-release-pr.py +++ b/scripts/prepare-release-pr.py @@ -14,6 +14,8 @@ `pytest bot ` commit author. """ +from __future__ import annotations + import argparse from pathlib import Path import re diff --git a/scripts/release.py b/scripts/release.py index bcbc4262d08..545919cd60b 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,6 +1,8 @@ # mypy: disallow-untyped-defs """Invoke development tasks.""" +from __future__ import annotations + import argparse import os from pathlib import Path diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py index 6831fc984dd..75df0ddba40 100644 --- a/scripts/update-plugin-list.py +++ b/scripts/update-plugin-list.py @@ -1,4 +1,6 @@ # mypy: disallow-untyped-defs +from __future__ import annotations + import datetime import pathlib import re diff --git a/src/_pytest/__init__.py b/src/_pytest/__init__.py index b694a5f244a..8eb8ec9605c 100644 --- a/src/_pytest/__init__.py +++ b/src/_pytest/__init__.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __all__ = ["__version__", "version_tuple"] try: diff --git a/src/_pytest/_argcomplete.py b/src/_pytest/_argcomplete.py index c24f925202a..59426ef949e 100644 --- a/src/_pytest/_argcomplete.py +++ b/src/_pytest/_argcomplete.py @@ -62,13 +62,13 @@ global argcomplete script). """ +from __future__ import annotations + import argparse from glob import glob import os import sys from typing import Any -from typing import List -from typing import Optional class FastFilesCompleter: @@ -77,7 +77,7 @@ class FastFilesCompleter: def __init__(self, directories: bool = True) -> None: self.directories = directories - def __call__(self, prefix: str, **kwargs: Any) -> List[str]: + def __call__(self, prefix: str, **kwargs: Any) -> list[str]: # Only called on non option completions. if os.sep in prefix[1:]: prefix_dir = len(os.path.dirname(prefix) + os.sep) @@ -104,7 +104,7 @@ def __call__(self, prefix: str, **kwargs: Any) -> List[str]: import argcomplete.completers except ImportError: sys.exit(-1) - filescompleter: Optional[FastFilesCompleter] = FastFilesCompleter() + filescompleter: FastFilesCompleter | None = FastFilesCompleter() def try_argcomplete(parser: argparse.ArgumentParser) -> None: argcomplete.autocomplete(parser, always_complete_options=False) diff --git a/src/_pytest/_code/__init__.py b/src/_pytest/_code/__init__.py index b0a418e9555..0bfde42604d 100644 --- a/src/_pytest/_code/__init__.py +++ b/src/_pytest/_code/__init__.py @@ -1,5 +1,7 @@ """Python inspection/code generation API.""" +from __future__ import annotations + from .code import Code from .code import ExceptionInfo from .code import filter_traceback diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index b6e06340dbe..750195b94e9 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import ast import dataclasses import inspect @@ -17,7 +19,6 @@ from typing import Any from typing import Callable from typing import ClassVar -from typing import Dict from typing import Final from typing import final from typing import Generic @@ -25,16 +26,11 @@ from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import overload from typing import Pattern from typing import Sequence -from typing import Set from typing import SupportsIndex -from typing import Tuple -from typing import Type from typing import TypeVar -from typing import Union import pluggy @@ -57,6 +53,10 @@ TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] +EXCEPTION_OR_MORE = type[Exception] | tuple[type[Exception], ...] + +type_alias = type # to sidestep shadowing + class Code: """Wrapper around Python code objects.""" @@ -67,7 +67,7 @@ def __init__(self, obj: CodeType) -> None: self.raw = obj @classmethod - def from_function(cls, obj: object) -> "Code": + def from_function(cls, obj: object) -> Code: return cls(getrawcode(obj)) def __eq__(self, other): @@ -85,7 +85,7 @@ def name(self) -> str: return self.raw.co_name @property - def path(self) -> Union[Path, str]: + def path(self) -> Path | str: """Return a path object pointing to source code, or an ``str`` in case of ``OSError`` / non-existing file.""" if not self.raw.co_filename: @@ -102,17 +102,17 @@ def path(self) -> Union[Path, str]: return self.raw.co_filename @property - def fullsource(self) -> Optional["Source"]: + def fullsource(self) -> Source | None: """Return a _pytest._code.Source object for the full source file of the code.""" full, _ = findsource(self.raw) return full - def source(self) -> "Source": + def source(self) -> Source: """Return a _pytest._code.Source object for the code object's source only.""" # return source only for that part of code return Source(self.raw) - def getargs(self, var: bool = False) -> Tuple[str, ...]: + def getargs(self, var: bool = False) -> tuple[str, ...]: """Return a tuple with the argument names for the code object. If 'var' is set True also return the names of the variable and @@ -141,11 +141,11 @@ def lineno(self) -> int: return self.raw.f_lineno - 1 @property - def f_globals(self) -> Dict[str, Any]: + def f_globals(self) -> dict[str, Any]: return self.raw.f_globals @property - def f_locals(self) -> Dict[str, Any]: + def f_locals(self) -> dict[str, Any]: return self.raw.f_locals @property @@ -153,7 +153,7 @@ def code(self) -> Code: return Code(self.raw.f_code) @property - def statement(self) -> "Source": + def statement(self) -> Source: """Statement this frame is at.""" if self.code.fullsource is None: return Source("") @@ -197,14 +197,14 @@ class TracebackEntry: def __init__( self, rawentry: TracebackType, - repr_style: Optional['Literal["short", "long"]'] = None, + repr_style: Literal["short", "long"] | None = None, ) -> None: self._rawentry: Final = rawentry self._repr_style: Final = repr_style def with_repr_style( - self, repr_style: Optional['Literal["short", "long"]'] - ) -> "TracebackEntry": + self, repr_style: Literal["short", "long"] | None + ) -> TracebackEntry: return TracebackEntry(self._rawentry, repr_style) @property @@ -223,19 +223,19 @@ def __repr__(self) -> str: return "" % (self.frame.code.path, self.lineno + 1) @property - def statement(self) -> "Source": + def statement(self) -> Source: """_pytest._code.Source object for the current statement.""" source = self.frame.code.fullsource assert source is not None return source.getstatement(self.lineno) @property - def path(self) -> Union[Path, str]: + def path(self) -> Path | str: """Path to the source code.""" return self.frame.code.path @property - def locals(self) -> Dict[str, Any]: + def locals(self) -> dict[str, Any]: """Locals of underlying frame.""" return self.frame.f_locals @@ -243,8 +243,8 @@ def getfirstlinesource(self) -> int: return self.frame.code.firstlineno def getsource( - self, astcache: Optional[Dict[Union[str, Path], ast.AST]] = None - ) -> Optional["Source"]: + self, astcache: dict[str | Path, ast.AST] | None = None + ) -> Source | None: """Return failing source code.""" # we use the passed in astcache to not reparse asttrees # within exception info printing @@ -270,7 +270,7 @@ def getsource( source = property(getsource) - def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: + def ishidden(self, excinfo: ExceptionInfo[BaseException] | None) -> bool: """Return True if the current frame has a var __tracebackhide__ resolving to True. @@ -279,9 +279,7 @@ def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool: Mostly for internal use. """ - tbh: Union[bool, Callable[[Optional[ExceptionInfo[BaseException]]], bool]] = ( - False - ) + tbh: bool | Callable[[ExceptionInfo[BaseException] | None], bool] = False for maybe_ns_dct in (self.frame.f_locals, self.frame.f_globals): # in normal cases, f_locals and f_globals are dictionaries # however via `exec(...)` / `eval(...)` they can be other types @@ -326,13 +324,13 @@ class Traceback(List[TracebackEntry]): def __init__( self, - tb: Union[TracebackType, Iterable[TracebackEntry]], + tb: TracebackType | Iterable[TracebackEntry], ) -> None: """Initialize from given python traceback object and ExceptionInfo.""" if isinstance(tb, TracebackType): def f(cur: TracebackType) -> Iterable[TracebackEntry]: - cur_: Optional[TracebackType] = cur + cur_: TracebackType | None = cur while cur_ is not None: yield TracebackEntry(cur_) cur_ = cur_.tb_next @@ -343,11 +341,11 @@ def f(cur: TracebackType) -> Iterable[TracebackEntry]: def cut( self, - path: Optional[Union["os.PathLike[str]", str]] = None, - lineno: Optional[int] = None, - firstlineno: Optional[int] = None, - excludepath: Optional["os.PathLike[str]"] = None, - ) -> "Traceback": + path: os.PathLike[str] | str | None = None, + lineno: int | None = None, + firstlineno: int | None = None, + excludepath: os.PathLike[str] | None = None, + ) -> Traceback: """Return a Traceback instance wrapping part of this Traceback. By providing any combination of path, lineno and firstlineno, the @@ -378,14 +376,12 @@ def cut( return self @overload - def __getitem__(self, key: "SupportsIndex") -> TracebackEntry: ... + def __getitem__(self, key: SupportsIndex) -> TracebackEntry: ... @overload - def __getitem__(self, key: slice) -> "Traceback": ... + def __getitem__(self, key: slice) -> Traceback: ... - def __getitem__( - self, key: Union["SupportsIndex", slice] - ) -> Union[TracebackEntry, "Traceback"]: + def __getitem__(self, key: SupportsIndex | slice) -> TracebackEntry | Traceback: if isinstance(key, slice): return self.__class__(super().__getitem__(key)) else: @@ -393,12 +389,9 @@ def __getitem__( def filter( self, - excinfo_or_fn: Union[ - "ExceptionInfo[BaseException]", - Callable[[TracebackEntry], bool], - ], + excinfo_or_fn: ExceptionInfo[BaseException] | Callable[[TracebackEntry], bool], /, - ) -> "Traceback": + ) -> Traceback: """Return a Traceback instance with certain items removed. If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s @@ -414,10 +407,10 @@ def filter( fn = excinfo_or_fn return Traceback(filter(fn, self)) - def recursionindex(self) -> Optional[int]: + def recursionindex(self) -> int | None: """Return the index of the frame/TracebackEntry where recursion originates if appropriate, None if no recursion occurred.""" - cache: Dict[Tuple[Any, int, int], List[Dict[str, Any]]] = {} + cache: dict[tuple[Any, int, int], list[dict[str, Any]]] = {} for i, entry in enumerate(self): # id for the code.raw is needed to work around # the strange metaprogramming in the decorator lib from pypi @@ -445,15 +438,15 @@ class ExceptionInfo(Generic[E]): _assert_start_repr: ClassVar = "AssertionError('assert " - _excinfo: Optional[Tuple[Type["E"], "E", TracebackType]] + _excinfo: tuple[type[E], E, TracebackType] | None _striptext: str - _traceback: Optional[Traceback] + _traceback: Traceback | None def __init__( self, - excinfo: Optional[Tuple[Type["E"], "E", TracebackType]], + excinfo: tuple[type[E], E, TracebackType] | None, striptext: str = "", - traceback: Optional[Traceback] = None, + traceback: Traceback | None = None, *, _ispytest: bool = False, ) -> None: @@ -469,8 +462,8 @@ def from_exception( # 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]": + exprinfo: str | None = None, + ) -> ExceptionInfo[E]: """Return an ExceptionInfo for an existing exception. The exception must have a non-``None`` ``__traceback__`` attribute, @@ -495,9 +488,9 @@ def from_exception( @classmethod def from_exc_info( cls, - exc_info: Tuple[Type[E], E, TracebackType], - exprinfo: Optional[str] = None, - ) -> "ExceptionInfo[E]": + exc_info: tuple[type[E], E, TracebackType], + exprinfo: str | None = 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): @@ -510,9 +503,7 @@ def from_exc_info( return cls(exc_info, _striptext, _ispytest=True) @classmethod - def from_current( - cls, exprinfo: Optional[str] = None - ) -> "ExceptionInfo[BaseException]": + def from_current(cls, exprinfo: str | None = None) -> ExceptionInfo[BaseException]: """Return an ExceptionInfo matching the current traceback. .. warning:: @@ -532,17 +523,17 @@ def from_current( return ExceptionInfo.from_exc_info(exc_info, exprinfo) @classmethod - def for_later(cls) -> "ExceptionInfo[E]": + def for_later(cls) -> ExceptionInfo[E]: """Return an unfilled ExceptionInfo.""" return cls(None, _ispytest=True) - def fill_unfilled(self, exc_info: Tuple[Type[E], E, TracebackType]) -> None: + def fill_unfilled(self, exc_info: tuple[type[E], E, TracebackType]) -> None: """Fill an unfilled ExceptionInfo created with ``for_later()``.""" assert self._excinfo is None, "ExceptionInfo was already filled" self._excinfo = exc_info @property - def type(self) -> Type[E]: + def type(self) -> type[E]: """The exception class.""" assert ( self._excinfo is not None @@ -605,16 +596,14 @@ def exconly(self, tryshort: bool = False) -> str: text = text[len(self._striptext) :] return text - def errisinstance( - self, exc: Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ) -> bool: + def errisinstance(self, exc: EXCEPTION_OR_MORE) -> bool: """Return True if the exception is an instance of exc. Consider using ``isinstance(excinfo.value, exc)`` instead. """ return isinstance(self.value, exc) - def _getreprcrash(self) -> Optional["ReprFileLocation"]: + def _getreprcrash(self) -> ReprFileLocation | None: # 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): @@ -630,14 +619,12 @@ def getrepr( showlocals: bool = False, style: TracebackStyle = "long", abspath: bool = False, - tbfilter: Union[ - bool, Callable[["ExceptionInfo[BaseException]"], Traceback] - ] = True, + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, funcargs: bool = False, truncate_locals: bool = True, truncate_args: bool = True, chain: bool = True, - ) -> Union["ReprExceptionInfo", "ExceptionChainRepr"]: + ) -> ReprExceptionInfo | ExceptionChainRepr: """Return str()able representation of this exception info. :param bool showlocals: @@ -719,7 +706,7 @@ def _stringify_exception(self, exc: BaseException) -> str: ] ) - def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": + def match(self, regexp: str | Pattern[str]) -> Literal[True]: """Check whether the regular expression `regexp` matches the string representation of the exception using :func:`python:re.search`. @@ -737,9 +724,9 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": def _group_contains( self, exc_group: BaseExceptionGroup[BaseException], - expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], - match: Union[str, Pattern[str], None], - target_depth: Optional[int] = None, + expected_exception: EXCEPTION_OR_MORE, + match: str | Pattern[str] | None, + target_depth: int | None = None, current_depth: int = 1, ) -> bool: """Return `True` if a `BaseExceptionGroup` contains a matching exception.""" @@ -766,10 +753,10 @@ def _group_contains( def group_contains( self, - expected_exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]], + expected_exception: EXCEPTION_OR_MORE, *, - match: Union[str, Pattern[str], None] = None, - depth: Optional[int] = None, + match: str | Pattern[str] | None = None, + depth: int | None = None, ) -> bool: """Check whether a captured exception group contains a matching exception. @@ -811,16 +798,16 @@ class FormattedExcinfo: showlocals: bool = False style: TracebackStyle = "long" abspath: bool = True - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True funcargs: bool = False truncate_locals: bool = True truncate_args: bool = True chain: bool = True - astcache: Dict[Union[str, Path], ast.AST] = dataclasses.field( + astcache: dict[str | Path, ast.AST] = dataclasses.field( default_factory=dict, init=False, repr=False ) - def _getindent(self, source: "Source") -> int: + def _getindent(self, source: Source) -> int: # Figure out indent for the given source. try: s = str(source.getstatement(len(source) - 1)) @@ -835,13 +822,13 @@ def _getindent(self, source: "Source") -> int: return 0 return 4 + (len(s) - len(s.lstrip())) - def _getentrysource(self, entry: TracebackEntry) -> Optional["Source"]: + def _getentrysource(self, entry: TracebackEntry) -> Source | None: source = entry.getsource(self.astcache) if source is not None: source = source.deindent() return source - def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: + def repr_args(self, entry: TracebackEntry) -> ReprFuncArgs | None: if self.funcargs: args = [] for argname, argvalue in entry.frame.getargs(var=True): @@ -855,11 +842,11 @@ def repr_args(self, entry: TracebackEntry) -> Optional["ReprFuncArgs"]: def get_source( self, - source: Optional["Source"], + source: Source | None, line_index: int = -1, - excinfo: Optional[ExceptionInfo[BaseException]] = None, + excinfo: ExceptionInfo[BaseException] | None = None, short: bool = False, - ) -> List[str]: + ) -> list[str]: """Return formatted and marked up source lines.""" lines = [] if source is not None and line_index < 0: @@ -888,7 +875,7 @@ def get_exconly( excinfo: ExceptionInfo[BaseException], indent: int = 4, markall: bool = False, - ) -> List[str]: + ) -> list[str]: lines = [] indentstr = " " * indent # Get the real exception information out. @@ -900,7 +887,7 @@ def get_exconly( failindent = indentstr return lines - def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: + def repr_locals(self, locals: Mapping[str, object]) -> ReprLocals | None: if self.showlocals: lines = [] keys = [loc for loc in locals if loc[0] != "@"] @@ -928,10 +915,10 @@ def repr_locals(self, locals: Mapping[str, object]) -> Optional["ReprLocals"]: def repr_traceback_entry( self, - entry: Optional[TracebackEntry], - excinfo: Optional[ExceptionInfo[BaseException]] = None, - ) -> "ReprEntry": - lines: List[str] = [] + entry: TracebackEntry | None, + excinfo: ExceptionInfo[BaseException] | None = None, + ) -> ReprEntry: + lines: list[str] = [] style = ( entry._repr_style if entry is not None and entry._repr_style is not None @@ -966,7 +953,7 @@ def repr_traceback_entry( lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) - def _makepath(self, path: Union[Path, str]) -> str: + def _makepath(self, path: Path | str) -> str: if not self.abspath and isinstance(path, Path): try: np = bestrelpath(Path.cwd(), path) @@ -976,7 +963,7 @@ def _makepath(self, path: Union[Path, str]) -> str: return np return str(path) - def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback": + def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> ReprTraceback: traceback = excinfo.traceback if callable(self.tbfilter): traceback = self.tbfilter(excinfo) @@ -1007,7 +994,7 @@ def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTracebac def _truncate_recursive_traceback( self, traceback: Traceback - ) -> Tuple[Traceback, Optional[str]]: + ) -> tuple[Traceback, str | None]: """Truncate the given recursive traceback trying to find the starting point of the recursion. @@ -1024,7 +1011,7 @@ def _truncate_recursive_traceback( recursionindex = traceback.recursionindex() except Exception as e: max_frames = 10 - extraline: Optional[str] = ( + extraline: str | None = ( "!!! Recursion error detected, but an error occurred locating the origin of recursion.\n" " The following exception happened when comparing locals in the stack frame:\n" f" {type(e).__name__}: {e!s}\n" @@ -1042,16 +1029,12 @@ def _truncate_recursive_traceback( return traceback, extraline - def repr_excinfo( - self, excinfo: ExceptionInfo[BaseException] - ) -> "ExceptionChainRepr": - repr_chain: List[ - Tuple[ReprTraceback, Optional[ReprFileLocation], Optional[str]] - ] = [] - e: Optional[BaseException] = excinfo.value - excinfo_: Optional[ExceptionInfo[BaseException]] = excinfo + def repr_excinfo(self, excinfo: ExceptionInfo[BaseException]) -> ExceptionChainRepr: + repr_chain: list[tuple[ReprTraceback, ReprFileLocation | None, str | None]] = [] + e: BaseException | None = excinfo.value + excinfo_: ExceptionInfo[BaseException] | None = excinfo descr = None - seen: Set[int] = set() + seen: set[int] = set() while e is not None and id(e) not in seen: seen.add(id(e)) @@ -1060,7 +1043,7 @@ def repr_excinfo( # full support for exception groups added to ExceptionInfo. # See https://github.com/pytest-dev/pytest/issues/9159 if isinstance(e, BaseExceptionGroup): - reprtraceback: Union[ReprTracebackNative, ReprTraceback] = ( + reprtraceback: ReprTracebackNative | ReprTraceback = ( ReprTracebackNative( traceback.format_exception( type(excinfo_.value), @@ -1118,9 +1101,9 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ExceptionRepr(TerminalRepr): # Provided by subclasses. - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] - sections: List[Tuple[str, str, str]] = dataclasses.field( + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None + sections: list[tuple[str, str, str]] = dataclasses.field( init=False, default_factory=list ) @@ -1135,13 +1118,11 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ExceptionChainRepr(ExceptionRepr): - chain: Sequence[Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]]] + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]] def __init__( self, - chain: Sequence[ - Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ], + chain: Sequence[tuple[ReprTraceback, ReprFileLocation | None, str | None]], ) -> None: # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain. @@ -1162,8 +1143,8 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprExceptionInfo(ExceptionRepr): - reprtraceback: "ReprTraceback" - reprcrash: Optional["ReprFileLocation"] + reprtraceback: ReprTraceback + reprcrash: ReprFileLocation | None def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) @@ -1172,8 +1153,8 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprTraceback(TerminalRepr): - reprentries: Sequence[Union["ReprEntry", "ReprEntryNative"]] - extraline: Optional[str] + reprentries: Sequence[ReprEntry | ReprEntryNative] + extraline: str | None style: TracebackStyle entrysep: ClassVar = "_ " @@ -1217,9 +1198,9 @@ def toterminal(self, tw: TerminalWriter) -> None: @dataclasses.dataclass(eq=False) class ReprEntry(TerminalRepr): lines: Sequence[str] - reprfuncargs: Optional["ReprFuncArgs"] - reprlocals: Optional["ReprLocals"] - reprfileloc: Optional["ReprFileLocation"] + reprfuncargs: ReprFuncArgs | None + reprlocals: ReprLocals | None + reprfileloc: ReprFileLocation | None style: TracebackStyle def _write_entry_lines(self, tw: TerminalWriter) -> None: @@ -1243,9 +1224,9 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None: # such as "> assert 0" fail_marker = f"{FormattedExcinfo.fail_marker} " indent_size = len(fail_marker) - indents: List[str] = [] - source_lines: List[str] = [] - failure_lines: List[str] = [] + indents: list[str] = [] + source_lines: list[str] = [] + failure_lines: list[str] = [] for index, line in enumerate(self.lines): is_failure_line = line.startswith(fail_marker) if is_failure_line: @@ -1324,7 +1305,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None: @dataclasses.dataclass(eq=False) class ReprFuncArgs(TerminalRepr): - args: Sequence[Tuple[str, object]] + args: Sequence[tuple[str, object]] def toterminal(self, tw: TerminalWriter) -> None: if self.args: @@ -1345,7 +1326,7 @@ def toterminal(self, tw: TerminalWriter) -> None: tw.line("") -def getfslineno(obj: object) -> Tuple[Union[str, Path], int]: +def getfslineno(obj: object) -> tuple[str | Path, int]: """Return source location (path, lineno) for the given object. If the source cannot be determined return ("", -1). diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 7fa577e03b3..604aff8ba19 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import ast from bisect import bisect_right import inspect @@ -7,11 +9,7 @@ import types from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import overload -from typing import Tuple -from typing import Union import warnings @@ -23,7 +21,7 @@ class Source: def __init__(self, obj: object = None) -> None: if not obj: - self.lines: List[str] = [] + self.lines: list[str] = [] elif isinstance(obj, Source): self.lines = obj.lines elif isinstance(obj, (tuple, list)): @@ -50,9 +48,9 @@ def __eq__(self, other: object) -> bool: def __getitem__(self, key: int) -> str: ... @overload - def __getitem__(self, key: slice) -> "Source": ... + def __getitem__(self, key: slice) -> Source: ... - def __getitem__(self, key: Union[int, slice]) -> Union[str, "Source"]: + def __getitem__(self, key: int | slice) -> str | Source: if isinstance(key, int): return self.lines[key] else: @@ -68,7 +66,7 @@ def __iter__(self) -> Iterator[str]: def __len__(self) -> int: return len(self.lines) - def strip(self) -> "Source": + def strip(self) -> Source: """Return new Source object with trailing and leading blank lines removed.""" start, end = 0, len(self) while start < end and not self.lines[start].strip(): @@ -79,20 +77,20 @@ def strip(self) -> "Source": source.lines[:] = self.lines[start:end] return source - def indent(self, indent: str = " " * 4) -> "Source": + def indent(self, indent: str = " " * 4) -> Source: """Return a copy of the source object with all lines indented by the given indent-string.""" newsource = Source() newsource.lines = [(indent + line) for line in self.lines] return newsource - def getstatement(self, lineno: int) -> "Source": + def getstatement(self, lineno: int) -> Source: """Return Source statement which contains the given linenumber (counted from 0).""" start, end = self.getstatementrange(lineno) return self[start:end] - def getstatementrange(self, lineno: int) -> Tuple[int, int]: + def getstatementrange(self, lineno: int) -> tuple[int, int]: """Return (start, end) tuple which spans the minimal statement region which containing the given lineno.""" if not (0 <= lineno < len(self)): @@ -100,7 +98,7 @@ def getstatementrange(self, lineno: int) -> Tuple[int, int]: ast, start, end = getstatementrange_ast(lineno, self) return start, end - def deindent(self) -> "Source": + def deindent(self) -> Source: """Return a new Source object deindented.""" newsource = Source() newsource.lines[:] = deindent(self.lines) @@ -115,7 +113,7 @@ def __str__(self) -> str: # -def findsource(obj) -> Tuple[Optional[Source], int]: +def findsource(obj) -> tuple[Source | None, int]: try: sourcelines, lineno = inspect.findsource(obj) except Exception: @@ -138,14 +136,14 @@ def getrawcode(obj: object, trycall: bool = True) -> types.CodeType: raise TypeError(f"could not get code object for {obj!r}") -def deindent(lines: Iterable[str]) -> List[str]: +def deindent(lines: Iterable[str]) -> list[str]: return textwrap.dedent("\n".join(lines)).splitlines() -def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[int]]: +def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None]: # Flatten all statements and except handlers into one lineno-list. # AST's line numbers start indexing at 1. - values: List[int] = [] + values: list[int] = [] for x in ast.walk(node): if isinstance(x, (ast.stmt, ast.ExceptHandler)): # The lineno points to the class/def, so need to include the decorators. @@ -154,7 +152,7 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> Tuple[int, Optional[i values.append(d.lineno - 1) values.append(x.lineno - 1) for name in ("finalbody", "orelse"): - val: Optional[List[ast.stmt]] = getattr(x, name, None) + val: list[ast.stmt] | None = getattr(x, name, None) if val: # Treat the finally/orelse part as its own statement. values.append(val[0].lineno - 1 - 1) @@ -172,8 +170,8 @@ def getstatementrange_ast( lineno: int, source: Source, assertion: bool = False, - astnode: Optional[ast.AST] = None, -) -> Tuple[ast.AST, int, int]: + astnode: ast.AST | None = None, +) -> tuple[ast.AST, int, int]: if astnode is None: content = str(source) # See #4260: diff --git a/src/_pytest/_io/__init__.py b/src/_pytest/_io/__init__.py index db001e918cb..b0155b18b60 100644 --- a/src/_pytest/_io/__init__.py +++ b/src/_pytest/_io/__init__.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from .terminalwriter import get_terminal_width from .terminalwriter import TerminalWriter diff --git a/src/_pytest/_io/pprint.py b/src/_pytest/_io/pprint.py index e637eec59e1..7213be7ba9b 100644 --- a/src/_pytest/_io/pprint.py +++ b/src/_pytest/_io/pprint.py @@ -13,6 +13,8 @@ # tuples with fairly non-descriptive content. This is modeled very much # after Lisp/Scheme - style pretty-printing of lists. If you find it # useful, thank small children who sleep at night. +from __future__ import annotations + import collections as _collections import dataclasses as _dataclasses from io import StringIO as _StringIO @@ -20,13 +22,8 @@ import types as _types from typing import Any from typing import Callable -from typing import Dict from typing import IO from typing import Iterator -from typing import List -from typing import Optional -from typing import Set -from typing import Tuple class _safe_key: @@ -64,7 +61,7 @@ def __init__( self, indent: int = 4, width: int = 80, - depth: Optional[int] = None, + depth: int | None = None, ) -> None: """Handle pretty printing operations onto a stream using a set of configured parameters. @@ -100,7 +97,7 @@ def _format( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: objid = id(object) @@ -136,7 +133,7 @@ def _pprint_dataclass( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: cls_name = object.__class__.__name__ @@ -149,9 +146,9 @@ def _pprint_dataclass( self._format_namespace_items(items, stream, indent, allowance, context, level) stream.write(")") - _dispatch: Dict[ + _dispatch: dict[ Callable[..., str], - Callable[["PrettyPrinter", Any, IO[str], int, int, Set[int], int], None], + Callable[[PrettyPrinter, Any, IO[str], int, int, set[int], int], None], ] = {} def _pprint_dict( @@ -160,7 +157,7 @@ def _pprint_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -177,7 +174,7 @@ def _pprint_ordered_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object): @@ -196,7 +193,7 @@ def _pprint_list( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("[") @@ -211,7 +208,7 @@ def _pprint_tuple( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("(") @@ -226,7 +223,7 @@ def _pprint_set( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object): @@ -252,7 +249,7 @@ def _pprint_str( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -311,7 +308,7 @@ def _pprint_bytes( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -340,7 +337,7 @@ def _pprint_bytearray( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: write = stream.write @@ -358,7 +355,7 @@ def _pprint_mappingproxy( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write("mappingproxy(") @@ -373,7 +370,7 @@ def _pprint_simplenamespace( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if type(object) is _types.SimpleNamespace: @@ -391,11 +388,11 @@ def _pprint_simplenamespace( def _format_dict_items( self, - items: List[Tuple[Any, Any]], + items: list[tuple[Any, Any]], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -415,11 +412,11 @@ def _format_dict_items( def _format_namespace_items( self, - items: List[Tuple[Any, Any]], + items: list[tuple[Any, Any]], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -452,11 +449,11 @@ def _format_namespace_items( def _format_items( self, - items: List[Any], + items: list[Any], stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not items: @@ -473,7 +470,7 @@ def _format_items( write("\n" + " " * indent) - def _repr(self, object: Any, context: Set[int], level: int) -> str: + def _repr(self, object: Any, context: set[int], level: int) -> str: return self._safe_repr(object, context.copy(), self._depth, level) def _pprint_default_dict( @@ -482,7 +479,7 @@ def _pprint_default_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: rdf = self._repr(object.default_factory, context, level) @@ -498,7 +495,7 @@ def _pprint_counter( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -519,7 +516,7 @@ def _pprint_chain_map( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: if not len(object.maps) or (len(object.maps) == 1 and not len(object.maps[0])): @@ -538,7 +535,7 @@ def _pprint_deque( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: stream.write(object.__class__.__name__ + "(") @@ -557,7 +554,7 @@ def _pprint_user_dict( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -570,7 +567,7 @@ def _pprint_user_list( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -583,7 +580,7 @@ def _pprint_user_string( stream: IO[str], indent: int, allowance: int, - context: Set[int], + context: set[int], level: int, ) -> None: self._format(object.data, stream, indent, allowance, context, level - 1) @@ -591,7 +588,7 @@ def _pprint_user_string( _dispatch[_collections.UserString.__repr__] = _pprint_user_string def _safe_repr( - self, object: Any, context: Set[int], maxlevels: Optional[int], level: int + self, object: Any, context: set[int], maxlevels: int | None, level: int ) -> str: typ = type(object) if typ in _builtin_scalars: @@ -608,7 +605,7 @@ def _safe_repr( if objid in context: return _recursion(object) context.add(objid) - components: List[str] = [] + components: list[str] = [] append = components.append level += 1 for k, v in sorted(object.items(), key=_safe_tuple): diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index 9f33fced676..5ace418227d 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -1,6 +1,7 @@ +from __future__ import annotations + import pprint import reprlib -from typing import Optional def _try_repr_or_str(obj: object) -> str: @@ -38,7 +39,7 @@ class SafeRepr(reprlib.Repr): information on exceptions raised during the call. """ - def __init__(self, maxsize: Optional[int], use_ascii: bool = False) -> None: + def __init__(self, maxsize: int | None, use_ascii: bool = False) -> None: """ :param maxsize: If not None, will truncate the resulting repr to that specific size, using ellipsis @@ -97,7 +98,7 @@ def safeformat(obj: object) -> str: def saferepr( - obj: object, maxsize: Optional[int] = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False + obj: object, maxsize: int | None = DEFAULT_REPR_MAX_SIZE, use_ascii: bool = False ) -> str: """Return a size-limited safe repr-string for the given object. diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 083c18232ff..70ebd3d061b 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -1,11 +1,12 @@ """Helper functions for writing to terminals and files.""" +from __future__ import annotations + import os import shutil import sys from typing import final from typing import Literal -from typing import Optional from typing import Sequence from typing import TextIO from typing import TYPE_CHECKING @@ -71,7 +72,7 @@ class TerminalWriter: invert=7, ) - def __init__(self, file: Optional[TextIO] = None) -> None: + def __init__(self, file: TextIO | None = None) -> None: if file is None: file = sys.stdout if hasattr(file, "isatty") and file.isatty() and sys.platform == "win32": @@ -85,7 +86,7 @@ def __init__(self, file: Optional[TextIO] = None) -> None: self._file = file self.hasmarkup = should_do_markup(file) self._current_line = "" - self._terminal_width: Optional[int] = None + self._terminal_width: int | None = None self.code_highlight = True @property @@ -116,8 +117,8 @@ def markup(self, text: str, **markup: bool) -> str: def sep( self, sepchar: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: if fullwidth is None: @@ -200,9 +201,7 @@ def _write_source(self, lines: Sequence[str], indents: Sequence[str] = ()) -> No for indent, new_line in zip(indents, new_lines): self.line(indent + new_line) - def _get_pygments_lexer( - self, lexer: Literal["python", "diff"] - ) -> Optional["Lexer"]: + def _get_pygments_lexer(self, lexer: Literal["python", "diff"]) -> Lexer | None: try: if lexer == "python": from pygments.lexers.python import PythonLexer @@ -217,7 +216,7 @@ def _get_pygments_lexer( except ModuleNotFoundError: return None - def _get_pygments_formatter(self) -> Optional["Formatter"]: + def _get_pygments_formatter(self) -> Formatter | None: try: import pygments.util except ModuleNotFoundError: diff --git a/src/_pytest/_io/wcwidth.py b/src/_pytest/_io/wcwidth.py index 53803133519..23886ff1581 100644 --- a/src/_pytest/_io/wcwidth.py +++ b/src/_pytest/_io/wcwidth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from functools import lru_cache import unicodedata diff --git a/src/_pytest/assertion/__init__.py b/src/_pytest/assertion/__init__.py index 21dd4a4a4bb..f2f1d029b4c 100644 --- a/src/_pytest/assertion/__init__.py +++ b/src/_pytest/assertion/__init__.py @@ -1,11 +1,11 @@ # mypy: allow-untyped-defs """Support for presenting detailed information in failing assertions.""" +from __future__ import annotations + import sys from typing import Any from typing import Generator -from typing import List -from typing import Optional from typing import TYPE_CHECKING from _pytest.assertion import rewrite @@ -94,7 +94,7 @@ class AssertionState: def __init__(self, config: Config, mode) -> None: self.mode = mode self.trace = config.trace.root.get("assertion") - self.hook: Optional[rewrite.AssertionRewritingHook] = None + self.hook: rewrite.AssertionRewritingHook | None = None def install_importhook(config: Config) -> rewrite.AssertionRewritingHook: @@ -113,7 +113,7 @@ def undo() -> None: return hook -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: # This hook is only called when test modules are collected # so for example not in the managing process of pytest-xdist # (which does not collect test modules). @@ -133,7 +133,7 @@ def pytest_runtest_protocol(item: Item) -> Generator[None, object, object]: """ ihook = item.ihook - def callbinrepr(op, left: object, right: object) -> Optional[str]: + def callbinrepr(op, left: object, right: object) -> str | None: """Call the pytest_assertrepr_compare hook and prepare the result. This uses the first result from the hook and then ensures the @@ -179,7 +179,7 @@ def call_assertion_pass_hook(lineno: int, orig: str, expl: str) -> None: util._config = None -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: assertstate = session.config.stash.get(assertstate_key, None) if assertstate: if assertstate.hook is not None: @@ -188,5 +188,5 @@ def pytest_sessionfinish(session: "Session") -> None: def pytest_assertrepr_compare( config: Config, op: str, left: Any, right: Any -) -> Optional[List[str]]: +) -> list[str] | None: return util.assertrepr_compare(config=config, op=op, left=left, right=right) diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index b29a254f5f7..442fc5d9f30 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -1,5 +1,7 @@ """Rewrite assertion AST to produce nice error messages.""" +from __future__ import annotations + import ast from collections import defaultdict import errno @@ -18,17 +20,11 @@ import tokenize import types from typing import Callable -from typing import Dict from typing import IO from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Set -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr @@ -73,17 +69,17 @@ def __init__(self, config: Config) -> None: self.fnpats = config.getini("python_files") except ValueError: self.fnpats = ["test_*.py", "*_test.py"] - self.session: Optional[Session] = None - self._rewritten_names: Dict[str, Path] = {} - self._must_rewrite: Set[str] = set() + self.session: Session | None = None + self._rewritten_names: dict[str, Path] = {} + self._must_rewrite: set[str] = set() # flag to guard against trying to rewrite a pyc file while we are already writing another pyc file, # which might result in infinite recursion (#3506) self._writing_pyc = False self._basenames_to_check_rewrite = {"conftest"} - self._marked_for_rewrite_cache: Dict[str, bool] = {} + self._marked_for_rewrite_cache: dict[str, bool] = {} self._session_paths_checked = False - def set_session(self, session: Optional[Session]) -> None: + def set_session(self, session: Session | None) -> None: self.session = session self._session_paths_checked = False @@ -93,9 +89,9 @@ def set_session(self, session: Optional[Session]) -> None: def find_spec( self, name: str, - path: Optional[Sequence[Union[str, bytes]]] = None, - target: Optional[types.ModuleType] = None, - ) -> Optional[importlib.machinery.ModuleSpec]: + path: Sequence[str | bytes] | None = None, + target: types.ModuleType | None = None, + ) -> importlib.machinery.ModuleSpec | None: if self._writing_pyc: return None state = self.config.stash[assertstate_key] @@ -132,7 +128,7 @@ def find_spec( def create_module( self, spec: importlib.machinery.ModuleSpec - ) -> Optional[types.ModuleType]: + ) -> types.ModuleType | None: return None # default behaviour is fine def exec_module(self, module: types.ModuleType) -> None: @@ -177,7 +173,7 @@ def exec_module(self, module: types.ModuleType) -> None: state.trace(f"found cached rewritten pyc for {fn}") exec(co, module.__dict__) - def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: + def _early_rewrite_bailout(self, name: str, state: AssertionState) -> bool: """A fast way to get out of rewriting modules. Profiling has shown that the call to PathFinder.find_spec (inside of @@ -216,7 +212,7 @@ def _early_rewrite_bailout(self, name: str, state: "AssertionState") -> bool: state.trace(f"early skip of rewriting module: {name}") return True - def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: + def _should_rewrite(self, name: str, fn: str, state: AssertionState) -> bool: # always rewrite conftest files if os.path.basename(fn) == "conftest.py": state.trace(f"rewriting conftest file: {fn!r}") @@ -237,7 +233,7 @@ def _should_rewrite(self, name: str, fn: str, state: "AssertionState") -> bool: return self._is_marked_for_rewrite(name, state) - def _is_marked_for_rewrite(self, name: str, state: "AssertionState") -> bool: + def _is_marked_for_rewrite(self, name: str, state: AssertionState) -> bool: try: return self._marked_for_rewrite_cache[name] except KeyError: @@ -278,7 +274,7 @@ def _warn_already_imported(self, name: str) -> None: stacklevel=5, ) - def get_data(self, pathname: Union[str, bytes]) -> bytes: + def get_data(self, pathname: str | bytes) -> bytes: """Optional PEP302 get_data API.""" with open(pathname, "rb") as f: return f.read() @@ -317,7 +313,7 @@ def _write_pyc_fp( def _write_pyc( - state: "AssertionState", + state: AssertionState, co: types.CodeType, source_stat: os.stat_result, pyc: Path, @@ -341,7 +337,7 @@ def _write_pyc( return True -def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeType]: +def _rewrite_test(fn: Path, config: Config) -> tuple[os.stat_result, types.CodeType]: """Read and rewrite *fn* and return the code object.""" stat = os.stat(fn) source = fn.read_bytes() @@ -354,7 +350,7 @@ def _rewrite_test(fn: Path, config: Config) -> Tuple[os.stat_result, types.CodeT def _read_pyc( source: Path, pyc: Path, trace: Callable[[str], None] = lambda x: None -) -> Optional[types.CodeType]: +) -> types.CodeType | None: """Possibly read a pytest pyc containing rewritten code. Return rewritten code if successful or None if not. @@ -404,8 +400,8 @@ def _read_pyc( def rewrite_asserts( mod: ast.Module, source: bytes, - module_path: Optional[str] = None, - config: Optional[Config] = None, + module_path: str | None = None, + config: Config | None = None, ) -> None: """Rewrite the assert statements in mod.""" AssertionRewriter(module_path, config, source).run(mod) @@ -425,7 +421,7 @@ def _saferepr(obj: object) -> str: return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") -def _get_maxsize_for_saferepr(config: Optional[Config]) -> Optional[int]: +def _get_maxsize_for_saferepr(config: Config | None) -> int | None: """Get `maxsize` configuration for saferepr based on the given config object.""" if config is None: verbosity = 0 @@ -543,14 +539,14 @@ def traverse_node(node: ast.AST) -> Iterator[ast.AST]: @functools.lru_cache(maxsize=1) -def _get_assertion_exprs(src: bytes) -> Dict[int, str]: +def _get_assertion_exprs(src: bytes) -> dict[int, str]: """Return a mapping from {lineno: "assertion test expression"}.""" - ret: Dict[int, str] = {} + ret: dict[int, str] = {} depth = 0 - lines: List[str] = [] - assert_lineno: Optional[int] = None - seen_lines: Set[int] = set() + lines: list[str] = [] + assert_lineno: int | None = None + seen_lines: set[int] = set() def _write_and_reset() -> None: nonlocal depth, lines, assert_lineno, seen_lines @@ -657,7 +653,7 @@ class AssertionRewriter(ast.NodeVisitor): """ def __init__( - self, module_path: Optional[str], config: Optional[Config], source: bytes + self, module_path: str | None, config: Config | None, source: bytes ) -> None: super().__init__() self.module_path = module_path @@ -670,7 +666,7 @@ def __init__( self.enable_assertion_pass_hook = False self.source = source self.scope: tuple[ast.AST, ...] = () - self.variables_overwrite: defaultdict[tuple[ast.AST, ...], Dict[str, str]] = ( + self.variables_overwrite: defaultdict[tuple[ast.AST, ...], dict[str, str]] = ( defaultdict(dict) ) @@ -737,7 +733,7 @@ def run(self, mod: ast.Module) -> None: # Collect asserts. self.scope = (mod,) - nodes: List[Union[ast.AST, Sentinel]] = [mod] + nodes: list[ast.AST | Sentinel] = [mod] while nodes: node = nodes.pop() if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): @@ -749,7 +745,7 @@ def run(self, mod: ast.Module) -> None: assert isinstance(node, ast.AST) for name, field in ast.iter_fields(node): if isinstance(field, list): - new: List[ast.AST] = [] + new: list[ast.AST] = [] for i, child in enumerate(field): if isinstance(child, ast.Assert): # Transform assert. @@ -821,7 +817,7 @@ def push_format_context(self) -> None: to format a string of %-formatted values as added by .explanation_param(). """ - self.explanation_specifiers: Dict[str, ast.expr] = {} + self.explanation_specifiers: dict[str, ast.expr] = {} self.stack.append(self.explanation_specifiers) def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: @@ -835,7 +831,7 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys: List[Optional[ast.expr]] = [ast.Constant(key) for key in current.keys()] + keys: list[ast.expr | None] = [ast.Constant(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)) @@ -844,13 +840,13 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: self.expl_stmts.append(ast.Assign([ast.Name(name, ast.Store())], form)) return ast.Name(name, ast.Load()) - def generic_visit(self, node: ast.AST) -> Tuple[ast.Name, str]: + def generic_visit(self, node: ast.AST) -> tuple[ast.Name, str]: """Handle expressions we don't have custom code for.""" assert isinstance(node, ast.expr) res = self.assign(node) return res, self.explanation_param(self.display(res)) - def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: + def visit_Assert(self, assert_: ast.Assert) -> list[ast.stmt]: """Return the AST statements to replace the ast.Assert instance. This rewrites the test of an assertion to provide @@ -874,15 +870,15 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: lineno=assert_.lineno, ) - self.statements: List[ast.stmt] = [] - self.variables: List[str] = [] + self.statements: list[ast.stmt] = [] + self.variables: list[str] = [] self.variable_counter = itertools.count() if self.enable_assertion_pass_hook: - self.format_variables: List[str] = [] + self.format_variables: list[str] = [] - self.stack: List[Dict[str, ast.expr]] = [] - self.expl_stmts: List[ast.stmt] = [] + self.stack: list[dict[str, ast.expr]] = [] + self.expl_stmts: list[ast.stmt] = [] self.push_format_context() # Rewrite assert into a bunch of statements. top_condition, explanation = self.visit(assert_.test) @@ -926,13 +922,13 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: [*self.expl_stmts, hook_call_pass], [], ) - statements_pass: List[ast.stmt] = [hook_impl_test] + statements_pass: list[ast.stmt] = [hook_impl_test] # Test for assertion condition main_test = ast.If(negation, statements_fail, statements_pass) self.statements.append(main_test) if self.format_variables: - variables: List[ast.expr] = [ + variables: list[ast.expr] = [ ast.Name(name, ast.Store()) for name in self.format_variables ] clear_format = ast.Assign(variables, ast.Constant(None)) @@ -968,7 +964,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: ast.copy_location(node, assert_) return self.statements - def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: + def visit_NamedExpr(self, name: ast.NamedExpr) -> tuple[ast.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. @@ -980,7 +976,7 @@ def visit_NamedExpr(self, name: ast.NamedExpr) -> Tuple[ast.NamedExpr, str]: expr = ast.IfExp(test, self.display(name), ast.Constant(target_id)) return name, self.explanation_param(expr) - def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: + 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"), [], []) @@ -990,7 +986,7 @@ def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: expr = ast.IfExp(test, self.display(name), ast.Constant(name.id)) return name, self.explanation_param(expr) - def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: + def visit_BoolOp(self, boolop: ast.BoolOp) -> tuple[ast.Name, str]: res_var = self.variable() expl_list = self.assign(ast.List([], ast.Load())) app = ast.Attribute(expl_list, "append", ast.Load()) @@ -1002,7 +998,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: # Process each operand, short-circuiting if needed. for i, v in enumerate(boolop.values): if i: - fail_inner: List[ast.stmt] = [] + fail_inner: list[ast.stmt] = [] # cond is set in a prior loop iteration below self.expl_stmts.append(ast.If(cond, fail_inner, [])) # noqa: F821 self.expl_stmts = fail_inner @@ -1030,7 +1026,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: cond: ast.expr = res if is_or: cond = ast.UnaryOp(ast.Not(), cond) - inner: List[ast.stmt] = [] + inner: list[ast.stmt] = [] self.statements.append(ast.If(cond, inner, [])) self.statements = body = inner self.statements = save @@ -1039,13 +1035,13 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) - def visit_UnaryOp(self, unary: ast.UnaryOp) -> Tuple[ast.Name, str]: + def visit_UnaryOp(self, unary: ast.UnaryOp) -> tuple[ast.Name, str]: pattern = UNARY_MAP[unary.op.__class__] operand_res, operand_expl = self.visit(unary.operand) res = self.assign(ast.UnaryOp(unary.op, operand_res)) return res, pattern % (operand_expl,) - def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: + def visit_BinOp(self, binop: ast.BinOp) -> tuple[ast.Name, str]: symbol = BINOP_MAP[binop.op.__class__] left_expr, left_expl = self.visit(binop.left) right_expr, right_expl = self.visit(binop.right) @@ -1053,7 +1049,7 @@ def visit_BinOp(self, binop: ast.BinOp) -> Tuple[ast.Name, str]: res = self.assign(ast.BinOp(left_expr, binop.op, right_expr)) return res, explanation - def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: + def visit_Call(self, call: ast.Call) -> tuple[ast.Name, str]: new_func, func_expl = self.visit(call.func) arg_expls = [] new_args = [] @@ -1085,13 +1081,13 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: outer_expl = f"{res_expl}\n{{{res_expl} = {expl}\n}}" return res, outer_expl - def visit_Starred(self, starred: ast.Starred) -> Tuple[ast.Starred, str]: + def visit_Starred(self, starred: ast.Starred) -> tuple[ast.Starred, str]: # A Starred node can appear in a function call. res, expl = self.visit(starred.value) new_starred = ast.Starred(res, starred.ctx) return new_starred, "*" + expl - def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: + def visit_Attribute(self, attr: ast.Attribute) -> tuple[ast.Name, str]: if not isinstance(attr.ctx, ast.Load): return self.generic_visit(attr) value, value_expl = self.visit(attr.value) @@ -1101,7 +1097,7 @@ def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, str]: expl = pat % (res_expl, res_expl, value_expl, attr.attr) return res, expl - def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: + 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( @@ -1114,11 +1110,11 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" res_variables = [self.variable() for i in range(len(comp.ops))] - load_names: List[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] + load_names: list[ast.expr] = [ast.Name(v, ast.Load()) for v in res_variables] store_names = [ast.Name(v, ast.Store()) for v in res_variables] it = zip(range(len(comp.ops)), comp.ops, comp.comparators) - expls: List[ast.expr] = [] - syms: List[ast.expr] = [] + expls: list[ast.expr] = [] + syms: list[ast.expr] = [] results = [left_res] for i, op, next_operand in it: if ( diff --git a/src/_pytest/assertion/truncate.py b/src/_pytest/assertion/truncate.py index 4fdfd86a519..b67f02ccaf8 100644 --- a/src/_pytest/assertion/truncate.py +++ b/src/_pytest/assertion/truncate.py @@ -4,8 +4,7 @@ terminal lines, unless running with an assertions verbosity level of at least 2 or running on CI. """ -from typing import List -from typing import Optional +from __future__ import annotations from _pytest.assertion import util from _pytest.config import Config @@ -18,8 +17,8 @@ def truncate_if_required( - explanation: List[str], item: Item, max_length: Optional[int] = None -) -> List[str]: + explanation: list[str], item: Item, max_length: int | None = None +) -> list[str]: """Truncate this assertion explanation if the given test item is eligible.""" if _should_truncate_item(item): return _truncate_explanation(explanation) @@ -33,10 +32,10 @@ def _should_truncate_item(item: Item) -> bool: def _truncate_explanation( - input_lines: List[str], - max_lines: Optional[int] = None, - max_chars: Optional[int] = None, -) -> List[str]: + input_lines: list[str], + max_lines: int | None = None, + max_chars: int | None = None, +) -> list[str]: """Truncate given list of strings that makes up the assertion explanation. Truncates to either 8 lines, or 640 characters - whichever the input reaches @@ -100,7 +99,7 @@ def _truncate_explanation( ] -def _truncate_by_char_count(input_lines: List[str], max_chars: int) -> List[str]: +def _truncate_by_char_count(input_lines: list[str], max_chars: int) -> list[str]: # 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/assertion/util.py b/src/_pytest/assertion/util.py index a118befcc16..4dc1af4af03 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Utilities for assertion debugging.""" +from __future__ import annotations + import collections.abc import os import pprint @@ -8,10 +10,8 @@ from typing import Any from typing import Callable from typing import Iterable -from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import Protocol from typing import Sequence from unicodedata import normalize @@ -28,14 +28,14 @@ # interpretation code and assertion rewriter to detect this plugin was # loaded and in turn call the hooks defined here as part of the # DebugInterpreter. -_reprcompare: Optional[Callable[[str, object, object], Optional[str]]] = None +_reprcompare: Callable[[str, object, object], str | None] | None = None # Works similarly as _reprcompare attribute. Is populated with the hook call # when pytest_runtest_setup is called. -_assertion_pass: Optional[Callable[[int, str, str], None]] = None +_assertion_pass: Callable[[int, str, str], None] | None = None # Config object which is assigned during pytest_runtest_protocol. -_config: Optional[Config] = None +_config: Config | None = None class _HighlightFunc(Protocol): @@ -58,7 +58,7 @@ def format_explanation(explanation: str) -> str: return "\n".join(result) -def _split_explanation(explanation: str) -> List[str]: +def _split_explanation(explanation: str) -> list[str]: r"""Return a list of individual lines in the explanation. This will return a list of lines split on '\n{', '\n}' and '\n~'. @@ -75,7 +75,7 @@ def _split_explanation(explanation: str) -> List[str]: return lines -def _format_lines(lines: Sequence[str]) -> List[str]: +def _format_lines(lines: Sequence[str]) -> list[str]: """Format the individual lines. This will replace the '{', '}' and '~' characters of our mini formatting @@ -169,7 +169,7 @@ def has_default_eq( def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False -) -> Optional[List[str]]: +) -> list[str] | None: """Return specialised explanations for some operators/operands.""" verbose = config.get_verbosity(Config.VERBOSITY_ASSERTIONS) @@ -239,7 +239,7 @@ def assertrepr_compare( def _compare_eq_any( left: Any, right: Any, highlighter: _HighlightFunc, verbose: int = 0 -) -> List[str]: +) -> list[str]: explanation = [] if istext(left) and istext(right): explanation = _diff_text(left, right, verbose) @@ -274,7 +274,7 @@ def _compare_eq_any( return explanation -def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: +def _diff_text(left: str, right: str, verbose: int = 0) -> list[str]: """Return the explanation for the diff between text. Unless --verbose is used this will skip leading and trailing @@ -282,7 +282,7 @@ def _diff_text(left: str, right: str, verbose: int = 0) -> List[str]: """ from difflib import ndiff - explanation: List[str] = [] + explanation: list[str] = [] if verbose < 1: i = 0 # just in case left or right has zero length @@ -327,7 +327,7 @@ def _compare_eq_iterable( right: Iterable[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: if verbose <= 0 and not running_on_ci(): return ["Use -v to get more diff"] # dynamic import to speedup pytest @@ -356,9 +356,9 @@ def _compare_eq_sequence( right: Sequence[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: comparing_bytes = isinstance(left, bytes) and isinstance(right, bytes) - explanation: List[str] = [] + explanation: list[str] = [] len_left = len(left) len_right = len(right) for i in range(min(len_left, len_right)): @@ -417,7 +417,7 @@ def _compare_eq_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = [] explanation.extend(_set_one_sided_diff("left", left, right, highlighter)) explanation.extend(_set_one_sided_diff("right", right, left, highlighter)) @@ -429,7 +429,7 @@ def _compare_gt_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = _compare_gte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] @@ -441,7 +441,7 @@ def _compare_lt_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: explanation = _compare_lte_set(left, right, highlighter) if not explanation: return ["Both sets are equal"] @@ -453,7 +453,7 @@ def _compare_gte_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: return _set_one_sided_diff("right", right, left, highlighter) @@ -462,7 +462,7 @@ def _compare_lte_set( right: AbstractSet[Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: +) -> list[str]: return _set_one_sided_diff("left", left, right, highlighter) @@ -471,7 +471,7 @@ def _set_one_sided_diff( set1: AbstractSet[Any], set2: AbstractSet[Any], highlighter: _HighlightFunc, -) -> List[str]: +) -> list[str]: explanation = [] diff = set1 - set2 if diff: @@ -486,8 +486,8 @@ def _compare_eq_dict( right: Mapping[Any, Any], highlighter: _HighlightFunc, verbose: int = 0, -) -> List[str]: - explanation: List[str] = [] +) -> list[str]: + explanation: list[str] = [] set_left = set(left) set_right = set(right) common = set_left.intersection(set_right) @@ -531,7 +531,7 @@ def _compare_eq_dict( def _compare_eq_cls( left: Any, right: Any, highlighter: _HighlightFunc, verbose: int -) -> List[str]: +) -> list[str]: if not has_default_eq(left): return [] if isdatacls(left): @@ -584,7 +584,7 @@ def _compare_eq_cls( return explanation -def _notin_text(term: str, text: str, verbose: int = 0) -> List[str]: +def _notin_text(term: str, text: str, verbose: int = 0) -> list[str]: index = text.find(term) head = text[:index] tail = text[index + len(term) :] diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 8ad36f9b91c..51778c456c4 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -3,20 +3,17 @@ # This plugin was not named "cache" to avoid conflicts with the external # pytest-cache version. +from __future__ import annotations + import dataclasses import errno import json import os from pathlib import Path import tempfile -from typing import Dict from typing import final from typing import Generator from typing import Iterable -from typing import List -from typing import Optional -from typing import Set -from typing import Union from .pathlib import resolve_from_str from .pathlib import rm_rf @@ -77,7 +74,7 @@ def __init__( self._config = config @classmethod - def for_config(cls, config: Config, *, _ispytest: bool = False) -> "Cache": + def for_config(cls, config: Config, *, _ispytest: bool = False) -> Cache: """Create the Cache instance for a Config. :meta private: @@ -249,7 +246,7 @@ def _ensure_cache_dir_and_supporting_files(self) -> None: class LFPluginCollWrapper: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin self._collected_at_least_one_failure = False @@ -263,7 +260,7 @@ def pytest_make_collect_report( lf_paths = self.lfplugin._last_failed_paths # Use stable sort to prioritize last failed. - def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: + def sort_key(node: nodes.Item | nodes.Collector) -> bool: return node.path in lf_paths res.result = sorted( @@ -301,13 +298,13 @@ def sort_key(node: Union[nodes.Item, nodes.Collector]) -> bool: class LFPluginCollSkipfiles: - def __init__(self, lfplugin: "LFPlugin") -> None: + def __init__(self, lfplugin: LFPlugin) -> None: self.lfplugin = lfplugin @hookimpl def pytest_make_collect_report( self, collector: nodes.Collector - ) -> Optional[CollectReport]: + ) -> CollectReport | None: if isinstance(collector, File): if collector.path not in self.lfplugin._last_failed_paths: self.lfplugin._skipped_files += 1 @@ -326,9 +323,9 @@ def __init__(self, config: Config) -> None: active_keys = "lf", "failedfirst" self.active = any(config.getoption(key) for key in active_keys) assert config.cache - self.lastfailed: Dict[str, bool] = config.cache.get("cache/lastfailed", {}) - self._previously_failed_count: Optional[int] = None - self._report_status: Optional[str] = None + self.lastfailed: dict[str, bool] = config.cache.get("cache/lastfailed", {}) + self._previously_failed_count: int | None = None + self._report_status: str | None = None self._skipped_files = 0 # count skipped files during collection due to --lf if config.getoption("lf"): @@ -337,7 +334,7 @@ def __init__(self, config: Config) -> None: LFPluginCollWrapper(self), "lfplugin-collwrapper" ) - def get_last_failed_paths(self) -> Set[Path]: + def get_last_failed_paths(self) -> set[Path]: """Return a set with all Paths of the previously failed nodeids and their parents.""" rootpath = self.config.rootpath @@ -348,7 +345,7 @@ def get_last_failed_paths(self) -> Set[Path]: result.update(path.parents) return {x for x in result if x.exists()} - def pytest_report_collectionfinish(self) -> Optional[str]: + def pytest_report_collectionfinish(self) -> str | None: if self.active and self.config.getoption("verbose") >= 0: return f"run-last-failure: {self._report_status}" return None @@ -370,7 +367,7 @@ def pytest_collectreport(self, report: CollectReport) -> None: @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> Generator[None, None, None]: res = yield @@ -442,13 +439,13 @@ def __init__(self, config: Config) -> None: @hookimpl(wrapper=True, tryfirst=True) def pytest_collection_modifyitems( - self, items: List[nodes.Item] + self, items: list[nodes.Item] ) -> Generator[None, None, None]: res = yield if self.active: - new_items: Dict[str, nodes.Item] = {} - other_items: Dict[str, nodes.Item] = {} + new_items: dict[str, nodes.Item] = {} + other_items: dict[str, nodes.Item] = {} for item in items: if item.nodeid not in self.cached_nodeids: new_items[item.nodeid] = item @@ -464,7 +461,7 @@ def pytest_collection_modifyitems( return res - def _get_increasing_order(self, items: Iterable[nodes.Item]) -> List[nodes.Item]: + def _get_increasing_order(self, items: Iterable[nodes.Item]) -> list[nodes.Item]: return sorted(items, key=lambda item: item.path.stat().st_mtime, reverse=True) def pytest_sessionfinish(self) -> None: @@ -541,7 +538,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session @@ -572,7 +569,7 @@ def cache(request: FixtureRequest) -> Cache: return request.config.cache -def pytest_report_header(config: Config) -> Optional[str]: +def pytest_report_header(config: Config) -> str | None: """Display cachedir with --cache-show and if non-default.""" if config.option.verbose > 0 or config.getini("cache_dir") != ".pytest_cache": assert config.cache is not None diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 89a938d5416..c4dfcc27552 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Per-test stdout/stderr capturing mechanism.""" +from __future__ import annotations + import abc import collections import contextlib @@ -19,15 +21,14 @@ from typing import Generic from typing import Iterable from typing import Iterator -from typing import List from typing import Literal 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 + + +if TYPE_CHECKING: + from typing_extensions import Self from _pytest.config import Config from _pytest.config import hookimpl @@ -213,7 +214,7 @@ def read(self, size: int = -1) -> str: def __next__(self) -> str: return self.readline() - def readlines(self, hint: Optional[int] = -1) -> List[str]: + def readlines(self, hint: int | None = -1) -> list[str]: raise OSError( "pytest: reading from stdin while output is captured! Consider using `-s`." ) @@ -245,7 +246,7 @@ def seekable(self) -> bool: def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") - def truncate(self, size: Optional[int] = None) -> int: + def truncate(self, size: int | None = None) -> int: raise UnsupportedOperation("cannot truncate stdin") def write(self, data: str) -> int: @@ -257,14 +258,14 @@ def writelines(self, lines: Iterable[str]) -> None: def writable(self) -> bool: return False - def __enter__(self) -> "DontReadFromInput": + def __enter__(self) -> Self: return self def __exit__( self, - type: Optional[Type[BaseException]], - value: Optional[BaseException], - traceback: Optional[TracebackType], + type: type[BaseException] | None, + value: BaseException | None, + traceback: TracebackType | None, ) -> None: pass @@ -339,7 +340,7 @@ def writeorg(self, data: str) -> None: class SysCaptureBase(CaptureBase[AnyStr]): def __init__( - self, fd: int, tmpfile: Optional[TextIO] = None, *, tee: bool = False + self, fd: int, tmpfile: TextIO | None = None, *, tee: bool = False ) -> None: name = patchsysdict[fd] self._old: TextIO = getattr(sys, name) @@ -370,7 +371,7 @@ def __repr__(self) -> str: self.tmpfile, ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -457,7 +458,7 @@ def __init__(self, targetfd: int) -> None: # Further complications are the need to support suspend() and the # possibility of FD reuse (e.g. the tmpfile getting the very same # target FD). The following approach is robust, I believe. - self.targetfd_invalid: Optional[int] = os.open(os.devnull, os.O_RDWR) + self.targetfd_invalid: int | None = os.open(os.devnull, os.O_RDWR) os.dup2(self.targetfd_invalid, targetfd) else: self.targetfd_invalid = None @@ -487,7 +488,7 @@ def __repr__(self) -> str: f"_state={self._state!r} tmpfile={self.tmpfile!r}>" ) - def _assert_state(self, op: str, states: Tuple[str, ...]) -> None: + def _assert_state(self, op: str, states: tuple[str, ...]) -> None: assert ( self._state in states ), "cannot {} in state {!r}: expected one of {}".format( @@ -609,13 +610,13 @@ class MultiCapture(Generic[AnyStr]): def __init__( self, - in_: Optional[CaptureBase[AnyStr]], - out: Optional[CaptureBase[AnyStr]], - err: Optional[CaptureBase[AnyStr]], + in_: CaptureBase[AnyStr] | None, + out: CaptureBase[AnyStr] | None, + err: CaptureBase[AnyStr] | None, ) -> None: - self.in_: Optional[CaptureBase[AnyStr]] = in_ - self.out: Optional[CaptureBase[AnyStr]] = out - self.err: Optional[CaptureBase[AnyStr]] = err + self.in_: CaptureBase[AnyStr] | None = in_ + self.out: CaptureBase[AnyStr] | None = out + self.err: CaptureBase[AnyStr] | None = err def __repr__(self) -> str: return ( @@ -632,7 +633,7 @@ def start_capturing(self) -> None: if self.err: self.err.start() - def pop_outerr_to_orig(self) -> Tuple[AnyStr, AnyStr]: + def pop_outerr_to_orig(self) -> tuple[AnyStr, AnyStr]: """Pop current snapshot out/err capture and flush to orig streams.""" out, err = self.readouterr() if out: @@ -725,8 +726,8 @@ class CaptureManager: def __init__(self, method: _CaptureMethod) -> None: self._method: Final = method - self._global_capturing: Optional[MultiCapture[str]] = None - self._capture_fixture: Optional[CaptureFixture[Any]] = None + self._global_capturing: MultiCapture[str] | None = None + self._capture_fixture: CaptureFixture[Any] | None = None def __repr__(self) -> str: return ( @@ -734,7 +735,7 @@ def __repr__(self) -> str: f"_capture_fixture={self._capture_fixture!r}>" ) - def is_capturing(self) -> Union[str, bool]: + def is_capturing(self) -> str | bool: if self.is_globally_capturing(): return "global" if self._capture_fixture: @@ -782,7 +783,7 @@ def read_global_capture(self) -> CaptureResult[str]: # Fixture Control - def set_fixture(self, capture_fixture: "CaptureFixture[Any]") -> None: + def set_fixture(self, capture_fixture: CaptureFixture[Any]) -> None: if self._capture_fixture: current_fixture = self._capture_fixture.request.fixturename requested_fixture = capture_fixture.request.fixturename @@ -897,15 +898,15 @@ class CaptureFixture(Generic[AnyStr]): def __init__( self, - captureclass: Type[CaptureBase[AnyStr]], + captureclass: type[CaptureBase[AnyStr]], request: SubRequest, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) - self.captureclass: Type[CaptureBase[AnyStr]] = captureclass + self.captureclass: type[CaptureBase[AnyStr]] = captureclass self.request = request - self._capture: Optional[MultiCapture[AnyStr]] = None + self._capture: MultiCapture[AnyStr] | None = None self._captured_out: AnyStr = self.captureclass.EMPTY_BUFFER self._captured_err: AnyStr = self.captureclass.EMPTY_BUFFER diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 058aaa1ff30..47e3f5b5a50 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Command line options, ini-file and conftest.py processing.""" +from __future__ import annotations + import argparse import collections.abc import copy @@ -21,22 +23,16 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import IO from typing import Iterable from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import Type from typing import TYPE_CHECKING -from typing import Union import warnings import pluggy @@ -141,9 +137,9 @@ def filter_traceback_for_conftest_import_failure( def main( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> Union[int, ExitCode]: + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> int | ExitCode: """Perform an in-process test run. :param args: @@ -176,9 +172,7 @@ def main( return ExitCode.USAGE_ERROR else: try: - ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( - config=config - ) + ret: ExitCode | int = config.hook.pytest_cmdline_main(config=config) try: return ExitCode(ret) except ValueError: @@ -286,9 +280,9 @@ def directory_arg(path: str, optname: str) -> str: def get_config( - args: Optional[List[str]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": + args: list[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: # subsequent calls to main will create a fresh instance pluginmanager = PytestPluginManager() config = Config( @@ -310,7 +304,7 @@ def get_config( return config -def get_plugin_manager() -> "PytestPluginManager": +def get_plugin_manager() -> PytestPluginManager: """Obtain a new instance of the :py:class:`pytest.PytestPluginManager`, with default plugins already loaded. @@ -322,9 +316,9 @@ def get_plugin_manager() -> "PytestPluginManager": def _prepareconfig( - args: Optional[Union[List[str], "os.PathLike[str]"]] = None, - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, -) -> "Config": + args: list[str] | os.PathLike[str] | None = None, + plugins: Sequence[str | _PluggyPlugin] | None = None, +) -> Config: if args is None: args = sys.argv[1:] elif isinstance(args, os.PathLike): @@ -364,14 +358,14 @@ def _get_directory(path: Path) -> Path: def _get_legacy_hook_marks( method: Any, hook_type: str, - opt_names: Tuple[str, ...], -) -> Dict[str, bool]: + opt_names: tuple[str, ...], +) -> dict[str, bool]: if TYPE_CHECKING: # abuse typeguard from importlib to avoid massive method type union thats lacking a alias assert inspect.isroutine(method) - known_marks: Set[str] = {m.name for m in getattr(method, "pytestmark", [])} - must_warn: List[str] = [] - opts: Dict[str, bool] = {} + known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} + must_warn: list[str] = [] + opts: dict[str, bool] = {} for opt_name in opt_names: opt_attr = getattr(method, opt_name, AttributeError) if opt_attr is not AttributeError: @@ -410,13 +404,13 @@ def __init__(self) -> None: # -- State related to local conftest plugins. # All loaded conftest modules. - self._conftest_plugins: Set[types.ModuleType] = set() + self._conftest_plugins: set[types.ModuleType] = set() # All conftest modules applicable for a directory. # This includes the directory's own conftest modules as well # as those of its parent directories. - self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} + self._dirpath2confmods: dict[Path, list[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: Optional[Path] = None + self._confcutdir: Path | None = None # If set, conftest loading is skipped. self._noconftest = False @@ -430,7 +424,7 @@ def __init__(self) -> None: # previously we would issue a warning when a plugin was skipped, but # since we refactored warnings as first citizens of Config, they are # just stored here to be used later. - self.skipped_plugins: List[Tuple[str, str]] = [] + self.skipped_plugins: list[tuple[str, str]] = [] self.add_hookspecs(_pytest.hookspec) self.register(self) @@ -456,7 +450,7 @@ def __init__(self) -> None: def parse_hookimpl_opts( self, plugin: _PluggyPlugin, name: str - ) -> Optional[HookimplOpts]: + ) -> HookimplOpts | None: """:meta private:""" # pytest hooks are always prefixed with "pytest_", # so we avoid accessing possibly non-readable attributes @@ -480,7 +474,7 @@ def parse_hookimpl_opts( method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") ) - def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOpts]: + def parse_hookspec_opts(self, module_or_class, name: str) -> HookspecOpts | None: """:meta private:""" opts = super().parse_hookspec_opts(module_or_class, name) if opts is None: @@ -493,9 +487,7 @@ def parse_hookspec_opts(self, module_or_class, name: str) -> Optional[HookspecOp ) return opts - def register( - self, plugin: _PluggyPlugin, name: Optional[str] = None - ) -> Optional[str]: + def register(self, plugin: _PluggyPlugin, name: str | None = None) -> str | None: if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: warnings.warn( PytestConfigWarning( @@ -522,14 +514,14 @@ def register( def getplugin(self, name: str): # Support deprecated naming because plugins (xdist e.g.) use it. - plugin: Optional[_PluggyPlugin] = self.get_plugin(name) + plugin: _PluggyPlugin | None = self.get_plugin(name) return plugin def hasplugin(self, name: str) -> bool: """Return whether a plugin with the given name is registered.""" return bool(self.get_plugin(name)) - def pytest_configure(self, config: "Config") -> None: + def pytest_configure(self, config: Config) -> None: """:meta private:""" # XXX now that the pluginmanager exposes hookimpl(tryfirst...) # we should remove tryfirst/trylast as markers. @@ -552,13 +544,13 @@ def pytest_configure(self, config: "Config") -> None: # def _set_initial_conftests( self, - args: Sequence[Union[str, Path]], + args: Sequence[str | Path], pyargs: bool, noconftest: bool, rootpath: Path, - confcutdir: Optional[Path], + confcutdir: Path | None, invocation_dir: Path, - importmode: Union[ImportMode, str], + importmode: ImportMode | str, *, consider_namespace_packages: bool, ) -> None: @@ -619,7 +611,7 @@ def _is_in_confcutdir(self, path: Path) -> bool: def _try_load_conftest( self, anchor: Path, - importmode: Union[str, ImportMode], + importmode: str | ImportMode, rootpath: Path, *, consider_namespace_packages: bool, @@ -644,7 +636,7 @@ def _try_load_conftest( def _loadconftestmodules( self, path: Path, - importmode: Union[str, ImportMode], + importmode: str | ImportMode, rootpath: Path, *, consider_namespace_packages: bool, @@ -681,7 +673,7 @@ def _rget_with_confmod( self, name: str, path: Path, - ) -> Tuple[types.ModuleType, Any]: + ) -> tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path) for mod in reversed(modules): try: @@ -693,7 +685,7 @@ def _rget_with_confmod( def _importconftest( self, conftestpath: Path, - importmode: Union[str, ImportMode], + importmode: str | ImportMode, rootpath: Path, *, consider_namespace_packages: bool, @@ -832,7 +824,7 @@ def consider_module(self, mod: types.ModuleType) -> None: self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) def _import_plugin_specs( - self, spec: Union[None, types.ModuleType, str, Sequence[str]] + self, spec: None | types.ModuleType | str | Sequence[str] ) -> None: plugins = _get_plugin_specs_as_list(spec) for import_spec in plugins: @@ -877,8 +869,8 @@ def import_plugin(self, modname: str, consider_entry_points: bool = False) -> No def _get_plugin_specs_as_list( - specs: Union[None, types.ModuleType, str, Sequence[str]], -) -> List[str]: + specs: None | types.ModuleType | str | Sequence[str], +) -> list[str]: """Parse a plugins specification into a list of plugin names.""" # None means empty. if specs is None: @@ -999,9 +991,9 @@ class InvocationParams: Plugins accessing ``InvocationParams`` must be aware of that. """ - args: Tuple[str, ...] + args: tuple[str, ...] """The command-line arguments as passed to :func:`pytest.main`.""" - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] + plugins: Sequence[str | _PluggyPlugin] | None """Extra plugins, might be `None`.""" dir: Path """The directory from which :func:`pytest.main` was invoked.""" @@ -1010,7 +1002,7 @@ def __init__( self, *, args: Iterable[str], - plugins: Optional[Sequence[Union[str, _PluggyPlugin]]], + plugins: Sequence[str | _PluggyPlugin] | None, dir: Path, ) -> None: object.__setattr__(self, "args", tuple(args)) @@ -1032,13 +1024,13 @@ class ArgsSource(enum.Enum): TESTPATHS = enum.auto() # Set by cacheprovider plugin. - cache: Optional["Cache"] + cache: Cache | None def __init__( self, pluginmanager: PytestPluginManager, *, - invocation_params: Optional[InvocationParams] = None, + invocation_params: InvocationParams | None = None, ) -> None: from .argparsing import FILE_OR_DIR from .argparsing import Parser @@ -1083,17 +1075,17 @@ def __init__( self.trace = self.pluginmanager.trace.root.get("config") self.hook: pluggy.HookRelay = PathAwareHookProxy(self.pluginmanager.hook) # type: ignore[assignment] - self._inicache: Dict[str, Any] = {} + self._inicache: dict[str, Any] = {} self._override_ini: Sequence[str] = () - self._opt2dest: Dict[str, str] = {} - self._cleanup: List[Callable[[], None]] = [] + self._opt2dest: dict[str, str] = {} + self._cleanup: list[Callable[[], None]] = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) self.args_source = Config.ArgsSource.ARGS - self.args: List[str] = [] + self.args: list[str] = [] @property def rootpath(self) -> Path: @@ -1106,7 +1098,7 @@ def rootpath(self) -> Path: return self._rootpath @property - def inipath(self) -> Optional[Path]: + def inipath(self) -> Path | None: """The path to the :ref:`configfile `. :type: Optional[pathlib.Path] @@ -1137,15 +1129,15 @@ def _ensure_unconfigure(self) -> None: fin() def get_terminal_writer(self) -> TerminalWriter: - terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin( + terminalreporter: TerminalReporter | None = self.pluginmanager.get_plugin( "terminalreporter" ) assert terminalreporter is not None return terminalreporter._tw def pytest_cmdline_parse( - self, pluginmanager: PytestPluginManager, args: List[str] - ) -> "Config": + self, pluginmanager: PytestPluginManager, args: list[str] + ) -> Config: try: self.parse(args) except UsageError: @@ -1171,7 +1163,7 @@ def pytest_cmdline_parse( def notify_exception( self, excinfo: ExceptionInfo[BaseException], - option: Optional[argparse.Namespace] = None, + option: argparse.Namespace | None = None, ) -> None: if option and getattr(option, "fulltrace", False): style: TracebackStyle = "long" @@ -1194,7 +1186,7 @@ def cwd_relative_nodeid(self, nodeid: str) -> str: return nodeid @classmethod - def fromdictargs(cls, option_dict, args) -> "Config": + def fromdictargs(cls, option_dict, args) -> Config: """Constructor usable for subprocesses.""" config = get_config(args) config.option.__dict__.update(option_dict) @@ -1203,7 +1195,7 @@ def fromdictargs(cls, option_dict, args) -> "Config": config.pluginmanager.consider_pluginarg(x) return config - def _processopt(self, opt: "Argument") -> None: + def _processopt(self, opt: Argument) -> None: for name in opt._short_opts + opt._long_opts: self._opt2dest[name] = opt.dest @@ -1212,7 +1204,7 @@ def _processopt(self, opt: "Argument") -> None: setattr(self.option, opt.dest, opt.default) @hookimpl(trylast=True) - def pytest_load_initial_conftests(self, early_config: "Config") -> None: + 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. @@ -1303,7 +1295,7 @@ def _mark_plugins_for_rewrite(self, hook) -> None: for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) - def _validate_args(self, args: List[str], via: str) -> List[str]: + def _validate_args(self, args: list[str], via: str) -> list[str]: """Validate known args.""" self._parser._config_source_hint = via # type: ignore try: @@ -1318,13 +1310,13 @@ def _validate_args(self, args: List[str], via: str) -> List[str]: def _decide_args( self, *, - args: List[str], + args: list[str], pyargs: bool, - testpaths: List[str], + testpaths: list[str], invocation_dir: Path, rootpath: Path, warn: bool, - ) -> Tuple[List[str], ArgsSource]: + ) -> tuple[list[str], ArgsSource]: """Decide the args (initial paths/nodeids) to use given the relevant inputs. :param warn: Whether can issue warnings. @@ -1360,7 +1352,7 @@ def _decide_args( result = [str(invocation_dir)] return result, source - def _preparse(self, args: List[str], addopts: bool = True) -> None: + def _preparse(self, args: list[str], addopts: bool = True) -> None: if addopts: env_addopts = os.environ.get("PYTEST_ADDOPTS", "") if len(env_addopts): @@ -1484,11 +1476,11 @@ def _warn_or_fail_if_strict(self, message: str) -> None: self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) - def _get_unknown_ini_keys(self) -> List[str]: + def _get_unknown_ini_keys(self) -> list[str]: parser_inicfg = self._parser._inidict return [name for name in self.inicfg if name not in parser_inicfg] - def parse(self, args: List[str], addopts: bool = True) -> None: + def parse(self, args: list[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. assert ( self.args == [] @@ -1592,7 +1584,7 @@ def getini(self, name: str): # Meant for easy monkeypatching by legacypath plugin. # Can be inlined back (with no cover removed) once legacypath is gone. - def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]): + def _getini_unknown_type(self, name: str, type: str, value: str | list[str]): msg = f"unknown configuration type: {type}" raise ValueError(msg, value) # pragma: no cover @@ -1648,14 +1640,14 @@ def _getini(self, name: str): else: return self._getini_unknown_type(name, type, value) - def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: + def _getconftest_pathlist(self, name: str, path: Path) -> list[Path] | None: try: mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None assert mod.__file__ is not None modpath = Path(mod.__file__).parent - values: List[Path] = [] + values: list[Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): relroot = Path(relroot) @@ -1665,7 +1657,7 @@ def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]: values.append(relroot) return values - def _get_override_ini_value(self, name: str) -> Optional[str]: + def _get_override_ini_value(self, name: str) -> str | None: value = None # override_ini is a list of "ini=value" options. # Always use the last item if multiple values are set for same ini-name, @@ -1720,7 +1712,7 @@ def getvalueorskip(self, name: str, path=None): VERBOSITY_TEST_CASES: Final = "test_cases" _VERBOSITY_INI_DEFAULT: Final = "auto" - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: r"""Retrieve the verbosity level for a fine-grained verbosity type. :param verbosity_type: Verbosity type to get level for. If a level is @@ -1771,7 +1763,7 @@ def _verbosity_ini_name(verbosity_type: str) -> str: return f"verbosity_{verbosity_type}" @staticmethod - def _add_verbosity_ini(parser: "Parser", verbosity_type: str, help: str) -> None: + def _add_verbosity_ini(parser: Parser, verbosity_type: str, help: str) -> None: """Add a output verbosity configuration option for the given output type. :param parser: Parser for command line arguments and ini-file values. @@ -1827,7 +1819,7 @@ def _assertion_supported() -> bool: def create_terminal_writer( - config: Config, file: Optional[TextIO] = None + config: Config, file: TextIO | None = None ) -> TerminalWriter: """Create a TerminalWriter instance configured according to the options in the config object. @@ -1871,7 +1863,7 @@ def _strtobool(val: str) -> bool: @lru_cache(maxsize=50) def parse_warning_filter( arg: str, *, escape: bool -) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: +) -> tuple[warnings._ActionKind, str, type[Warning], str, int]: """Parse a warnings filter string. This is copied from warnings._setoption with the following changes: @@ -1917,7 +1909,7 @@ def parse_warning_filter( except warnings._OptionError as e: raise UsageError(error_template.format(error=str(e))) from None try: - category: Type[Warning] = _resolve_warning_category(category_) + category: type[Warning] = _resolve_warning_category(category_) except Exception: exc_info = ExceptionInfo.from_current() exception_text = exc_info.getrepr(style="native") @@ -1940,7 +1932,7 @@ def parse_warning_filter( return action, message, category, module, lineno -def _resolve_warning_category(category: str) -> Type[Warning]: +def _resolve_warning_category(category: str) -> type[Warning]: """ Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) propagate so we can get access to their tracebacks (#9218). diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index f270b864c6a..85aa4632702 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse from gettext import gettext import os @@ -6,16 +8,12 @@ from typing import Any from typing import Callable from typing import cast -from typing import Dict from typing import final from typing import List from typing import Literal from typing import Mapping from typing import NoReturn -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import _pytest._io from _pytest.config.exceptions import UsageError @@ -41,32 +39,32 @@ class Parser: there's an error processing the command line arguments. """ - prog: Optional[str] = None + prog: str | None = None def __init__( self, - usage: Optional[str] = None, - processopt: Optional[Callable[["Argument"], None]] = None, + usage: str | None = None, + processopt: Callable[[Argument], None] | None = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) self._anonymous = OptionGroup("Custom options", parser=self, _ispytest=True) - self._groups: List[OptionGroup] = [] + self._groups: list[OptionGroup] = [] self._processopt = processopt self._usage = usage - self._inidict: Dict[str, Tuple[str, Optional[str], Any]] = {} - self._ininames: List[str] = [] - self.extra_info: Dict[str, Any] = {} + self._inidict: dict[str, tuple[str, str | None, Any]] = {} + self._ininames: list[str] = [] + self.extra_info: dict[str, Any] = {} - def processoption(self, option: "Argument") -> None: + def processoption(self, option: Argument) -> None: if self._processopt: if option.dest: self._processopt(option) def getgroup( - self, name: str, description: str = "", after: Optional[str] = None - ) -> "OptionGroup": + self, name: str, description: str = "", after: str | None = None + ) -> OptionGroup: """Get (or create) a named option Group. :param name: Name of the option group. @@ -108,8 +106,8 @@ def addoption(self, *opts: str, **attrs: Any) -> None: def parse( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: from _pytest._argcomplete import try_argcomplete @@ -118,7 +116,7 @@ def parse( strargs = [os.fspath(x) for x in args] return self.optparser.parse_args(strargs, namespace=namespace) - def _getparser(self) -> "MyOptionParser": + def _getparser(self) -> MyOptionParser: from _pytest._argcomplete import filescompleter optparser = MyOptionParser(self, self.extra_info, prog=self.prog) @@ -139,10 +137,10 @@ def _getparser(self) -> "MyOptionParser": def parse_setoption( self, - args: Sequence[Union[str, "os.PathLike[str]"]], + args: Sequence[str | os.PathLike[str]], option: argparse.Namespace, - namespace: Optional[argparse.Namespace] = None, - ) -> List[str]: + namespace: argparse.Namespace | None = None, + ) -> list[str]: parsedoption = self.parse(args, namespace=namespace) for name, value in parsedoption.__dict__.items(): setattr(option, name, value) @@ -150,8 +148,8 @@ def parse_setoption( def parse_known_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Parse the known arguments at this point. @@ -161,9 +159,9 @@ def parse_known_args( def parse_known_and_unknown_args( self, - args: Sequence[Union[str, "os.PathLike[str]"]], - namespace: Optional[argparse.Namespace] = None, - ) -> Tuple[argparse.Namespace, List[str]]: + args: Sequence[str | os.PathLike[str]], + namespace: argparse.Namespace | None = None, + ) -> tuple[argparse.Namespace, list[str]]: """Parse the known arguments at this point, and also return the remaining unknown arguments. @@ -179,9 +177,8 @@ def addini( self, name: str, help: str, - type: Optional[ - Literal["string", "paths", "pathlist", "args", "linelist", "bool"] - ] = None, + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] + | None = None, default: Any = NOT_SET, ) -> None: """Register an ini-file option. @@ -224,7 +221,7 @@ def addini( def get_ini_default_for_type( - type: Optional[Literal["string", "paths", "pathlist", "args", "linelist", "bool"]], + type: Literal["string", "paths", "pathlist", "args", "linelist", "bool"] | None, ) -> Any: """ Used by addini to get the default value for a given ini-option type, when @@ -244,7 +241,7 @@ class ArgumentError(Exception): """Raised if an Argument instance is created with invalid or inconsistent arguments.""" - def __init__(self, msg: str, option: Union["Argument", str]) -> None: + def __init__(self, msg: str, option: Argument | str) -> None: self.msg = msg self.option_id = str(option) @@ -267,8 +264,8 @@ class Argument: def __init__(self, *names: str, **attrs: Any) -> None: """Store params in private vars for use in add_argument.""" self._attrs = attrs - self._short_opts: List[str] = [] - self._long_opts: List[str] = [] + self._short_opts: list[str] = [] + self._long_opts: list[str] = [] try: self.type = attrs["type"] except KeyError: @@ -279,7 +276,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: except KeyError: pass self._set_opt_strings(names) - dest: Optional[str] = attrs.get("dest") + dest: str | None = attrs.get("dest") if dest: self.dest = dest elif self._long_opts: @@ -291,7 +288,7 @@ def __init__(self, *names: str, **attrs: Any) -> None: self.dest = "???" # Needed for the error repr. raise ArgumentError("need a long or short option", self) from e - def names(self) -> List[str]: + def names(self) -> list[str]: return self._short_opts + self._long_opts def attrs(self) -> Mapping[str, Any]: @@ -335,7 +332,7 @@ def _set_opt_strings(self, opts: Sequence[str]) -> None: self._long_opts.append(opt) def __repr__(self) -> str: - args: List[str] = [] + args: list[str] = [] if self._short_opts: args += ["_short_opts: " + repr(self._short_opts)] if self._long_opts: @@ -355,14 +352,14 @@ def __init__( self, name: str, description: str = "", - parser: Optional[Parser] = None, + parser: Parser | None = None, *, _ispytest: bool = False, ) -> None: check_ispytest(_ispytest) self.name = name self.description = description - self.options: List[Argument] = [] + self.options: list[Argument] = [] self.parser = parser def addoption(self, *opts: str, **attrs: Any) -> None: @@ -391,7 +388,7 @@ def _addoption(self, *opts: str, **attrs: Any) -> None: option = Argument(*opts, **attrs) self._addoption_instance(option, shortupper=True) - def _addoption_instance(self, option: "Argument", shortupper: bool = False) -> None: + def _addoption_instance(self, option: Argument, shortupper: bool = False) -> None: if not shortupper: for opt in option._short_opts: if opt[0] == "-" and opt[1].islower(): @@ -405,8 +402,8 @@ class MyOptionParser(argparse.ArgumentParser): def __init__( self, parser: Parser, - extra_info: Optional[Dict[str, Any]] = None, - prog: Optional[str] = None, + extra_info: dict[str, Any] | None = None, + prog: str | None = None, ) -> None: self._parser = parser super().__init__( @@ -433,8 +430,8 @@ def error(self, message: str) -> NoReturn: # Type ignored because typeshed has a very complex type in the superclass. def parse_args( # type: ignore self, - args: Optional[Sequence[str]] = None, - namespace: Optional[argparse.Namespace] = None, + args: Sequence[str] | None = None, + namespace: argparse.Namespace | None = None, ) -> argparse.Namespace: """Allow splitting of positional arguments.""" parsed, unrecognized = self.parse_known_args(args, namespace) @@ -455,7 +452,7 @@ def parse_args( # type: ignore # disable long --argument abbreviations without breaking short flags. def _parse_optional( self, arg_string: str - ) -> Optional[Tuple[Optional[argparse.Action], str, Optional[str]]]: + ) -> tuple[argparse.Action | None, str, str | None] | None: if not arg_string: return None if arg_string[0] not in self.prefix_chars: @@ -507,7 +504,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: orgstr = super()._format_action_invocation(action) if orgstr and orgstr[0] != "-": # only optional arguments return orgstr - res: Optional[str] = getattr(action, "_formatted_action_invocation", None) + res: str | None = getattr(action, "_formatted_action_invocation", None) if res: return res options = orgstr.split(", ") @@ -516,7 +513,7 @@ def _format_action_invocation(self, action: argparse.Action) -> str: action._formatted_action_invocation = orgstr # type: ignore return orgstr return_list = [] - short_long: Dict[str, str] = {} + short_long: dict[str, str] = {} for option in options: if len(option) == 2 or option[2] == " ": continue diff --git a/src/_pytest/config/exceptions.py b/src/_pytest/config/exceptions.py index 4031ea732f3..90108eca904 100644 --- a/src/_pytest/config/exceptions.py +++ b/src/_pytest/config/exceptions.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import final diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 9909376de0f..ce4c990b810 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -1,13 +1,10 @@ +from __future__ import annotations + import os from pathlib import Path import sys -from typing import Dict from typing import Iterable -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import iniconfig @@ -32,7 +29,7 @@ def _parse_ini_config(path: Path) -> iniconfig.IniConfig: def load_config_dict_from_file( filepath: Path, -) -> Optional[Dict[str, Union[str, List[str]]]]: +) -> dict[str, str | list[str]] | None: """Load pytest configuration from the given file path, if supported. Return None if the file does not contain valid pytest configuration. @@ -77,7 +74,7 @@ def load_config_dict_from_file( # TOML supports richer data types than ini files (strings, arrays, floats, ints, etc), # however we need to convert all scalar values to str for compatibility with the rest # of the configuration system, which expects strings only. - def make_scalar(v: object) -> Union[str, List[str]]: + def make_scalar(v: object) -> str | list[str]: return v if isinstance(v, list) else str(v) return {k: make_scalar(v) for k, v in result.items()} @@ -88,7 +85,7 @@ def make_scalar(v: object) -> Union[str, List[str]]: def locate_config( invocation_dir: Path, args: Iterable[Path], -) -> Tuple[Optional[Path], Optional[Path], Dict[str, Union[str, List[str]]]]: +) -> tuple[Path | None, Path | None, dict[str, str | list[str]]]: """Search in the list of arguments for a valid ini-file for pytest, and return a tuple of (rootdir, inifile, cfg-dict).""" config_names = [ @@ -101,7 +98,7 @@ def locate_config( args = [x for x in args if not str(x).startswith("-")] if not args: args = [invocation_dir] - found_pyproject_toml: Optional[Path] = None + found_pyproject_toml: Path | None = None for arg in args: argpath = absolutepath(arg) for base in (argpath, *argpath.parents): @@ -122,7 +119,7 @@ def get_common_ancestor( invocation_dir: Path, paths: Iterable[Path], ) -> Path: - common_ancestor: Optional[Path] = None + common_ancestor: Path | None = None for path in paths: if not path.exists(): continue @@ -144,7 +141,7 @@ def get_common_ancestor( return common_ancestor -def get_dirs_from_args(args: Iterable[str]) -> List[Path]: +def get_dirs_from_args(args: Iterable[str]) -> list[Path]: def is_option(x: str) -> bool: return x.startswith("-") @@ -171,11 +168,11 @@ def get_dir_from_path(path: Path) -> Path: def determine_setup( *, - inifile: Optional[str], + inifile: str | None, args: Sequence[str], - rootdir_cmd_arg: Optional[str], + rootdir_cmd_arg: str | None, invocation_dir: Path, -) -> Tuple[Path, Optional[Path], Dict[str, Union[str, List[str]]]]: +) -> tuple[Path, Path | None, dict[str, str | list[str]]]: """Determine the rootdir, inifile and ini configuration values from the command line arguments. @@ -192,7 +189,7 @@ def determine_setup( dirs = get_dirs_from_args(args) if inifile: inipath_ = absolutepath(inifile) - inipath: Optional[Path] = inipath_ + inipath: Path | None = inipath_ inicfg = load_config_dict_from_file(inipath_) or {} if rootdir_cmd_arg is None: rootdir = inipath_.parent diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index eacb2836d6c..3e1463fff26 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -2,6 +2,8 @@ # ruff: noqa: T100 """Interactive debugging with PDB, the Python Debugger.""" +from __future__ import annotations + import argparse import functools import sys @@ -9,11 +11,6 @@ from typing import Any from typing import Callable from typing import Generator -from typing import List -from typing import Optional -from typing import Tuple -from typing import Type -from typing import Union import unittest from _pytest import outcomes @@ -30,7 +27,7 @@ from _pytest.runner import CallInfo -def _validate_usepdb_cls(value: str) -> Tuple[str, str]: +def _validate_usepdb_cls(value: str) -> tuple[str, str]: """Validate syntax of --pdbcls option.""" try: modname, classname = value.split(":") @@ -95,22 +92,22 @@ def fin() -> None: class pytestPDB: """Pseudo PDB that defers to the real pdb.""" - _pluginmanager: Optional[PytestPluginManager] = None - _config: Optional[Config] = None - _saved: List[ - Tuple[Callable[..., None], Optional[PytestPluginManager], Optional[Config]] + _pluginmanager: PytestPluginManager | None = None + _config: Config | None = None + _saved: list[ + tuple[Callable[..., None], PytestPluginManager | None, Config | None] ] = [] _recursive_debug = 0 - _wrapped_pdb_cls: Optional[Tuple[Type[Any], Type[Any]]] = None + _wrapped_pdb_cls: tuple[type[Any], type[Any]] | None = None @classmethod - def _is_capturing(cls, capman: Optional["CaptureManager"]) -> Union[str, bool]: + def _is_capturing(cls, capman: CaptureManager | None) -> str | bool: if capman: return capman.is_capturing() return False @classmethod - def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): + def _import_pdb_cls(cls, capman: CaptureManager | None): if not cls._config: import pdb @@ -149,7 +146,7 @@ def _import_pdb_cls(cls, capman: Optional["CaptureManager"]): return wrapped_cls @classmethod - def _get_pdb_wrapper_class(cls, pdb_cls, capman: Optional["CaptureManager"]): + def _get_pdb_wrapper_class(cls, pdb_cls, capman: CaptureManager | None): import _pytest.config class PytestPdbWrapper(pdb_cls): @@ -238,7 +235,7 @@ def _init_pdb(cls, method, *args, **kwargs): import _pytest.config if cls._pluginmanager is None: - capman: Optional[CaptureManager] = None + capman: CaptureManager | None = None else: capman = cls._pluginmanager.getplugin("capturemanager") if capman: @@ -281,7 +278,7 @@ def set_trace(cls, *args, **kwargs) -> None: class PdbInvoke: def pytest_exception_interact( - self, node: Node, call: "CallInfo[Any]", report: BaseReport + self, node: Node, call: CallInfo[Any], report: BaseReport ) -> None: capman = node.config.pluginmanager.getplugin("capturemanager") if capman: diff --git a/src/_pytest/deprecated.py b/src/_pytest/deprecated.py index 10811d158aa..a605c24e58f 100644 --- a/src/_pytest/deprecated.py +++ b/src/_pytest/deprecated.py @@ -9,6 +9,8 @@ in case of warnings which need to format their messages. """ +from __future__ import annotations + from warnings import warn from _pytest.warning_types import PytestDeprecationWarning diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py index e61694d553f..cb46d9a3bb5 100644 --- a/src/_pytest/doctest.py +++ b/src/_pytest/doctest.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Discover and run doctests in modules and test files.""" +from __future__ import annotations + import bdb from contextlib import contextmanager import functools @@ -13,17 +15,11 @@ import types from typing import Any from typing import Callable -from typing import Dict from typing import Generator from typing import Iterable -from typing import List -from typing import Optional from typing import Pattern from typing import Sequence -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union import warnings from _pytest import outcomes @@ -67,7 +63,7 @@ # Lazy definition of runner class RUNNER_CLASS = None # Lazy definition of output checker class -CHECKER_CLASS: Optional[Type["doctest.OutputChecker"]] = None +CHECKER_CLASS: type[doctest.OutputChecker] | None = None def pytest_addoption(parser: Parser) -> None: @@ -129,7 +125,7 @@ def pytest_unconfigure() -> None: def pytest_collect_file( file_path: Path, parent: Collector, -) -> Optional[Union["DoctestModule", "DoctestTextfile"]]: +) -> DoctestModule | DoctestTextfile | None: config = parent.config if file_path.suffix == ".py": if config.option.doctestmodules and not any( @@ -161,7 +157,7 @@ def _is_main_py(path: Path) -> bool: class ReprFailDoctest(TerminalRepr): def __init__( - self, reprlocation_lines: Sequence[Tuple[ReprFileLocation, Sequence[str]]] + self, reprlocation_lines: Sequence[tuple[ReprFileLocation, Sequence[str]]] ) -> None: self.reprlocation_lines = reprlocation_lines @@ -173,12 +169,12 @@ def toterminal(self, tw: TerminalWriter) -> None: class MultipleDoctestFailures(Exception): - def __init__(self, failures: Sequence["doctest.DocTestFailure"]) -> None: + def __init__(self, failures: Sequence[doctest.DocTestFailure]) -> None: super().__init__() self.failures = failures -def _init_runner_class() -> Type["doctest.DocTestRunner"]: +def _init_runner_class() -> type[doctest.DocTestRunner]: import doctest class PytestDoctestRunner(doctest.DebugRunner): @@ -190,8 +186,8 @@ class PytestDoctestRunner(doctest.DebugRunner): def __init__( self, - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, ) -> None: @@ -201,8 +197,8 @@ def __init__( def report_failure( self, out, - test: "doctest.DocTest", - example: "doctest.Example", + test: doctest.DocTest, + example: doctest.Example, got: str, ) -> None: failure = doctest.DocTestFailure(test, example, got) @@ -214,9 +210,9 @@ def report_failure( def report_unexpected_exception( self, out, - test: "doctest.DocTest", - example: "doctest.Example", - exc_info: Tuple[Type[BaseException], BaseException, types.TracebackType], + test: doctest.DocTest, + example: doctest.Example, + exc_info: tuple[type[BaseException], BaseException, types.TracebackType], ) -> None: if isinstance(exc_info[1], OutcomeException): raise exc_info[1] @@ -232,11 +228,11 @@ def report_unexpected_exception( def _get_runner( - checker: Optional["doctest.OutputChecker"] = None, - verbose: Optional[bool] = None, + checker: doctest.OutputChecker | None = None, + verbose: bool | None = None, optionflags: int = 0, continue_on_failure: bool = True, -) -> "doctest.DocTestRunner": +) -> doctest.DocTestRunner: # We need this in order to do a lazy import on doctest global RUNNER_CLASS if RUNNER_CLASS is None: @@ -255,9 +251,9 @@ class DoctestItem(Item): def __init__( self, name: str, - parent: "Union[DoctestTextfile, DoctestModule]", - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", + parent: DoctestTextfile | DoctestModule, + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, ) -> None: super().__init__(name, parent) self.runner = runner @@ -274,18 +270,18 @@ def __init__( @classmethod def from_parent( # type: ignore[override] cls, - parent: "Union[DoctestTextfile, DoctestModule]", + parent: DoctestTextfile | DoctestModule, *, name: str, - runner: "doctest.DocTestRunner", - dtest: "doctest.DocTest", - ) -> "Self": + runner: doctest.DocTestRunner, + dtest: doctest.DocTest, + ) -> Self: # incompatible signature due to imposed limits on subclass """The public named constructor.""" return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: dict[str, object] = {} self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type] def setup(self) -> None: @@ -298,7 +294,7 @@ def setup(self) -> None: def runtest(self) -> None: _check_all_skipped(self.dtest) 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 # doctest expects. self.runner.run(self.dtest, out=failures) # type: ignore[arg-type] @@ -320,12 +316,12 @@ def _disable_output_capturing_for_darwin(self) -> None: def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: import doctest - failures: Optional[ - Sequence[Union[doctest.DocTestFailure, doctest.UnexpectedException]] - ] = None + failures: ( + Sequence[doctest.DocTestFailure | doctest.UnexpectedException] | None + ) = None if isinstance( excinfo.value, (doctest.DocTestFailure, doctest.UnexpectedException) ): @@ -381,11 +377,11 @@ def repr_failure( # type: ignore[override] reprlocation_lines.append((reprlocation, lines)) return ReprFailDoctest(reprlocation_lines) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: return self.path, self.dtest.lineno, f"[doctest] {self.name}" -def _get_flag_lookup() -> Dict[str, int]: +def _get_flag_lookup() -> dict[str, int]: import doctest return dict( @@ -451,7 +447,7 @@ def collect(self) -> Iterable[DoctestItem]: ) -def _check_all_skipped(test: "doctest.DocTest") -> None: +def _check_all_skipped(test: doctest.DocTest) -> None: """Raise pytest.skip() if all examples in the given DocTest have the SKIP option set.""" import doctest @@ -477,7 +473,7 @@ def _patch_unwrap_mock_aware() -> Generator[None, None, None]: real_unwrap = inspect.unwrap def _mock_aware_unwrap( - func: Callable[..., Any], *, stop: Optional[Callable[[Any], Any]] = None + func: Callable[..., Any], *, stop: Callable[[Any], Any] | None = None ) -> Any: try: if stop is None or stop is _is_mocked: @@ -594,7 +590,7 @@ def _from_module(self, module, object): ) -def _init_checker_class() -> Type["doctest.OutputChecker"]: +def _init_checker_class() -> type[doctest.OutputChecker]: import doctest import re @@ -662,8 +658,8 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return got offset = 0 for w, g in zip(wants, gots): - fraction: Optional[str] = w.group("fraction") - exponent: Optional[str] = w.group("exponent1") + fraction: str | None = w.group("fraction") + exponent: str | None = w.group("exponent1") if exponent is None: exponent = w.group("exponent2") precision = 0 if fraction is None else len(fraction) @@ -682,7 +678,7 @@ def _remove_unwanted_precision(self, want: str, got: str) -> str: return LiteralsOutputChecker -def _get_checker() -> "doctest.OutputChecker": +def _get_checker() -> doctest.OutputChecker: """Return a doctest.OutputChecker subclass that supports some additional options: @@ -741,7 +737,7 @@ def _get_report_choice(key: str) -> int: @fixture(scope="session") -def doctest_namespace() -> Dict[str, Any]: +def doctest_namespace() -> dict[str, Any]: """Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index 083bcb83739..07e60f03fc9 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import os import sys from typing import Generator diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 383084e07f1..0151a4d9c86 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import abc from collections import defaultdict from collections import deque @@ -20,7 +22,6 @@ from typing import Generic from typing import Iterable from typing import Iterator -from typing import List from typing import Mapping from typing import MutableMapping from typing import NoReturn @@ -28,7 +29,6 @@ from typing import OrderedDict from typing import overload from typing import Sequence -from typing import Set from typing import Tuple from typing import TYPE_CHECKING from typing import TypeVar @@ -113,18 +113,18 @@ @dataclasses.dataclass(frozen=True) class PseudoFixtureDef(Generic[FixtureValue]): - cached_result: "_FixtureCachedResult[FixtureValue]" + cached_result: _FixtureCachedResult[FixtureValue] _scope: Scope -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._fixturemanager = FixtureManager(session) def get_scope_package( node: nodes.Item, - fixturedef: "FixtureDef[object]", -) -> Optional[nodes.Node]: + fixturedef: FixtureDef[object], +) -> nodes.Node | None: from _pytest.python import Package for parent in node.iter_parents(): @@ -133,7 +133,7 @@ def get_scope_package( return node.session -def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]: +def get_scope_node(node: nodes.Node, scope: Scope) -> nodes.Node | None: import _pytest.python if scope is Scope.Function: @@ -152,7 +152,7 @@ def get_scope_node(node: nodes.Node, scope: Scope) -> Optional[nodes.Node]: assert_never(scope) -def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: +def getfixturemarker(obj: object) -> FixtureFunctionMarker | None: """Return fixturemarker or None if it doesn't exist or raised exceptions.""" return cast( @@ -171,8 +171,8 @@ def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]: class FixtureArgKey: argname: str param_index: int - scoped_item_path: Optional[Path] - item_cls: Optional[type] + scoped_item_path: Path | None + item_cls: type | None _V = TypeVar("_V") @@ -212,10 +212,10 @@ def get_parametrized_fixture_argkeys( yield FixtureArgKey(argname, param_index, scoped_item_path, item_cls) -def reorder_items(items: Sequence[nodes.Item]) -> List[nodes.Item]: - argkeys_by_item: Dict[Scope, Dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {} - items_by_argkey: Dict[ - Scope, Dict[FixtureArgKey, OrderedDict[nodes.Item, None]] +def reorder_items(items: Sequence[nodes.Item]) -> list[nodes.Item]: + argkeys_by_item: dict[Scope, dict[nodes.Item, OrderedSet[FixtureArgKey]]] = {} + items_by_argkey: dict[ + Scope, dict[FixtureArgKey, OrderedDict[nodes.Item, None]] ] = {} for scope in HIGH_SCOPES: scoped_argkeys_by_item = argkeys_by_item[scope] = {} @@ -249,7 +249,7 @@ def reorder_items_atscope( scoped_items_by_argkey = items_by_argkey[scope] scoped_argkeys_by_item = argkeys_by_item[scope] - ignore: Set[FixtureArgKey] = set() + ignore: set[FixtureArgKey] = set() items_deque = deque(items) items_done: OrderedSet[nodes.Item] = {} while items_deque: @@ -309,19 +309,19 @@ class FuncFixtureInfo: __slots__ = ("argnames", "initialnames", "names_closure", "name2fixturedefs") # Fixture names that the item requests directly by function parameters. - argnames: Tuple[str, ...] + argnames: tuple[str, ...] # Fixture names that the item immediately requires. These include # argnames + fixture names specified via usefixtures and via autouse=True in # fixture definitions. - initialnames: Tuple[str, ...] + initialnames: tuple[str, ...] # The transitive closure of the fixture names that the item requires. # Note: can't include dynamic dependencies (`request.getfixturevalue` calls). - names_closure: List[str] + names_closure: list[str] # A map from a fixture name in the transitive closure to the FixtureDefs # matching the name which are applicable to this function. # There may be multiple overriding fixtures with the same name. The # sequence is ordered from furthest to closes to the function. - name2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]] + name2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] def prune_dependency_tree(self) -> None: """Recompute names_closure from initialnames and name2fixturedefs. @@ -334,7 +334,7 @@ def prune_dependency_tree(self) -> None: tree. In this way the dependency tree can get pruned, and the closure of argnames may get reduced. """ - closure: Set[str] = set() + closure: set[str] = set() working_set = set(self.initialnames) while working_set: argname = working_set.pop() @@ -360,10 +360,10 @@ class FixtureRequest(abc.ABC): def __init__( self, - pyfuncitem: "Function", - fixturename: Optional[str], - arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]], - fixture_defs: Dict[str, "FixtureDef[Any]"], + pyfuncitem: Function, + fixturename: str | None, + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]], + fixture_defs: dict[str, FixtureDef[Any]], *, _ispytest: bool = False, ) -> None: @@ -390,7 +390,7 @@ def __init__( self.param: Any @property - def _fixturemanager(self) -> "FixtureManager": + def _fixturemanager(self) -> FixtureManager: return self._pyfuncitem.session._fixturemanager @property @@ -406,13 +406,13 @@ def scope(self) -> _ScopeName: @abc.abstractmethod def _check_scope( self, - requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], requested_scope: Scope, ) -> None: raise NotImplementedError() @property - def fixturenames(self) -> List[str]: + def fixturenames(self) -> list[str]: """Names of all active fixtures in this request.""" result = list(self._pyfuncitem.fixturenames) result.extend(set(self._fixture_defs).difference(result)) @@ -477,7 +477,7 @@ def keywords(self) -> MutableMapping[str, Any]: return node.keywords @property - def session(self) -> "Session": + def session(self) -> Session: """Pytest session object.""" return self._pyfuncitem.session @@ -487,7 +487,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: the last test within the requesting test context finished execution.""" raise NotImplementedError() - def applymarker(self, marker: Union[str, MarkDecorator]) -> None: + def applymarker(self, marker: str | MarkDecorator) -> None: """Apply a marker to a single test function invocation. This method is useful if you don't want to have a keyword/marker @@ -498,7 +498,7 @@ def applymarker(self, marker: Union[str, MarkDecorator]) -> None: """ self.node.add_marker(marker) - def raiseerror(self, msg: Optional[str]) -> NoReturn: + def raiseerror(self, msg: str | None) -> NoReturn: """Raise a FixtureLookupError exception. :param msg: @@ -535,7 +535,7 @@ def getfixturevalue(self, argname: str) -> Any: ) return fixturedef.cached_result[0] - def _iter_chain(self) -> Iterator["SubRequest"]: + def _iter_chain(self) -> Iterator[SubRequest]: """Yield all SubRequests in the chain, from self up. Note: does *not* yield the TopRequest. @@ -547,7 +547,7 @@ def _iter_chain(self) -> Iterator["SubRequest"]: def _get_active_fixturedef( self, argname: str - ) -> Union["FixtureDef[object]", PseudoFixtureDef[object]]: + ) -> FixtureDef[object] | PseudoFixtureDef[object]: if argname == "request": cached_result = (self, [0], None) return PseudoFixtureDef(cached_result, Scope.Function) @@ -618,7 +618,7 @@ def _get_active_fixturedef( self._fixture_defs[argname] = fixturedef return fixturedef - def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> None: + def _check_fixturedef_without_param(self, fixturedef: FixtureDef[object]) -> None: """Check that this request is allowed to execute this fixturedef without a param.""" funcitem = self._pyfuncitem @@ -651,7 +651,7 @@ def _check_fixturedef_without_param(self, fixturedef: "FixtureDef[object]") -> N ) fail(msg, pytrace=False) - def _get_fixturestack(self) -> List["FixtureDef[Any]"]: + def _get_fixturestack(self) -> list[FixtureDef[Any]]: values = [request._fixturedef for request in self._iter_chain()] values.reverse() return values @@ -661,7 +661,7 @@ def _get_fixturestack(self) -> List["FixtureDef[Any]"]: class TopRequest(FixtureRequest): """The type of the ``request`` fixture in a test function.""" - def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None: + def __init__(self, pyfuncitem: Function, *, _ispytest: bool = False) -> None: super().__init__( fixturename=None, pyfuncitem=pyfuncitem, @@ -676,7 +676,7 @@ def _scope(self) -> Scope: def _check_scope( self, - requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], requested_scope: Scope, ) -> None: # TopRequest always has function scope so always valid. @@ -710,7 +710,7 @@ def __init__( scope: Scope, param: Any, param_index: int, - fixturedef: "FixtureDef[object]", + fixturedef: FixtureDef[object], *, _ispytest: bool = False, ) -> None: @@ -740,7 +740,7 @@ def node(self): scope = self._scope if scope is Scope.Function: # This might also be a non-function Item despite its attribute name. - node: Optional[nodes.Node] = self._pyfuncitem + node: nodes.Node | None = self._pyfuncitem elif scope is Scope.Package: node = get_scope_package(self._pyfuncitem, self._fixturedef) else: @@ -753,7 +753,7 @@ def node(self): def _check_scope( self, - requested_fixturedef: Union["FixtureDef[object]", PseudoFixtureDef[object]], + requested_fixturedef: FixtureDef[object] | PseudoFixtureDef[object], requested_scope: Scope, ) -> None: if isinstance(requested_fixturedef, PseudoFixtureDef): @@ -774,7 +774,7 @@ def _check_scope( pytrace=False, ) - def _format_fixturedef_line(self, fixturedef: "FixtureDef[object]") -> str: + def _format_fixturedef_line(self, fixturedef: FixtureDef[object]) -> str: factory = fixturedef.func path, lineno = getfslineno(factory) if isinstance(path, Path): @@ -791,15 +791,15 @@ class FixtureLookupError(LookupError): """Could not return a requested fixture (missing or invalid).""" def __init__( - self, argname: Optional[str], request: FixtureRequest, msg: Optional[str] = None + self, argname: str | None, request: FixtureRequest, msg: str | None = None ) -> None: self.argname = argname self.request = request self.fixturestack = request._get_fixturestack() self.msg = msg - def formatrepr(self) -> "FixtureLookupErrorRepr": - tblines: List[str] = [] + def formatrepr(self) -> FixtureLookupErrorRepr: + tblines: list[str] = [] addline = tblines.append stack = [self.request._pyfuncitem.obj] stack.extend(map(lambda x: x.func, self.fixturestack)) @@ -847,11 +847,11 @@ def formatrepr(self) -> "FixtureLookupErrorRepr": class FixtureLookupErrorRepr(TerminalRepr): def __init__( self, - filename: Union[str, "os.PathLike[str]"], + filename: str | os.PathLike[str], firstlineno: int, tblines: Sequence[str], errorstring: str, - argname: Optional[str], + argname: str | None, ) -> None: self.tblines = tblines self.errorstring = errorstring @@ -879,7 +879,7 @@ def toterminal(self, tw: TerminalWriter) -> None: def call_fixture_func( - fixturefunc: "_FixtureFunc[FixtureValue]", request: FixtureRequest, kwargs + fixturefunc: _FixtureFunc[FixtureValue], request: FixtureRequest, kwargs ) -> FixtureValue: if is_generator(fixturefunc): fixturefunc = cast( @@ -950,14 +950,12 @@ class FixtureDef(Generic[FixtureValue]): def __init__( self, config: Config, - baseid: Optional[str], + baseid: str | None, argname: str, - func: "_FixtureFunc[FixtureValue]", - scope: Union[Scope, _ScopeName, Callable[[str, Config], _ScopeName], None], - params: Optional[Sequence[object]], - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None, + func: _FixtureFunc[FixtureValue], + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] | None, + params: Sequence[object] | None, + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, *, _ispytest: bool = False, ) -> None: @@ -1003,8 +1001,8 @@ def __init__( self.argnames: Final = getfuncargnames(func, name=argname) # If the fixture was executed, the current value of the fixture. # Can change if the fixture is executed with different parameters. - self.cached_result: Optional[_FixtureCachedResult[FixtureValue]] = None - self._finalizers: Final[List[Callable[[], object]]] = [] + self.cached_result: _FixtureCachedResult[FixtureValue] | None = None + self._finalizers: Final[list[Callable[[], object]]] = [] @property def scope(self) -> _ScopeName: @@ -1015,7 +1013,7 @@ def addfinalizer(self, finalizer: Callable[[], object]) -> None: self._finalizers.append(finalizer) def finish(self, request: SubRequest) -> None: - exceptions: List[BaseException] = [] + exceptions: list[BaseException] = [] while self._finalizers: fin = self._finalizers.pop() try: @@ -1099,7 +1097,7 @@ def __repr__(self) -> str: def resolve_fixture_function( fixturedef: FixtureDef[FixtureValue], request: FixtureRequest -) -> "_FixtureFunc[FixtureValue]": +) -> _FixtureFunc[FixtureValue]: """Get the actual callable that can be called to obtain the fixture value.""" fixturefunc = fixturedef.func @@ -1147,7 +1145,7 @@ def pytest_fixture_setup( def wrap_function_to_error_out_if_called_directly( function: FixtureFunction, - fixture_marker: "FixtureFunctionMarker", + fixture_marker: FixtureFunctionMarker, ) -> FixtureFunction: """Wrap the given fixture function so we can raise an error about it being called directly, instead of used as an argument in a test function.""" @@ -1173,13 +1171,11 @@ def result(*args, **kwargs): @final @dataclasses.dataclass(frozen=True) class FixtureFunctionMarker: - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" - params: Optional[Tuple[object, ...]] + scope: _ScopeName | Callable[[str, Config], _ScopeName] + params: tuple[object, ...] | None autouse: bool = False - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None - name: Optional[str] = None + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None + name: str | None = None _ispytest: dataclasses.InitVar[bool] = False @@ -1217,13 +1213,11 @@ def __call__(self, function: FixtureFunction) -> FixtureFunction: def fixture( fixture_function: FixtureFunction, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = ..., + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = ..., ) -> FixtureFunction: ... @@ -1231,27 +1225,23 @@ def fixture( def fixture( fixture_function: None = ..., *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = ..., - params: Optional[Iterable[object]] = ..., + scope: _ScopeName | Callable[[str, Config], _ScopeName] = ..., + params: Iterable[object] | None = ..., autouse: bool = ..., - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = ..., - name: Optional[str] = None, + ids: Sequence[object | None] | Callable[[Any], object | None] | None = ..., + name: str | None = None, ) -> FixtureFunctionMarker: ... def fixture( - fixture_function: Optional[FixtureFunction] = None, + fixture_function: FixtureFunction | None = None, *, - scope: "Union[_ScopeName, Callable[[str, Config], _ScopeName]]" = "function", - params: Optional[Iterable[object]] = None, + scope: _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Iterable[object] | None = None, autouse: bool = False, - ids: Optional[ - Union[Sequence[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - name: Optional[str] = None, -) -> Union[FixtureFunctionMarker, FixtureFunction]: + ids: Sequence[object | None] | Callable[[Any], object | None] | None = None, + name: str | None = None, +) -> FixtureFunctionMarker | FixtureFunction: """Decorator to mark a fixture factory function. This decorator can be used, with or without parameters, to define a @@ -1385,7 +1375,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.showfixtures: showfixtures(config) return 0 @@ -1395,7 +1385,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: return None -def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: +def _get_direct_parametrize_args(node: nodes.Node) -> set[str]: """Return all direct parametrization arguments of a node, so we don't mistake them for fixtures. @@ -1404,7 +1394,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: These things are done later as well when dealing with parametrization so this could be improved. """ - parametrize_argnames: Set[str] = set() + parametrize_argnames: set[str] = set() for marker in node.iter_markers(name="parametrize"): if not marker.kwargs.get("indirect", False): p_argnames, _ = ParameterSet._parse_parametrize_args( @@ -1414,7 +1404,7 @@ def _get_direct_parametrize_args(node: nodes.Node) -> Set[str]: return parametrize_argnames -def deduplicate_names(*seqs: Iterable[str]) -> Tuple[str, ...]: +def deduplicate_names(*seqs: Iterable[str]) -> tuple[str, ...]: """De-duplicate the sequence of names while keeping the original order.""" # Ideally we would use a set, but it does not preserve insertion order. return tuple(dict.fromkeys(name for seq in seqs for name in seq)) @@ -1451,17 +1441,17 @@ class FixtureManager: by a lookup of their FuncFixtureInfo. """ - def __init__(self, session: "Session") -> None: + def __init__(self, session: Session) -> None: self.session = session self.config: Config = session.config # Maps a fixture name (argname) to all of the FixtureDefs in the test # suite/plugins defined with this name. Populated by parsefactories(). # TODO: The order of the FixtureDefs list of each arg is significant, # explain. - self._arg2fixturedefs: Final[Dict[str, List[FixtureDef[Any]]]] = {} - self._holderobjseen: Final[Set[object]] = set() + self._arg2fixturedefs: Final[dict[str, list[FixtureDef[Any]]]] = {} + self._holderobjseen: Final[set[object]] = set() # A mapping from a nodeid to a list of autouse fixtures it defines. - self._nodeid_autousenames: Final[Dict[str, List[str]]] = { + self._nodeid_autousenames: Final[dict[str, list[str]]] = { "": self.config.getini("usefixtures"), } session.config.pluginmanager.register(self, "funcmanage") @@ -1469,8 +1459,8 @@ def __init__(self, session: "Session") -> None: def getfixtureinfo( self, node: nodes.Item, - func: Optional[Callable[..., object]], - cls: Optional[type], + func: Callable[..., object] | None, + cls: type | None, ) -> FuncFixtureInfo: """Calculate the :class:`FuncFixtureInfo` for an item. @@ -1541,9 +1531,9 @@ def _getusefixturesnames(self, node: nodes.Item) -> Iterator[str]: def getfixtureclosure( self, parentnode: nodes.Node, - initialnames: Tuple[str, ...], + initialnames: tuple[str, ...], ignore_args: AbstractSet[str], - ) -> Tuple[List[str], Dict[str, Sequence[FixtureDef[Any]]]]: + ) -> tuple[list[str], dict[str, Sequence[FixtureDef[Any]]]]: # Collect the closure of all fixtures, starting with the given # fixturenames as the initial set. As we have to visit all # factory definitions anyway, we also return an arg2fixturedefs @@ -1553,7 +1543,7 @@ def getfixtureclosure( fixturenames_closure = list(initialnames) - arg2fixturedefs: Dict[str, Sequence[FixtureDef[Any]]] = {} + arg2fixturedefs: dict[str, Sequence[FixtureDef[Any]]] = {} lastlen = -1 while lastlen != len(fixturenames_closure): lastlen = len(fixturenames_closure) @@ -1580,7 +1570,7 @@ def sort_by_scope(arg_name: str) -> Scope: fixturenames_closure.sort(key=sort_by_scope, reverse=True) return fixturenames_closure, arg2fixturedefs - def pytest_generate_tests(self, metafunc: "Metafunc") -> None: + def pytest_generate_tests(self, metafunc: Metafunc) -> None: """Generate new tests based on parametrized fixtures used by the given metafunc""" def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: @@ -1625,7 +1615,7 @@ def get_parametrize_mark_argnames(mark: Mark) -> Sequence[str]: # Try next super fixture, if any. - def pytest_collection_modifyitems(self, items: List[nodes.Item]) -> None: + def pytest_collection_modifyitems(self, items: list[nodes.Item]) -> None: # Separate parametrized setups. items[:] = reorder_items(items) @@ -1633,15 +1623,11 @@ def _register_fixture( self, *, name: str, - func: "_FixtureFunc[object]", - nodeid: Optional[str], - scope: Union[ - Scope, _ScopeName, Callable[[str, Config], _ScopeName] - ] = "function", - params: Optional[Sequence[object]] = None, - ids: Optional[ - Union[Tuple[Optional[object], ...], Callable[[Any], Optional[object]]] - ] = None, + func: _FixtureFunc[object], + nodeid: str | None, + scope: Scope | _ScopeName | Callable[[str, Config], _ScopeName] = "function", + params: Sequence[object] | None = None, + ids: tuple[object | None, ...] | Callable[[Any], object | None] | None = None, autouse: bool = False, ) -> None: """Register a fixture @@ -1699,14 +1685,14 @@ def parsefactories( def parsefactories( self, node_or_obj: object, - nodeid: Optional[str], + nodeid: str | None, ) -> None: raise NotImplementedError() def parsefactories( self, - node_or_obj: Union[nodes.Node, object], - nodeid: Union[str, NotSetType, None] = NOTSET, + node_or_obj: nodes.Node | object, + nodeid: str | NotSetType | None = NOTSET, ) -> None: """Collect fixtures from a collection node or object. @@ -1764,7 +1750,7 @@ def parsefactories( def getfixturedefs( self, argname: str, node: nodes.Node - ) -> Optional[Sequence[FixtureDef[Any]]]: + ) -> Sequence[FixtureDef[Any]] | None: """Get FixtureDefs for a fixture name which are applicable to a given node. @@ -1791,7 +1777,7 @@ def _matchfactories( yield fixturedef -def show_fixtures_per_test(config: Config) -> Union[int, ExitCode]: +def show_fixtures_per_test(config: Config) -> int | ExitCode: from _pytest.main import wrap_session return wrap_session(config, _show_fixtures_per_test) @@ -1809,7 +1795,7 @@ def _pretty_fixture_path(invocation_dir: Path, func) -> str: return bestrelpath(invocation_dir, loc) -def _show_fixtures_per_test(config: Config, session: "Session") -> None: +def _show_fixtures_per_test(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1842,7 +1828,7 @@ def write_fixture(fixture_def: FixtureDef[object]) -> None: def write_item(item: nodes.Item) -> None: # Not all items have _fixtureinfo attribute. - info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None) + info: FuncFixtureInfo | None = getattr(item, "_fixtureinfo", None) if info is None or not info.name2fixturedefs: # This test item does not use any fixtures. return @@ -1862,13 +1848,13 @@ def write_item(item: nodes.Item) -> None: write_item(session_item) -def showfixtures(config: Config) -> Union[int, ExitCode]: +def showfixtures(config: Config) -> int | ExitCode: from _pytest.main import wrap_session return wrap_session(config, _showfixtures_main) -def _showfixtures_main(config: Config, session: "Session") -> None: +def _showfixtures_main(config: Config, session: Session) -> None: import _pytest.config session.perform_collect() @@ -1879,7 +1865,7 @@ def _showfixtures_main(config: Config, session: "Session") -> None: fm = session._fixturemanager available = [] - seen: Set[Tuple[str, str]] = set() + seen: set[tuple[str, str]] = set() for argname, fixturedefs in fm._arg2fixturedefs.items(): assert fixturedefs is not None diff --git a/src/_pytest/freeze_support.py b/src/_pytest/freeze_support.py index e03a6d1753d..2ba6f9b8bcc 100644 --- a/src/_pytest/freeze_support.py +++ b/src/_pytest/freeze_support.py @@ -1,13 +1,13 @@ """Provides a function to report all internal modules for using freezing tools.""" +from __future__ import annotations + import types from typing import Iterator -from typing import List -from typing import Union -def freeze_includes() -> List[str]: +def freeze_includes() -> list[str]: """Return a list of module names used by pytest that should be included by cx_freeze.""" import _pytest @@ -17,7 +17,7 @@ def freeze_includes() -> List[str]: def _iter_all_modules( - package: Union[str, types.ModuleType], + package: str | types.ModuleType, prefix: str = "", ) -> Iterator[str]: """Iterate over the names of all modules that can be found in the given diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 68e0bd881a7..e7a1afab526 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -1,13 +1,12 @@ # mypy: allow-untyped-defs """Version info, help messages, tracing configuration.""" +from __future__ import annotations + from argparse import Action import os import sys from typing import Generator -from typing import List -from typing import Optional -from typing import Union from _pytest.config import Config from _pytest.config import ExitCode @@ -147,7 +146,7 @@ def showversion(config: Config) -> None: sys.stdout.write(f"pytest {pytest.__version__}\n") -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.version > 0: showversion(config) return 0 @@ -162,7 +161,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: def showhelp(config: Config) -> None: import textwrap - reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin( + reporter: TerminalReporter | None = config.pluginmanager.get_plugin( "terminalreporter" ) assert reporter is not None @@ -239,7 +238,7 @@ def showhelp(config: Config) -> None: conftest_options = [("pytest_plugins", "list of plugin names to load")] -def getpluginversioninfo(config: Config) -> List[str]: +def getpluginversioninfo(config: Config) -> list[str]: lines = [] plugininfo = config.pluginmanager.list_plugin_distinfo() if plugininfo: @@ -251,7 +250,7 @@ def getpluginversioninfo(config: Config) -> List[str]: return lines -def pytest_report_header(config: Config) -> List[str]: +def pytest_report_header(config: Config) -> list[str]: lines = [] if config.option.debug or config.option.traceconfig: lines.append(f"using: pytest-{pytest.__version__}") diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index c7f9d036c33..13f4fddbddb 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -3,16 +3,13 @@ """Hook specifications for pytest plugins which are invoked by pytest itself and by builtin plugins.""" +from __future__ import annotations + from pathlib import Path from typing import Any -from typing import Dict -from typing import List from typing import Mapping -from typing import Optional from typing import Sequence -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from pluggy import HookspecMarker @@ -57,7 +54,7 @@ @hookspec(historic=True) -def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: +def pytest_addhooks(pluginmanager: PytestPluginManager) -> None: """Called at plugin registration time to allow adding new hooks via a call to :func:`pluginmanager.add_hookspecs(module_or_class, prefix) `. @@ -76,9 +73,9 @@ def pytest_addhooks(pluginmanager: "PytestPluginManager") -> None: @hookspec(historic=True) def pytest_plugin_registered( - plugin: "_PluggyPlugin", + plugin: _PluggyPlugin, plugin_name: str, - manager: "PytestPluginManager", + manager: PytestPluginManager, ) -> None: """A new pytest plugin got registered. @@ -100,7 +97,7 @@ def pytest_plugin_registered( @hookspec(historic=True) -def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> None: +def pytest_addoption(parser: Parser, pluginmanager: PytestPluginManager) -> None: """Register argparse-style options and ini-style config values, called once at the beginning of a test run. @@ -141,7 +138,7 @@ def pytest_addoption(parser: "Parser", pluginmanager: "PytestPluginManager") -> @hookspec(historic=True) -def pytest_configure(config: "Config") -> None: +def pytest_configure(config: Config) -> None: """Allow plugins and conftest files to perform initial configuration. .. note:: @@ -166,8 +163,8 @@ def pytest_configure(config: "Config") -> None: @hookspec(firstresult=True) def pytest_cmdline_parse( - pluginmanager: "PytestPluginManager", args: List[str] -) -> Optional["Config"]: + pluginmanager: PytestPluginManager, args: list[str] +) -> Config | None: """Return an initialized :class:`~pytest.Config`, parsing the specified args. Stops at first non-None result, see :ref:`firstresult`. @@ -189,7 +186,7 @@ def pytest_cmdline_parse( def pytest_load_initial_conftests( - early_config: "Config", parser: "Parser", args: List[str] + early_config: Config, parser: Parser, args: list[str] ) -> None: """Called to implement the loading of :ref:`initial conftest files ` ahead of command line option parsing. @@ -206,7 +203,7 @@ def pytest_load_initial_conftests( @hookspec(firstresult=True) -def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: +def pytest_cmdline_main(config: Config) -> ExitCode | int | None: """Called for performing the main command line action. The default implementation will invoke the configure hooks and @@ -230,7 +227,7 @@ def pytest_cmdline_main(config: "Config") -> Optional[Union["ExitCode", int]]: @hookspec(firstresult=True) -def pytest_collection(session: "Session") -> Optional[object]: +def pytest_collection(session: Session) -> object | None: """Perform the collection phase for the given session. Stops at first non-None result, see :ref:`firstresult`. @@ -272,7 +269,7 @@ def pytest_collection(session: "Session") -> Optional[object]: def pytest_collection_modifyitems( - session: "Session", config: "Config", items: List["Item"] + session: Session, config: Config, items: list[Item] ) -> None: """Called after collection has been performed. May filter or re-order the items in-place. @@ -288,7 +285,7 @@ def pytest_collection_modifyitems( """ -def pytest_collection_finish(session: "Session") -> None: +def pytest_collection_finish(session: Session) -> None: """Called after collection has been performed and modified. :param session: The pytest session object. @@ -309,8 +306,8 @@ def pytest_collection_finish(session: "Session") -> None: }, ) def pytest_ignore_collect( - collection_path: Path, path: "LEGACY_PATH", config: "Config" -) -> Optional[bool]: + collection_path: Path, path: LEGACY_PATH, config: Config +) -> bool | None: """Return ``True`` to ignore this path for collection. Return ``None`` to let other plugins ignore the path for collection. @@ -343,7 +340,7 @@ def pytest_ignore_collect( @hookspec(firstresult=True) -def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Collector]": +def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: """Create a :class:`~pytest.Collector` for the given directory, or None if not relevant. @@ -379,8 +376,8 @@ def pytest_collect_directory(path: Path, parent: "Collector") -> "Optional[Colle }, ) def pytest_collect_file( - file_path: Path, path: "LEGACY_PATH", parent: "Collector" -) -> "Optional[Collector]": + file_path: Path, path: LEGACY_PATH, parent: Collector +) -> Collector | None: """Create a :class:`~pytest.Collector` for the given path, or None if not relevant. For best results, the returned collector should be a subclass of @@ -407,7 +404,7 @@ def pytest_collect_file( # logging hooks for collection -def pytest_collectstart(collector: "Collector") -> None: +def pytest_collectstart(collector: Collector) -> None: """Collector starts collecting. :param collector: @@ -422,7 +419,7 @@ def pytest_collectstart(collector: "Collector") -> None: """ -def pytest_itemcollected(item: "Item") -> None: +def pytest_itemcollected(item: Item) -> None: """We just collected a test item. :param item: @@ -436,7 +433,7 @@ def pytest_itemcollected(item: "Item") -> None: """ -def pytest_collectreport(report: "CollectReport") -> None: +def pytest_collectreport(report: CollectReport) -> None: """Collector finished collecting. :param report: @@ -451,7 +448,7 @@ def pytest_collectreport(report: "CollectReport") -> None: """ -def pytest_deselected(items: Sequence["Item"]) -> None: +def pytest_deselected(items: Sequence[Item]) -> None: """Called for deselected test items, e.g. by keyword. May be called multiple times. @@ -467,7 +464,7 @@ def pytest_deselected(items: Sequence["Item"]) -> None: @hookspec(firstresult=True) -def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectReport]": +def pytest_make_collect_report(collector: Collector) -> CollectReport | None: """Perform :func:`collector.collect() ` and return a :class:`~pytest.CollectReport`. @@ -499,8 +496,8 @@ def pytest_make_collect_report(collector: "Collector") -> "Optional[CollectRepor }, ) def pytest_pycollect_makemodule( - module_path: Path, path: "LEGACY_PATH", parent -) -> Optional["Module"]: + module_path: Path, path: LEGACY_PATH, parent +) -> Module | None: """Return a :class:`pytest.Module` collector or None for the given path. This hook will be called for each matching test module path. @@ -529,8 +526,8 @@ def pytest_pycollect_makemodule( @hookspec(firstresult=True) def pytest_pycollect_makeitem( - collector: Union["Module", "Class"], name: str, obj: object -) -> Union[None, "Item", "Collector", List[Union["Item", "Collector"]]]: + collector: Module | Class, name: str, obj: object +) -> None | Item | Collector | list[Item | Collector]: """Return a custom item/collector for a Python object in a module, or None. Stops at first non-None result, see :ref:`firstresult`. @@ -554,7 +551,7 @@ def pytest_pycollect_makeitem( @hookspec(firstresult=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: """Call underlying test function. Stops at first non-None result, see :ref:`firstresult`. @@ -571,7 +568,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: """ -def pytest_generate_tests(metafunc: "Metafunc") -> None: +def pytest_generate_tests(metafunc: Metafunc) -> None: """Generate (multiple) parametrized calls to a test function. :param metafunc: @@ -587,9 +584,7 @@ def pytest_generate_tests(metafunc: "Metafunc") -> None: @hookspec(firstresult=True) -def pytest_make_parametrize_id( - config: "Config", val: object, argname: str -) -> Optional[str]: +def pytest_make_parametrize_id(config: Config, val: object, argname: str) -> str | None: """Return a user-friendly string representation of the given ``val`` that will be used by @pytest.mark.parametrize calls, or None if the hook doesn't know about ``val``. @@ -615,7 +610,7 @@ def pytest_make_parametrize_id( @hookspec(firstresult=True) -def pytest_runtestloop(session: "Session") -> Optional[object]: +def pytest_runtestloop(session: Session) -> object | None: """Perform the main runtest loop (after collection finished). The default hook implementation performs the runtest protocol for all items @@ -641,9 +636,7 @@ def pytest_runtestloop(session: "Session") -> Optional[object]: @hookspec(firstresult=True) -def pytest_runtest_protocol( - item: "Item", nextitem: "Optional[Item]" -) -> Optional[object]: +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> object | None: """Perform the runtest protocol for a single test item. The default runtest protocol is this (see individual hooks for full details): @@ -683,9 +676,7 @@ def pytest_runtest_protocol( """ -def pytest_runtest_logstart( - nodeid: str, location: Tuple[str, Optional[int], str] -) -> None: +def pytest_runtest_logstart(nodeid: str, location: tuple[str, int | None, str]) -> None: """Called at the start of running the runtest protocol for a single item. See :hook:`pytest_runtest_protocol` for a description of the runtest protocol. @@ -704,7 +695,7 @@ def pytest_runtest_logstart( def pytest_runtest_logfinish( - nodeid: str, location: Tuple[str, Optional[int], str] + nodeid: str, location: tuple[str, int | None, str] ) -> None: """Called at the end of running the runtest protocol for a single item. @@ -723,7 +714,7 @@ def pytest_runtest_logfinish( """ -def pytest_runtest_setup(item: "Item") -> None: +def pytest_runtest_setup(item: Item) -> None: """Called to perform the setup phase for a test item. The default implementation runs ``setup()`` on ``item`` and all of its @@ -742,7 +733,7 @@ def pytest_runtest_setup(item: "Item") -> None: """ -def pytest_runtest_call(item: "Item") -> None: +def pytest_runtest_call(item: Item) -> None: """Called to run the test for test item (the call phase). The default implementation calls ``item.runtest()``. @@ -758,7 +749,7 @@ def pytest_runtest_call(item: "Item") -> None: """ -def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: """Called to perform the teardown phase for a test item. The default implementation runs the finalizers and calls ``teardown()`` @@ -783,9 +774,7 @@ def pytest_runtest_teardown(item: "Item", nextitem: Optional["Item"]) -> None: @hookspec(firstresult=True) -def pytest_runtest_makereport( - item: "Item", call: "CallInfo[None]" -) -> Optional["TestReport"]: +def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport | None: """Called to create a :class:`~pytest.TestReport` for each of the setup, call and teardown runtest phases of a test item. @@ -804,7 +793,7 @@ def pytest_runtest_makereport( """ -def pytest_runtest_logreport(report: "TestReport") -> None: +def pytest_runtest_logreport(report: TestReport) -> None: """Process the :class:`~pytest.TestReport` produced for each of the setup, call and teardown runtest phases of an item. @@ -820,9 +809,9 @@ def pytest_runtest_logreport(report: "TestReport") -> None: @hookspec(firstresult=True) def pytest_report_to_serializable( - config: "Config", - report: Union["CollectReport", "TestReport"], -) -> Optional[Dict[str, Any]]: + config: Config, + report: CollectReport | TestReport, +) -> dict[str, Any] | None: """Serialize the given report object into a data structure suitable for sending over the wire, e.g. converted to JSON. @@ -839,9 +828,9 @@ def pytest_report_to_serializable( @hookspec(firstresult=True) def pytest_report_from_serializable( - config: "Config", - data: Dict[str, Any], -) -> Optional[Union["CollectReport", "TestReport"]]: + config: Config, + data: dict[str, Any], +) -> CollectReport | TestReport | None: """Restore a report object previously serialized with :hook:`pytest_report_to_serializable`. @@ -862,8 +851,8 @@ def pytest_report_from_serializable( @hookspec(firstresult=True) def pytest_fixture_setup( - fixturedef: "FixtureDef[Any]", request: "SubRequest" -) -> Optional[object]: + fixturedef: FixtureDef[Any], request: SubRequest +) -> object | None: """Perform fixture setup execution. :param fixturedef: @@ -890,7 +879,7 @@ def pytest_fixture_setup( def pytest_fixture_post_finalizer( - fixturedef: "FixtureDef[Any]", request: "SubRequest" + fixturedef: FixtureDef[Any], request: SubRequest ) -> None: """Called after fixture teardown, but before the cache is cleared, so the fixture result ``fixturedef.cached_result`` is still available (not @@ -915,7 +904,7 @@ def pytest_fixture_post_finalizer( # ------------------------------------------------------------------------- -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: """Called after the ``Session`` object has been created and before performing collection and entering the run test loop. @@ -929,8 +918,8 @@ def pytest_sessionstart(session: "Session") -> None: def pytest_sessionfinish( - session: "Session", - exitstatus: Union[int, "ExitCode"], + session: Session, + exitstatus: int | ExitCode, ) -> None: """Called after whole test run finished, right before returning the exit status to the system. @@ -944,7 +933,7 @@ def pytest_sessionfinish( """ -def pytest_unconfigure(config: "Config") -> None: +def pytest_unconfigure(config: Config) -> None: """Called before test process is exited. :param config: The pytest config object. @@ -962,8 +951,8 @@ def pytest_unconfigure(config: "Config") -> None: def pytest_assertrepr_compare( - config: "Config", op: str, left: object, right: object -) -> Optional[List[str]]: + config: Config, op: str, left: object, right: object +) -> list[str] | None: """Return explanation for comparisons in failing assert expressions. Return None for no custom explanation, otherwise return a list @@ -984,7 +973,7 @@ def pytest_assertrepr_compare( """ -def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> None: +def pytest_assertion_pass(item: Item, lineno: int, orig: str, expl: str) -> None: """Called whenever an assertion passes. .. versionadded:: 5.0 @@ -1031,8 +1020,8 @@ def pytest_assertion_pass(item: "Item", lineno: int, orig: str, expl: str) -> No }, ) def pytest_report_header( # type:ignore[empty-body] - config: "Config", start_path: Path, startdir: "LEGACY_PATH" -) -> Union[str, List[str]]: + config: Config, start_path: Path, startdir: LEGACY_PATH +) -> str | list[str]: """Return a string or list of strings to be displayed as header info for terminal reporting. :param config: The pytest config object. @@ -1066,11 +1055,11 @@ def pytest_report_header( # type:ignore[empty-body] }, ) def pytest_report_collectionfinish( # type:ignore[empty-body] - config: "Config", + config: Config, start_path: Path, - startdir: "LEGACY_PATH", - items: Sequence["Item"], -) -> Union[str, List[str]]: + startdir: LEGACY_PATH, + items: Sequence[Item], +) -> str | list[str]: """Return a string or list of strings to be displayed after collection has finished successfully. @@ -1104,8 +1093,8 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] @hookspec(firstresult=True) def pytest_report_teststatus( # type:ignore[empty-body] - report: Union["CollectReport", "TestReport"], config: "Config" -) -> "TestShortLogReport | Tuple[str, str, Union[str, Tuple[str, Mapping[str, bool]]]]": + report: CollectReport | TestReport, config: Config +) -> TestShortLogReport | tuple[str, str, str | tuple[str, Mapping[str, bool]]]: """Return result-category, shortletter and verbose word for status reporting. @@ -1136,9 +1125,9 @@ def pytest_report_teststatus( # type:ignore[empty-body] def pytest_terminal_summary( - terminalreporter: "TerminalReporter", - exitstatus: "ExitCode", - config: "Config", + terminalreporter: TerminalReporter, + exitstatus: ExitCode, + config: Config, ) -> None: """Add a section to terminal summary reporting. @@ -1158,10 +1147,10 @@ def pytest_terminal_summary( @hookspec(historic=True) def pytest_warning_recorded( - warning_message: "warnings.WarningMessage", - when: "Literal['config', 'collect', 'runtest']", + warning_message: warnings.WarningMessage, + when: Literal["config", "collect", "runtest"], nodeid: str, - location: Optional[Tuple[str, int, str]], + location: tuple[str, int, str] | None, ) -> None: """Process a warning captured by the internal pytest warnings plugin. @@ -1202,8 +1191,8 @@ def pytest_warning_recorded( def pytest_markeval_namespace( # type:ignore[empty-body] - config: "Config", -) -> Dict[str, Any]: + config: Config, +) -> dict[str, Any]: """Called when constructing the globals dictionary used for evaluating string conditions in xfail/skipif markers. @@ -1231,9 +1220,9 @@ def pytest_markeval_namespace( # type:ignore[empty-body] def pytest_internalerror( - excrepr: "ExceptionRepr", - excinfo: "ExceptionInfo[BaseException]", -) -> Optional[bool]: + excrepr: ExceptionRepr, + excinfo: ExceptionInfo[BaseException], +) -> bool | None: """Called for internal errors. Return True to suppress the fallback handling of printing an @@ -1250,7 +1239,7 @@ def pytest_internalerror( def pytest_keyboard_interrupt( - excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]", + excinfo: ExceptionInfo[KeyboardInterrupt | Exit], ) -> None: """Called for keyboard interrupt. @@ -1264,9 +1253,9 @@ def pytest_keyboard_interrupt( def pytest_exception_interact( - node: Union["Item", "Collector"], - call: "CallInfo[Any]", - report: Union["CollectReport", "TestReport"], + node: Item | Collector, + call: CallInfo[Any], + report: CollectReport | TestReport, ) -> None: """Called when an exception was raised which can potentially be interactively handled. @@ -1295,7 +1284,7 @@ def pytest_exception_interact( """ -def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: +def pytest_enter_pdb(config: Config, pdb: pdb.Pdb) -> None: """Called upon pdb.set_trace(). Can be used by plugins to take special action just before the python @@ -1311,7 +1300,7 @@ def pytest_enter_pdb(config: "Config", pdb: "pdb.Pdb") -> None: """ -def pytest_leave_pdb(config: "Config", pdb: "pdb.Pdb") -> None: +def pytest_leave_pdb(config: Config, pdb: pdb.Pdb) -> None: """Called when leaving pdb (e.g. with continue after pdb.set_trace()). Can be used by plugins to take special action just after the python diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 011af6100e9..a525f1d14de 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -8,18 +8,15 @@ https://github.com/jenkinsci/xunit-plugin/blob/master/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd """ +from __future__ import annotations + from datetime import datetime import functools import os import platform import re from typing import Callable -from typing import Dict -from typing import List from typing import Match -from typing import Optional -from typing import Tuple -from typing import Union import xml.etree.ElementTree as ET from _pytest import nodes @@ -89,15 +86,15 @@ def merge_family(left, right) -> None: class _NodeReporter: - def __init__(self, nodeid: Union[str, TestReport], xml: "LogXML") -> None: + def __init__(self, nodeid: str | TestReport, xml: LogXML) -> None: self.id = nodeid self.xml = xml self.add_stats = self.xml.add_stats self.family = self.xml.family self.duration = 0.0 - self.properties: List[Tuple[str, str]] = [] - self.nodes: List[ET.Element] = [] - self.attrs: Dict[str, str] = {} + self.properties: list[tuple[str, str]] = [] + self.nodes: list[ET.Element] = [] + self.attrs: dict[str, str] = {} def append(self, node: ET.Element) -> None: self.xml.add_stats(node.tag) @@ -109,7 +106,7 @@ def add_property(self, name: str, value: object) -> None: def add_attribute(self, name: str, value: object) -> None: self.attrs[str(name)] = bin_xml_escape(value) - def make_properties_node(self) -> Optional[ET.Element]: + def make_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.properties: properties = ET.Element("properties") @@ -124,7 +121,7 @@ def record_testreport(self, testreport: TestReport) -> None: classnames = names[:-1] if self.xml.prefix: classnames.insert(0, self.xml.prefix) - attrs: Dict[str, str] = { + attrs: dict[str, str] = { "classname": ".".join(classnames), "name": bin_xml_escape(names[-1]), "file": testreport.location[0], @@ -156,7 +153,7 @@ def to_xml(self) -> ET.Element: testcase.extend(self.nodes) return testcase - def _add_simple(self, tag: str, message: str, data: Optional[str] = None) -> None: + def _add_simple(self, tag: str, message: str, data: str | None = None) -> None: node = ET.Element(tag, message=message) node.text = bin_xml_escape(data) self.append(node) @@ -201,7 +198,7 @@ def append_failure(self, report: TestReport) -> None: self._add_simple("skipped", "xfail-marked test passes unexpectedly") else: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( + reprcrash: ReprFileLocation | None = getattr( report.longrepr, "reprcrash", None ) if reprcrash is not None: @@ -221,9 +218,7 @@ def append_collect_skipped(self, report: TestReport) -> None: def append_error(self, report: TestReport) -> None: assert report.longrepr is not None - reprcrash: Optional[ReprFileLocation] = getattr( - report.longrepr, "reprcrash", None - ) + reprcrash: ReprFileLocation | None = getattr(report.longrepr, "reprcrash", None) if reprcrash is not None: reason = reprcrash.message else: @@ -451,7 +446,7 @@ def pytest_unconfigure(config: Config) -> None: config.pluginmanager.unregister(xml) -def mangle_test_address(address: str) -> List[str]: +def mangle_test_address(address: str) -> list[str]: path, possible_open_bracket, params = address.partition("[") names = path.split("::") # Convert file path to dotted path. @@ -466,7 +461,7 @@ class LogXML: def __init__( self, logfile, - prefix: Optional[str], + prefix: str | None, suite_name: str = "pytest", logging: str = "no", report_duration: str = "total", @@ -481,17 +476,15 @@ def __init__( self.log_passing_tests = log_passing_tests self.report_duration = report_duration self.family = family - self.stats: Dict[str, int] = dict.fromkeys( + self.stats: dict[str, int] = dict.fromkeys( ["error", "passed", "failure", "skipped"], 0 ) - self.node_reporters: Dict[ - Tuple[Union[str, TestReport], object], _NodeReporter - ] = {} - self.node_reporters_ordered: List[_NodeReporter] = [] - self.global_properties: List[Tuple[str, str]] = [] + self.node_reporters: dict[tuple[str | TestReport, object], _NodeReporter] = {} + self.node_reporters_ordered: list[_NodeReporter] = [] + self.global_properties: list[tuple[str, str]] = [] # List of reports that failed on call but teardown is pending. - self.open_reports: List[TestReport] = [] + self.open_reports: list[TestReport] = [] self.cnt_double_fail_tests = 0 # Replaces convenience family with real family. @@ -510,8 +503,8 @@ def finalize(self, report: TestReport) -> None: if reporter is not None: reporter.finalize() - def node_reporter(self, report: Union[TestReport, str]) -> _NodeReporter: - nodeid: Union[str, TestReport] = getattr(report, "nodeid", report) + def node_reporter(self, report: TestReport | str) -> _NodeReporter: + nodeid: str | TestReport = getattr(report, "nodeid", report) # Local hack to handle xdist report order. workernode = getattr(report, "node", None) @@ -691,7 +684,7 @@ def add_global_property(self, name: str, value: object) -> None: _check_record_param_type("name", name) self.global_properties.append((name, bin_xml_escape(value))) - def _get_global_properties_node(self) -> Optional[ET.Element]: + def _get_global_properties_node(self) -> ET.Element | None: """Return a Junit node containing custom properties, if any.""" if self.global_properties: properties = ET.Element("properties") diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index d9de65b1a53..61476d68932 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,16 +1,15 @@ # mypy: allow-untyped-defs """Add backward compatibility support for the legacy py path type.""" +from __future__ import annotations + import dataclasses from pathlib import Path import shlex import subprocess from typing import Final from typing import final -from typing import List -from typing import Optional from typing import TYPE_CHECKING -from typing import Union from iniconfig import SectionWrapper @@ -50,8 +49,8 @@ class Testdir: __test__ = False - CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN - TimeoutExpired: "Final" = Pytester.TimeoutExpired + CLOSE_STDIN: Final = Pytester.CLOSE_STDIN + TimeoutExpired: Final = Pytester.TimeoutExpired def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) @@ -145,7 +144,7 @@ def copy_example(self, name=None) -> LEGACY_PATH: """See :meth:`Pytester.copy_example`.""" return legacy_path(self._pytester.copy_example(name)) - def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]: + def getnode(self, config: Config, arg) -> Item | Collector | None: """See :meth:`Pytester.getnode`.""" return self._pytester.getnode(config, arg) @@ -153,7 +152,7 @@ def getpathnode(self, path): """See :meth:`Pytester.getpathnode`.""" return self._pytester.getpathnode(path) - def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: list[Item | Collector]) -> list[Item]: """See :meth:`Pytester.genitems`.""" return self._pytester.genitems(colitems) @@ -205,9 +204,7 @@ def getmodulecol(self, source, configargs=(), withinit=False): source, configargs=configargs, withinit=withinit ) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: """See :meth:`Pytester.collect_by_name`.""" return self._pytester.collect_by_name(modcol, name) @@ -238,13 +235,11 @@ def runpytest_subprocess(self, *args, timeout=None) -> RunResult: """See :meth:`Pytester.runpytest_subprocess`.""" return self._pytester.runpytest_subprocess(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """See :meth:`Pytester.spawn_pytest`.""" return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """See :meth:`Pytester.spawn`.""" return self._pytester.spawn(cmd, expect_timeout=expect_timeout) @@ -374,7 +369,7 @@ def Config_rootdir(self: Config) -> LEGACY_PATH: return legacy_path(str(self.rootpath)) -def Config_inifile(self: Config) -> Optional[LEGACY_PATH]: +def Config_inifile(self: Config) -> LEGACY_PATH | None: """The path to the :ref:`configfile `. Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. @@ -394,9 +389,7 @@ def Session_startdir(self: Session) -> LEGACY_PATH: return legacy_path(self.startpath) -def Config__getini_unknown_type( - self, name: str, type: str, value: Union[str, List[str]] -): +def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): if type == "pathlist": # TODO: This assert is probably not valid in all cases. assert self.inipath is not None diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index c9139d369ef..fe3be060fdd 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Access and control log capturing.""" +from __future__ import annotations + from contextlib import contextmanager from contextlib import nullcontext from datetime import datetime @@ -22,12 +24,8 @@ from typing import List from typing import Literal from typing import Mapping -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from _pytest import nodes from _pytest._io import TerminalWriter @@ -68,7 +66,7 @@ class DatetimeFormatter(logging.Formatter): :func:`time.strftime` in case of microseconds in format string. """ - def formatTime(self, record: LogRecord, datefmt: Optional[str] = None) -> str: + def formatTime(self, record: LogRecord, datefmt: str | None = None) -> str: if datefmt and "%f" in datefmt: ct = self.converter(record.created) tz = timezone(timedelta(seconds=ct.tm_gmtoff), ct.tm_zone) @@ -100,7 +98,7 @@ def __init__(self, terminalwriter: TerminalWriter, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._terminalwriter = terminalwriter self._original_fmt = self._style._fmt - self._level_to_fmt_mapping: Dict[int, str] = {} + self._level_to_fmt_mapping: dict[int, str] = {} for level, color_opts in self.LOGLEVEL_COLOROPTS.items(): self.add_color_level(level, *color_opts) @@ -148,12 +146,12 @@ class PercentStyleMultiline(logging.PercentStyle): formats the message as if each line were logged separately. """ - def __init__(self, fmt: str, auto_indent: Union[int, str, bool, None]) -> None: + def __init__(self, fmt: str, auto_indent: int | str | bool | None) -> None: super().__init__(fmt) self._auto_indent = self._get_auto_indent(auto_indent) @staticmethod - def _get_auto_indent(auto_indent_option: Union[int, str, bool, None]) -> int: + def _get_auto_indent(auto_indent_option: int | str | bool | None) -> int: """Determine the current auto indentation setting. Specify auto indent behavior (on/off/fixed) by passing in @@ -348,7 +346,7 @@ class catching_logs(Generic[_HandlerType]): __slots__ = ("handler", "level", "orig_level") - def __init__(self, handler: _HandlerType, level: Optional[int] = None) -> None: + def __init__(self, handler: _HandlerType, level: int | None = None) -> None: self.handler = handler self.level = level @@ -364,9 +362,9 @@ def __enter__(self) -> _HandlerType: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: root_logger = logging.getLogger() if self.level is not None: @@ -380,7 +378,7 @@ class LogCaptureHandler(logging_StreamHandler): def __init__(self) -> None: """Create a new log handler.""" super().__init__(StringIO()) - self.records: List[logging.LogRecord] = [] + self.records: list[logging.LogRecord] = [] def emit(self, record: logging.LogRecord) -> None: """Keep the log records in a list in addition to the log text.""" @@ -411,10 +409,10 @@ class LogCaptureFixture: def __init__(self, item: nodes.Node, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) self._item = item - self._initial_handler_level: Optional[int] = None + self._initial_handler_level: int | None = None # Dict of log name -> log level. - self._initial_logger_levels: Dict[Optional[str], int] = {} - self._initial_disabled_logging_level: Optional[int] = None + self._initial_logger_levels: dict[str | None, int] = {} + self._initial_disabled_logging_level: int | None = None def _finalize(self) -> None: """Finalize the fixture. @@ -439,7 +437,7 @@ def handler(self) -> LogCaptureHandler: def get_records( self, when: Literal["setup", "call", "teardown"] - ) -> List[logging.LogRecord]: + ) -> list[logging.LogRecord]: """Get the logging records for one of the possible test phases. :param when: @@ -458,12 +456,12 @@ def text(self) -> str: return _remove_ansi_escape_sequences(self.handler.stream.getvalue()) @property - def records(self) -> List[logging.LogRecord]: + def records(self) -> list[logging.LogRecord]: """The list of log records.""" return self.handler.records @property - def record_tuples(self) -> List[Tuple[str, int, str]]: + def record_tuples(self) -> list[tuple[str, int, str]]: """A list of a stripped down version of log records intended for use in assertion comparison. @@ -474,7 +472,7 @@ def record_tuples(self) -> List[Tuple[str, int, str]]: return [(r.name, r.levelno, r.getMessage()) for r in self.records] @property - def messages(self) -> List[str]: + def messages(self) -> list[str]: """A list of format-interpolated log messages. Unlike 'records', which contains the format string and parameters for @@ -497,7 +495,7 @@ def clear(self) -> None: self.handler.clear() def _force_enable_logging( - self, level: Union[int, str], logger_obj: logging.Logger + self, level: int | str, logger_obj: logging.Logger ) -> int: """Enable the desired logging level if the global level was disabled via ``logging.disabled``. @@ -530,7 +528,7 @@ def _force_enable_logging( return original_disable_level - def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> None: + def set_level(self, level: int | str, logger: str | None = None) -> None: """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. @@ -557,7 +555,7 @@ def set_level(self, level: Union[int, str], logger: Optional[str] = None) -> Non @contextmanager def at_level( - self, level: Union[int, str], logger: Optional[str] = None + self, level: int | str, logger: str | None = None ) -> Generator[None, None, None]: """Context manager that sets the level for capturing of logs. After the end of the 'with' statement the level is restored to its original @@ -615,7 +613,7 @@ def caplog(request: FixtureRequest) -> Generator[LogCaptureFixture, None, None]: result._finalize() -def get_log_level_for_setting(config: Config, *setting_names: str) -> Optional[int]: +def get_log_level_for_setting(config: Config, *setting_names: str) -> int | None: for setting_name in setting_names: log_level = config.getoption(setting_name) if log_level is None: @@ -701,9 +699,9 @@ def __init__(self, config: Config) -> None: assert terminal_reporter is not None capture_manager = config.pluginmanager.get_plugin("capturemanager") # if capturemanager plugin is disabled, live logging still works. - self.log_cli_handler: Union[ - _LiveLoggingStreamHandler, _LiveLoggingNullHandler - ] = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) + self.log_cli_handler: ( + _LiveLoggingStreamHandler | _LiveLoggingNullHandler + ) = _LiveLoggingStreamHandler(terminal_reporter, capture_manager) else: self.log_cli_handler = _LiveLoggingNullHandler() log_cli_formatter = self._create_formatter( @@ -714,7 +712,7 @@ def __init__(self, config: Config) -> None: 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: + def _disable_loggers(self, loggers_to_disable: list[str]) -> None: if not loggers_to_disable: return @@ -839,7 +837,7 @@ def _runtest_for(self, item: nodes.Item, when: str) -> Generator[None, None, Non def pytest_runtest_setup(self, item: nodes.Item) -> Generator[None, None, None]: self.log_cli_handler.set_when("setup") - empty: Dict[str, List[logging.LogRecord]] = {} + empty: dict[str, list[logging.LogRecord]] = {} item.stash[caplog_records_key] = empty yield from self._runtest_for(item, "setup") @@ -902,7 +900,7 @@ class _LiveLoggingStreamHandler(logging_StreamHandler): def __init__( self, terminal_reporter: TerminalReporter, - capture_manager: Optional[CaptureManager], + capture_manager: CaptureManager | None, ) -> None: super().__init__(stream=terminal_reporter) # type: ignore[arg-type] self.capture_manager = capture_manager @@ -914,7 +912,7 @@ def reset(self) -> None: """Reset the handler; should be called before the start of each test.""" self._first_record_emitted = False - def set_when(self, when: Optional[str]) -> None: + def set_when(self, when: str | None) -> None: """Prepare for the given test phase (setup/call/teardown).""" self._when = when self._section_name_shown = False diff --git a/src/_pytest/main.py b/src/_pytest/main.py index d200a6877ff..a19ddef58fb 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -1,5 +1,7 @@ """Core implementation of the testing process: init, session, runtest loop.""" +from __future__ import annotations + import argparse import dataclasses import fnmatch @@ -13,17 +15,12 @@ from typing import Callable from typing import Dict from typing import final -from typing import FrozenSet from typing import Iterable from typing import Iterator -from typing import List from typing import Literal -from typing import Optional from typing import overload from typing import Sequence -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import warnings import pluggy @@ -271,8 +268,8 @@ def is_ancestor(base: Path, query: Path) -> bool: def wrap_session( - config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] -) -> Union[int, ExitCode]: + config: Config, doit: Callable[[Config, Session], int | ExitCode | None] +) -> int | ExitCode: """Skeleton command line program.""" session = Session.from_config(config) session.exitstatus = ExitCode.OK @@ -291,7 +288,7 @@ def wrap_session( session.exitstatus = ExitCode.TESTS_FAILED except (KeyboardInterrupt, exit.Exception): excinfo = _pytest._code.ExceptionInfo.from_current() - exitstatus: Union[int, ExitCode] = ExitCode.INTERRUPTED + exitstatus: int | ExitCode = ExitCode.INTERRUPTED if isinstance(excinfo.value, exit.Exception): if excinfo.value.returncode is not None: exitstatus = excinfo.value.returncode @@ -329,11 +326,11 @@ def wrap_session( return session.exitstatus -def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: +def pytest_cmdline_main(config: Config) -> int | ExitCode: return wrap_session(config, _main) -def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: +def _main(config: Config, session: Session) -> int | ExitCode | None: """Default command line protocol for initialization, session, running tests and reporting.""" config.hook.pytest_collection(session=session) @@ -346,11 +343,11 @@ def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: return None -def pytest_collection(session: "Session") -> None: +def pytest_collection(session: Session) -> None: session.perform_collect() -def pytest_runtestloop(session: "Session") -> bool: +def pytest_runtestloop(session: Session) -> bool: if session.testsfailed and not session.config.option.continue_on_collection_errors: raise session.Interrupted( "%d error%s during collection" @@ -390,7 +387,7 @@ def _in_venv(path: Path) -> bool: return any(fname.name in activates for fname in bindir.iterdir()) -def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]: +def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: if collection_path.name == "__pycache__": return True @@ -430,11 +427,11 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo def pytest_collect_directory( path: Path, parent: nodes.Collector -) -> Optional[nodes.Collector]: +) -> nodes.Collector | None: return Dir.from_parent(parent, path=path) -def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: +def pytest_collection_modifyitems(items: list[nodes.Item], config: Config) -> None: deselect_prefixes = tuple(config.getoption("deselect") or []) if not deselect_prefixes: return @@ -508,7 +505,7 @@ def from_parent( # type: ignore[override] parent: nodes.Collector, *, path: Path, - ) -> "Self": + ) -> Self: """The public constructor. :param parent: The parent collector of this Dir. @@ -516,9 +513,9 @@ def from_parent( # type: ignore[override] """ return super().from_parent(parent=parent, path=path) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: config = self.config - col: Optional[nodes.Collector] + col: nodes.Collector | None cols: Sequence[nodes.Collector] ihook = self.ihook for direntry in scandir(self.path): @@ -552,8 +549,8 @@ class Session(nodes.Collector): # Set on the session by runner.pytest_sessionstart. _setupstate: SetupState # Set on the session by fixtures.pytest_sessionstart. - _fixturemanager: "FixtureManager" - exitstatus: Union[int, ExitCode] + _fixturemanager: FixtureManager + exitstatus: int | ExitCode def __init__(self, config: Config) -> None: super().__init__( @@ -567,22 +564,22 @@ def __init__(self, config: Config) -> None: ) self.testsfailed = 0 self.testscollected = 0 - self._shouldstop: Union[bool, str] = False - self._shouldfail: Union[bool, str] = False + self._shouldstop: bool | str = False + self._shouldfail: bool | str = False self.trace = config.trace.root.get("collection") - self._initialpaths: FrozenSet[Path] = frozenset() - self._initialpaths_with_parents: FrozenSet[Path] = frozenset() - self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] - self._initial_parts: List[CollectionArgument] = [] - self._collection_cache: Dict[nodes.Collector, CollectReport] = {} - self.items: List[nodes.Item] = [] + self._initialpaths: frozenset[Path] = frozenset() + self._initialpaths_with_parents: frozenset[Path] = frozenset() + self._notfound: list[tuple[str, Sequence[nodes.Collector]]] = [] + self._initial_parts: list[CollectionArgument] = [] + self._collection_cache: dict[nodes.Collector, CollectReport] = {} + self.items: list[nodes.Item] = [] - self._bestrelpathcache: Dict[Path, str] = _bestrelpath_cache(config.rootpath) + self._bestrelpathcache: dict[Path, str] = _bestrelpath_cache(config.rootpath) self.config.pluginmanager.register(self, name="session") @classmethod - def from_config(cls, config: Config) -> "Session": + def from_config(cls, config: Config) -> Session: session: Session = cls._create(config=config) return session @@ -596,11 +593,11 @@ def __repr__(self) -> str: ) @property - def shouldstop(self) -> Union[bool, str]: + def shouldstop(self) -> bool | str: return self._shouldstop @shouldstop.setter - def shouldstop(self, value: Union[bool, str]) -> None: + def shouldstop(self, value: bool | str) -> None: # The runner checks shouldfail and assumes that if it is set we are # definitely stopping, so prevent unsetting it. if value is False and self._shouldstop: @@ -614,11 +611,11 @@ def shouldstop(self, value: Union[bool, str]) -> None: self._shouldstop = value @property - def shouldfail(self) -> Union[bool, str]: + def shouldfail(self) -> bool | str: return self._shouldfail @shouldfail.setter - def shouldfail(self, value: Union[bool, str]) -> None: + def shouldfail(self, value: bool | str) -> None: # The runner checks shouldfail and assumes that if it is set we are # definitely stopping, so prevent unsetting it. if value is False and self._shouldfail: @@ -651,9 +648,7 @@ def pytest_collectstart(self) -> None: raise self.Interrupted(self.shouldstop) @hookimpl(tryfirst=True) - def pytest_runtest_logreport( - self, report: Union[TestReport, CollectReport] - ) -> None: + def pytest_runtest_logreport(self, report: TestReport | CollectReport) -> None: if report.failed and not hasattr(report, "wasxfail"): self.testsfailed += 1 maxfail = self.config.getvalue("maxfail") @@ -664,7 +659,7 @@ def pytest_runtest_logreport( def isinitpath( self, - path: Union[str, "os.PathLike[str]"], + path: str | os.PathLike[str], *, with_parents: bool = False, ) -> bool: @@ -686,7 +681,7 @@ def isinitpath( else: return path_ in self._initialpaths - def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: + def gethookproxy(self, fspath: os.PathLike[str]) -> pluggy.HookRelay: # Optimization: Path(Path(...)) is much slower than isinstance. path = fspath if isinstance(fspath, Path) else Path(fspath) pm = self.config.pluginmanager @@ -706,7 +701,7 @@ def gethookproxy(self, fspath: "os.PathLike[str]") -> pluggy.HookRelay: def _collect_path( self, path: Path, - path_cache: Dict[Path, Sequence[nodes.Collector]], + path_cache: dict[Path, Sequence[nodes.Collector]], ) -> Sequence[nodes.Collector]: """Create a Collector for the given path. @@ -718,7 +713,7 @@ def _collect_path( if path.is_dir(): ihook = self.gethookproxy(path.parent) - col: Optional[nodes.Collector] = ihook.pytest_collect_directory( + col: nodes.Collector | None = ihook.pytest_collect_directory( path=path, parent=self ) cols: Sequence[nodes.Collector] = (col,) if col is not None else () @@ -736,17 +731,17 @@ def _collect_path( @overload def perform_collect( - self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... + self, args: Sequence[str] | None = ..., genitems: Literal[True] = ... ) -> Sequence[nodes.Item]: ... @overload def perform_collect( - self, args: Optional[Sequence[str]] = ..., genitems: bool = ... - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: ... + self, args: Sequence[str] | None = ..., genitems: bool = ... + ) -> Sequence[nodes.Item | nodes.Collector]: ... def perform_collect( - self, args: Optional[Sequence[str]] = None, genitems: bool = True - ) -> Sequence[Union[nodes.Item, nodes.Collector]]: + self, args: Sequence[str] | None = None, genitems: bool = True + ) -> Sequence[nodes.Item | nodes.Collector]: """Perform the collection phase for this session. This is called by the default :hook:`pytest_collection` hook @@ -772,10 +767,10 @@ def perform_collect( self._initial_parts = [] self._collection_cache = {} self.items = [] - items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items + items: Sequence[nodes.Item | nodes.Collector] = self.items try: - initialpaths: List[Path] = [] - initialpaths_with_parents: List[Path] = [] + initialpaths: list[Path] = [] + initialpaths_with_parents: list[Path] = [] for arg in args: collection_argument = resolve_collection_argument( self.config.invocation_params.dir, @@ -830,7 +825,7 @@ def _collect_one_node( self, node: nodes.Collector, handle_dupes: bool = True, - ) -> Tuple[CollectReport, bool]: + ) -> tuple[CollectReport, bool]: if node in self._collection_cache and handle_dupes: rep = self._collection_cache[node] return rep, True @@ -839,11 +834,11 @@ def _collect_one_node( self._collection_cache[node] = rep return rep, False - def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterator[nodes.Item | nodes.Collector]: # This is a cache for the root directories of the initial paths. # We can't use collection_cache for Session because of its special # role as the bootstrapping collector. - path_cache: Dict[Path, Sequence[nodes.Collector]] = {} + path_cache: dict[Path, Sequence[nodes.Collector]] = {} pm = self.config.pluginmanager @@ -881,9 +876,9 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # and discarding all nodes which don't match the level's part. any_matched_in_initial_part = False notfound_collectors = [] - work: List[ - Tuple[Union[nodes.Collector, nodes.Item], List[Union[Path, str]]] - ] = [(self, [*paths, *names])] + work: list[tuple[nodes.Collector | nodes.Item, list[Path | str]]] = [ + (self, [*paths, *names]) + ] while work: matchnode, matchparts = work.pop() @@ -900,7 +895,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: # Collect this level of matching. # Collecting Session (self) is done directly to avoid endless # recursion to this function. - subnodes: Sequence[Union[nodes.Collector, nodes.Item]] + subnodes: Sequence[nodes.Collector | nodes.Item] if isinstance(matchnode, Session): assert isinstance(matchparts[0], Path) subnodes = matchnode._collect_path(matchparts[0], path_cache) @@ -960,9 +955,7 @@ def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: self.trace.root.indent -= 1 - def genitems( - self, node: Union[nodes.Item, nodes.Collector] - ) -> Iterator[nodes.Item]: + def genitems(self, node: nodes.Item | nodes.Collector) -> Iterator[nodes.Item]: self.trace("genitems", node) if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) @@ -982,7 +975,7 @@ def genitems( node.ihook.pytest_collectreport(report=rep) -def search_pypath(module_name: str) -> Optional[str]: +def search_pypath(module_name: str) -> str | None: """Search sys.path for the given a dotted module name, and return its file system path if found.""" try: @@ -1006,7 +999,7 @@ class CollectionArgument: path: Path parts: Sequence[str] - module_name: Optional[str] + module_name: str | None def resolve_collection_argument( diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 950a6959ec9..b8a3092151f 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -1,12 +1,12 @@ """Generic mechanism for marking and selecting python functions.""" +from __future__ import annotations + import dataclasses from typing import AbstractSet from typing import Collection -from typing import List from typing import Optional from typing import TYPE_CHECKING -from typing import Union from .expression import Expression from .expression import ParseError @@ -44,8 +44,8 @@ def param( *values: object, - marks: Union[MarkDecorator, Collection[Union[MarkDecorator, Mark]]] = (), - id: Optional[str] = None, + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, ) -> ParameterSet: """Specify a parameter in `pytest.mark.parametrize`_ calls or :ref:`parametrized fixtures `. @@ -112,7 +112,7 @@ def pytest_addoption(parser: Parser) -> None: @hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: import _pytest.config if config.option.markers: @@ -151,7 +151,7 @@ class KeywordMatcher: _names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "KeywordMatcher": + def from_item(cls, item: Item) -> KeywordMatcher: mapped_names = set() # Add the names of the current item and any parent items, @@ -191,7 +191,7 @@ def __call__(self, subname: str) -> bool: return False -def deselect_by_keyword(items: "List[Item]", config: Config) -> None: +def deselect_by_keyword(items: list[Item], config: Config) -> None: keywordexpr = config.option.keyword.lstrip() if not keywordexpr: return @@ -223,7 +223,7 @@ class MarkMatcher: own_mark_names: AbstractSet[str] @classmethod - def from_item(cls, item: "Item") -> "MarkMatcher": + def from_item(cls, item: Item) -> MarkMatcher: mark_names = {mark.name for mark in item.iter_markers()} return cls(mark_names) @@ -231,14 +231,14 @@ def __call__(self, name: str) -> bool: return name in self.own_mark_names -def deselect_by_mark(items: "List[Item]", config: Config) -> None: +def deselect_by_mark(items: list[Item], config: Config) -> None: matchexpr = config.option.markexpr if not matchexpr: return expr = _parse_expression(matchexpr, "Wrong expression passed to '-m'") - remaining: List[Item] = [] - deselected: List[Item] = [] + remaining: list[Item] = [] + deselected: list[Item] = [] for item in items: if expr.evaluate(MarkMatcher.from_item(item)): remaining.append(item) @@ -256,7 +256,7 @@ def _parse_expression(expr: str, exc_message: str) -> Expression: raise UsageError(f"{exc_message}: {expr}: {e}") from None -def pytest_collection_modifyitems(items: "List[Item]", config: Config) -> None: +def pytest_collection_modifyitems(items: list[Item], config: Config) -> None: deselect_by_keyword(items, config) deselect_by_mark(items, config) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 78b7fda696b..e65b028589b 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -15,6 +15,8 @@ - or/and/not evaluate according to the usual boolean semantics. """ +from __future__ import annotations + import ast import dataclasses import enum @@ -24,7 +26,6 @@ from typing import Iterator from typing import Mapping from typing import NoReturn -from typing import Optional from typing import Sequence @@ -105,7 +106,7 @@ def lex(self, input: str) -> Iterator[Token]: ) yield Token(TokenType.EOF, "", pos) - def accept(self, type: TokenType, *, reject: bool = False) -> Optional[Token]: + def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: if self.current.type is type: token = self.current if token.type is not TokenType.EOF: @@ -197,7 +198,7 @@ def __init__(self, code: types.CodeType) -> None: self.code = code @classmethod - def compile(self, input: str) -> "Expression": + def compile(self, input: str) -> Expression: """Compile a match expression. :param input: The input expression - one line. diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py index 456808063ad..92ade55f7c0 100644 --- a/src/_pytest/mark/structures.py +++ b/src/_pytest/mark/structures.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import collections.abc import dataclasses import inspect @@ -8,16 +10,11 @@ from typing import final from typing import Iterable from typing import Iterator -from typing import List from typing import Mapping from typing import MutableMapping from typing import NamedTuple -from typing import Optional from typing import overload from typing import Sequence -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar from typing import Union @@ -48,7 +45,7 @@ def istestfunc(func) -> bool: def get_empty_parameterset_mark( config: Config, argnames: Sequence[str], func -) -> "MarkDecorator": +) -> MarkDecorator: from ..nodes import Collector fs, lineno = getfslineno(func) @@ -76,17 +73,17 @@ def get_empty_parameterset_mark( class ParameterSet(NamedTuple): - values: Sequence[Union[object, NotSetType]] - marks: Collection[Union["MarkDecorator", "Mark"]] - id: Optional[str] + values: Sequence[object | NotSetType] + marks: Collection[MarkDecorator | Mark] + id: str | None @classmethod def param( cls, *values: object, - marks: Union["MarkDecorator", Collection[Union["MarkDecorator", "Mark"]]] = (), - id: Optional[str] = None, - ) -> "ParameterSet": + marks: MarkDecorator | Collection[MarkDecorator | Mark] = (), + id: str | None = None, + ) -> ParameterSet: if isinstance(marks, MarkDecorator): marks = (marks,) else: @@ -101,9 +98,9 @@ def param( @classmethod def extract_from( cls, - parameterset: Union["ParameterSet", Sequence[object], object], + parameterset: ParameterSet | Sequence[object] | object, force_tuple: bool = False, - ) -> "ParameterSet": + ) -> ParameterSet: """Extract from an object or objects. :param parameterset: @@ -128,11 +125,11 @@ def extract_from( @staticmethod def _parse_parametrize_args( - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *args, **kwargs, - ) -> Tuple[Sequence[str], bool]: + ) -> tuple[Sequence[str], bool]: if isinstance(argnames, str): argnames = [x.strip() for x in argnames.split(",") if x.strip()] force_tuple = len(argnames) == 1 @@ -142,9 +139,9 @@ def _parse_parametrize_args( @staticmethod def _parse_parametrize_parameters( - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argvalues: Iterable[ParameterSet | Sequence[object] | object], force_tuple: bool, - ) -> List["ParameterSet"]: + ) -> list[ParameterSet]: return [ ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues ] @@ -152,12 +149,12 @@ def _parse_parametrize_parameters( @classmethod def _for_parametrize( cls, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union["ParameterSet", Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], func, config: Config, nodeid: str, - ) -> Tuple[Sequence[str], List["ParameterSet"]]: + ) -> tuple[Sequence[str], list[ParameterSet]]: argnames, force_tuple = cls._parse_parametrize_args(argnames, argvalues) parameters = cls._parse_parametrize_parameters(argvalues, force_tuple) del argvalues @@ -200,24 +197,24 @@ class Mark: #: Name of the mark. name: str #: Positional arguments of the mark decorator. - args: Tuple[Any, ...] + args: tuple[Any, ...] #: Keyword arguments of the mark decorator. kwargs: Mapping[str, Any] #: Source Mark for ids with parametrize Marks. - _param_ids_from: Optional["Mark"] = dataclasses.field(default=None, repr=False) + _param_ids_from: Mark | None = dataclasses.field(default=None, repr=False) #: Resolved/generated ids with parametrize Marks. - _param_ids_generated: Optional[Sequence[str]] = dataclasses.field( + _param_ids_generated: Sequence[str] | None = dataclasses.field( default=None, repr=False ) def __init__( self, name: str, - args: Tuple[Any, ...], + args: tuple[Any, ...], kwargs: Mapping[str, Any], - param_ids_from: Optional["Mark"] = None, - param_ids_generated: Optional[Sequence[str]] = None, + param_ids_from: Mark | None = None, + param_ids_generated: Sequence[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -233,7 +230,7 @@ def __init__( def _has_param_ids(self) -> bool: return "ids" in self.kwargs or len(self.args) >= 4 - def combined_with(self, other: "Mark") -> "Mark": + def combined_with(self, other: Mark) -> Mark: """Return a new Mark which is a combination of this Mark and another Mark. @@ -245,7 +242,7 @@ def combined_with(self, other: "Mark") -> "Mark": assert self.name == other.name # Remember source of ids with parametrize Marks. - param_ids_from: Optional[Mark] = None + param_ids_from: Mark | None = None if self.name == "parametrize": if other._has_param_ids(): param_ids_from = other @@ -316,7 +313,7 @@ def name(self) -> str: return self.mark.name @property - def args(self) -> Tuple[Any, ...]: + def args(self) -> tuple[Any, ...]: """Alias for mark.args.""" return self.mark.args @@ -330,7 +327,7 @@ def markname(self) -> str: """:meta private:""" return self.name # for backward-compat (2.4.1 had this attr) - def with_args(self, *args: object, **kwargs: object) -> "MarkDecorator": + def with_args(self, *args: object, **kwargs: object) -> MarkDecorator: """Return a MarkDecorator with extra arguments added. Unlike calling the MarkDecorator, with_args() can be used even @@ -347,7 +344,7 @@ def __call__(self, arg: Markable) -> Markable: # type: ignore[overload-overlap] pass @overload - def __call__(self, *args: object, **kwargs: object) -> "MarkDecorator": + def __call__(self, *args: object, **kwargs: object) -> MarkDecorator: pass def __call__(self, *args: object, **kwargs: object): @@ -362,10 +359,10 @@ def __call__(self, *args: object, **kwargs: object): def get_unpacked_marks( - obj: Union[object, type], + obj: object | type, *, consider_mro: bool = True, -) -> List[Mark]: +) -> 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 @@ -395,7 +392,7 @@ def get_unpacked_marks( def normalize_mark_list( - mark_list: Iterable[Union[Mark, MarkDecorator]], + mark_list: Iterable[Mark | MarkDecorator], ) -> Iterable[Mark]: """ Normalize an iterable of Mark or MarkDecorator objects into a list of marks @@ -437,13 +434,13 @@ class _SkipMarkDecorator(MarkDecorator): def __call__(self, arg: Markable) -> Markable: ... @overload - def __call__(self, reason: str = ...) -> "MarkDecorator": ... + def __call__(self, reason: str = ...) -> MarkDecorator: ... class _SkipifMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - condition: Union[str, bool] = ..., - *conditions: Union[str, bool], + condition: str | bool = ..., + *conditions: str | bool, reason: str = ..., ) -> MarkDecorator: ... @@ -454,30 +451,25 @@ def __call__(self, arg: Markable) -> Markable: ... @overload def __call__( self, - condition: Union[str, bool] = False, - *conditions: Union[str, bool], + condition: str | bool = False, + *conditions: str | bool, reason: str = ..., run: bool = ..., - raises: Union[ - None, Type[BaseException], Tuple[Type[BaseException], ...] - ] = ..., + raises: None | type[BaseException] | tuple[type[BaseException], ...] = ..., strict: bool = ..., ) -> MarkDecorator: ... class _ParametrizeMarkDecorator(MarkDecorator): def __call__( # type: ignore[override] self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], *, - indirect: Union[bool, Sequence[str]] = ..., - ids: Optional[ - Union[ - Iterable[Union[None, str, float, int, bool]], - Callable[[Any], Optional[object]], - ] - ] = ..., - scope: Optional[_ScopeName] = ..., + indirect: bool | Sequence[str] = ..., + ids: Iterable[None | str | float | int | bool] + | Callable[[Any], object | None] + | None = ..., + scope: _ScopeName | None = ..., ) -> MarkDecorator: ... class _UsefixturesMarkDecorator(MarkDecorator): @@ -517,8 +509,8 @@ def test_function(): def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) - self._config: Optional[Config] = None - self._markers: Set[str] = set() + self._config: Config | None = None + self._markers: set[str] = set() def __getattr__(self, name: str) -> MarkDecorator: """Generate a new :class:`MarkDecorator` with the given name.""" @@ -569,7 +561,7 @@ def __getattr__(self, name: str) -> MarkDecorator: class NodeKeywords(MutableMapping[str, Any]): __slots__ = ("node", "parent", "_markers") - def __init__(self, node: "Node") -> None: + def __init__(self, node: Node) -> None: self.node = node self.parent = node.parent self._markers = {node.name: True} @@ -597,7 +589,7 @@ def __contains__(self, key: object) -> bool: def update( # type: ignore[override] self, - other: Union[Mapping[str, Any], Iterable[Tuple[str, Any]]] = (), + other: Mapping[str, Any] | Iterable[tuple[str, Any]] = (), **kwds: Any, ) -> None: self._markers.update(other) diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index f498d60df14..75b019a3be6 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Monkeypatching and mocking functionality.""" +from __future__ import annotations + from contextlib import contextmanager import os import re @@ -8,14 +10,10 @@ from typing import Any from typing import final from typing import Generator -from typing import List from typing import Mapping from typing import MutableMapping -from typing import Optional from typing import overload -from typing import Tuple from typing import TypeVar -from typing import Union import warnings from _pytest.fixtures import fixture @@ -30,7 +28,7 @@ @fixture -def monkeypatch() -> Generator["MonkeyPatch", None, None]: +def monkeypatch() -> Generator[MonkeyPatch, None, None]: """A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or @@ -97,7 +95,7 @@ def annotated_getattr(obj: object, name: str, ann: str) -> object: return obj -def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]: +def derive_importpath(import_path: str, raising: bool) -> tuple[str, object]: if not isinstance(import_path, str) or "." not in import_path: raise TypeError(f"must be absolute import path string, not {import_path!r}") module, attr = import_path.rsplit(".", 1) @@ -130,14 +128,14 @@ class MonkeyPatch: """ def __init__(self) -> None: - self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] - self._cwd: Optional[str] = None - self._savesyspath: Optional[List[str]] = None + self._setattr: list[tuple[object, str, object]] = [] + self._setitem: list[tuple[Mapping[Any, Any], object, object]] = [] + self._cwd: str | None = None + self._savesyspath: list[str] | None = None @classmethod @contextmanager - def context(cls) -> Generator["MonkeyPatch", None, None]: + def context(cls) -> Generator[MonkeyPatch, None, None]: """Context manager that returns a new :class:`MonkeyPatch` object which undoes any patching done inside the ``with`` block upon exit. @@ -182,8 +180,8 @@ def setattr( def setattr( self, - target: Union[str, object], - name: Union[object, str], + target: str | object, + name: object | str, value: object = notset, raising: bool = True, ) -> None: @@ -254,8 +252,8 @@ def setattr( def delattr( self, - target: Union[object, str], - name: Union[str, Notset] = notset, + target: object | str, + name: str | Notset = notset, raising: bool = True, ) -> None: """Delete attribute ``name`` from ``target``. @@ -310,7 +308,7 @@ def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: # 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: + def setenv(self, name: str, value: str, prepend: str | None = None) -> None: """Set environment variable ``name`` to ``value``. If ``prepend`` is a character, read the current environment variable @@ -363,7 +361,7 @@ def syspath_prepend(self, path) -> None: invalidate_caches() - def chdir(self, path: Union[str, "os.PathLike[str]"]) -> None: + def chdir(self, path: str | os.PathLike[str]) -> None: """Change the current working directory to the specified path. :param path: diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py index aad54f8b325..bbde2664b90 100644 --- a/src/_pytest/nodes.py +++ b/src/_pytest/nodes.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import abc from functools import cached_property from inspect import signature @@ -10,17 +12,11 @@ from typing import cast from typing import Iterable from typing import Iterator -from typing import List from typing import MutableMapping from typing import NoReturn -from typing import Optional from typing import overload -from typing import Set -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import warnings import pluggy @@ -62,9 +58,9 @@ def _imply_path( - node_type: Type["Node"], - path: Optional[Path], - fspath: Optional[LEGACY_PATH], + node_type: type[Node], + path: Path | None, + fspath: LEGACY_PATH | None, ) -> Path: if fspath is not None: warnings.warn( @@ -109,7 +105,7 @@ def __call__(cls, *k, **kw) -> NoReturn: ).format(name=f"{cls.__module__}.{cls.__name__}") fail(msg, pytrace=False) - def _create(cls: Type[_T], *k, **kw) -> _T: + def _create(cls: type[_T], *k, **kw) -> _T: try: return super().__call__(*k, **kw) # type: ignore[no-any-return,misc] except TypeError: @@ -160,12 +156,12 @@ class Node(abc.ABC, metaclass=NodeMeta): def __init__( self, name: str, - parent: "Optional[Node]" = None, - config: Optional[Config] = None, - session: "Optional[Session]" = None, - fspath: Optional[LEGACY_PATH] = None, - path: Optional[Path] = None, - nodeid: Optional[str] = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + fspath: LEGACY_PATH | None = None, + path: Path | None = None, + nodeid: str | None = None, ) -> None: #: A unique name within the scope of the parent node. self.name: str = name @@ -199,10 +195,10 @@ def __init__( self.keywords: MutableMapping[str, Any] = NodeKeywords(self) #: The marker objects belonging to this node. - self.own_markers: List[Mark] = [] + self.own_markers: list[Mark] = [] #: Allow adding of extra keywords to use for matching. - self.extra_keyword_matches: Set[str] = set() + self.extra_keyword_matches: set[str] = set() if nodeid is not None: assert "::()" not in nodeid @@ -219,7 +215,7 @@ def __init__( self._store = self.stash @classmethod - def from_parent(cls, parent: "Node", **kw) -> "Self": + def from_parent(cls, parent: Node, **kw) -> Self: """Public constructor for Nodes. This indirection got introduced in order to enable removing @@ -295,31 +291,29 @@ def setup(self) -> None: def teardown(self) -> None: pass - def iter_parents(self) -> Iterator["Node"]: + def iter_parents(self) -> Iterator[Node]: """Iterate over all parent collectors starting from and including self up to the root of the collection tree. .. versionadded:: 8.1 """ - parent: Optional[Node] = self + parent: Node | None = self while parent is not None: yield parent parent = parent.parent - def listchain(self) -> List["Node"]: + def listchain(self) -> list[Node]: """Return a list of all parent collectors starting from the root of the collection tree down to and including self.""" chain = [] - item: Optional[Node] = self + item: Node | None = self while item is not None: chain.append(item) item = item.parent chain.reverse() return chain - def add_marker( - self, marker: Union[str, MarkDecorator], append: bool = True - ) -> None: + def add_marker(self, marker: str | MarkDecorator, append: bool = True) -> None: """Dynamically add a marker object to the node. :param marker: @@ -341,7 +335,7 @@ def add_marker( else: self.own_markers.insert(0, marker_.mark) - def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: + def iter_markers(self, name: str | None = None) -> Iterator[Mark]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -350,8 +344,8 @@ def iter_markers(self, name: Optional[str] = None) -> Iterator[Mark]: return (x[1] for x in self.iter_markers_with_node(name=name)) def iter_markers_with_node( - self, name: Optional[str] = None - ) -> Iterator[Tuple["Node", Mark]]: + self, name: str | None = None + ) -> Iterator[tuple[Node, Mark]]: """Iterate over all markers of the node. :param name: If given, filter the results by the name attribute. @@ -363,14 +357,12 @@ def iter_markers_with_node( yield node, mark @overload - def get_closest_marker(self, name: str) -> Optional[Mark]: ... + def get_closest_marker(self, name: str) -> Mark | None: ... @overload def get_closest_marker(self, name: str, default: Mark) -> Mark: ... - def get_closest_marker( - self, name: str, default: Optional[Mark] = None - ) -> Optional[Mark]: + def get_closest_marker(self, name: str, default: Mark | None = None) -> Mark | None: """Return the first marker matching the name, from closest (for example function) to farther level (for example module level). @@ -379,14 +371,14 @@ def get_closest_marker( """ return next(self.iter_markers(name=name), default) - def listextrakeywords(self) -> Set[str]: + def listextrakeywords(self) -> set[str]: """Return a set of all extra keywords in self and any parents.""" - extra_keywords: Set[str] = set() + extra_keywords: set[str] = set() for item in self.listchain(): extra_keywords.update(item.extra_keyword_matches) return extra_keywords - def listnames(self) -> List[str]: + def listnames(self) -> list[str]: return [x.name for x in self.listchain()] def addfinalizer(self, fin: Callable[[], object]) -> None: @@ -398,7 +390,7 @@ def addfinalizer(self, fin: Callable[[], object]) -> None: """ self.session._setupstate.addfinalizer(fin, self) - def getparent(self, cls: Type[_NodeType]) -> Optional[_NodeType]: + def getparent(self, cls: type[_NodeType]) -> _NodeType | None: """Get the closest parent node (including self) which is an instance of the given class. @@ -416,7 +408,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def _repr_failure_py( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[TracebackStyle]" = None, + style: TracebackStyle | None = None, ) -> TerminalRepr: from _pytest.fixtures import FixtureLookupError @@ -428,7 +420,7 @@ def _repr_failure_py( if isinstance(excinfo.value, FixtureLookupError): return excinfo.value.formatrepr() - tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] + tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] if self.config.getoption("fulltrace", False): style = "long" tbfilter = False @@ -474,8 +466,8 @@ def _repr_failure_py( def repr_failure( self, excinfo: ExceptionInfo[BaseException], - style: "Optional[TracebackStyle]" = None, - ) -> Union[str, TerminalRepr]: + style: TracebackStyle | None = None, + ) -> str | TerminalRepr: """Return a representation of a collection or test failure. .. seealso:: :ref:`non-python tests` @@ -485,7 +477,7 @@ def repr_failure( return self._repr_failure_py(excinfo, style) -def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[int]]: +def get_fslocation_from_item(node: Node) -> tuple[str | Path, int | None]: """Try to extract the actual location from a node, depending on available attributes: * "location": a pair (path, lineno) @@ -495,7 +487,7 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i :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) + location: tuple[str, int | None, str] | None = getattr(node, "location", None) if location is not None: return location[:2] obj = getattr(node, "obj", None) @@ -515,14 +507,14 @@ class CollectError(Exception): """An error during collection, contains a custom message.""" @abc.abstractmethod - def collect(self) -> Iterable[Union["Item", "Collector"]]: + def collect(self) -> Iterable[Item | Collector]: """Collect children (items and collectors) for this collector.""" raise NotImplementedError("abstract") # TODO: This omits the style= parameter which breaks Liskov Substitution. def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException] - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: """Return a representation of a collection failure. :param excinfo: Exception information for the failure. @@ -551,7 +543,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: return excinfo.traceback -def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]: +def _check_initialpaths_for_relpath(session: Session, path: Path) -> str | None: for initial_path in session._initialpaths: if commonpath(path, initial_path) == initial_path: rel = str(path.relative_to(initial_path)) @@ -564,14 +556,14 @@ class FSCollector(Collector, abc.ABC): def __init__( self, - fspath: Optional[LEGACY_PATH] = None, - path_or_parent: Optional[Union[Path, Node]] = None, - path: Optional[Path] = None, - name: Optional[str] = None, - parent: Optional[Node] = None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + fspath: LEGACY_PATH | None = None, + path_or_parent: Path | Node | None = None, + path: Path | None = None, + name: str | None = None, + parent: Node | None = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, ) -> None: if path_or_parent: if isinstance(path_or_parent, Node): @@ -621,10 +613,10 @@ def from_parent( cls, parent, *, - fspath: Optional[LEGACY_PATH] = None, - path: Optional[Path] = None, + fspath: LEGACY_PATH | None = None, + path: Path | None = None, **kw, - ) -> "Self": + ) -> Self: """The public constructor.""" return super().from_parent(parent=parent, fspath=fspath, path=path, **kw) @@ -666,9 +658,9 @@ def __init__( self, name, parent=None, - config: Optional[Config] = None, - session: Optional["Session"] = None, - nodeid: Optional[str] = None, + config: Config | None = None, + session: Session | None = None, + nodeid: str | None = None, **kw, ) -> None: # The first two arguments are intentionally passed positionally, @@ -683,11 +675,11 @@ def __init__( nodeid=nodeid, **kw, ) - self._report_sections: List[Tuple[str, str, str]] = [] + self._report_sections: list[tuple[str, str, str]] = [] #: A list of tuples (name, value) that holds user defined properties #: for this test. - self.user_properties: List[Tuple[str, object]] = [] + self.user_properties: list[tuple[str, object]] = [] self._check_item_and_collector_diamond_inheritance() @@ -747,7 +739,7 @@ def add_report_section(self, when: str, key: str, content: str) -> None: if content: self._report_sections.append((when, key, content)) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: """Get location information for this item for test reports. Returns a tuple with three elements: @@ -761,7 +753,7 @@ def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str return self.path, None, "" @cached_property - def location(self) -> Tuple[str, Optional[int], str]: + def location(self) -> tuple[str, int | None, str]: """ Returns a tuple of ``(relfspath, lineno, testname)`` for this item where ``relfspath`` is file path relative to ``config.rootpath`` diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index f953dabe03d..5b20803e586 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -1,12 +1,13 @@ """Exception classes and constants handling test outcomes as well as functions creating them.""" +from __future__ import annotations + import sys from typing import Any from typing import Callable from typing import cast from typing import NoReturn -from typing import Optional from typing import Protocol from typing import Type from typing import TypeVar @@ -18,7 +19,7 @@ class OutcomeException(BaseException): """OutcomeException and its subclass instances indicate and contain info about test and collection outcomes.""" - def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: + def __init__(self, msg: str | None = None, pytrace: bool = True) -> None: if msg is not None and not isinstance(msg, str): error_msg = ( # type: ignore[unreachable] "{} expected string as 'msg' parameter, got '{}' instead.\n" @@ -47,7 +48,7 @@ class Skipped(OutcomeException): def __init__( self, - msg: Optional[str] = None, + msg: str | None = None, pytrace: bool = True, allow_module_level: bool = False, *, @@ -70,7 +71,7 @@ class Exit(Exception): """Raised for immediate program exits (no tracebacks/summaries).""" def __init__( - self, msg: str = "unknown reason", returncode: Optional[int] = None + self, msg: str = "unknown reason", returncode: int | None = None ) -> None: self.msg = msg self.returncode = returncode @@ -104,7 +105,7 @@ def decorate(func: _F) -> _WithException[_F, _ET]: @_with_exception(Exit) def exit( reason: str = "", - returncode: Optional[int] = None, + returncode: int | None = None, ) -> NoReturn: """Exit testing process. @@ -207,10 +208,10 @@ def xfail(reason: str = "") -> NoReturn: def importorskip( modname: str, - minversion: Optional[str] = None, - reason: Optional[str] = None, + minversion: str | None = None, + reason: str | None = None, *, - exc_type: Optional[Type[ImportError]] = None, + exc_type: type[ImportError] | None = None, ) -> Any: """Import and return the requested module ``modname``, or skip the current test if the module cannot be imported. @@ -267,8 +268,8 @@ def importorskip( else: warn_on_import_error = False - skipped: Optional[Skipped] = None - warning: Optional[Warning] = None + skipped: Skipped | None = None + warning: Warning | None = None with warnings.catch_warnings(): # Make sure to ignore ImportWarnings that might happen because diff --git a/src/_pytest/pastebin.py b/src/_pytest/pastebin.py index 20cb8ca83c3..69c011ed24a 100644 --- a/src/_pytest/pastebin.py +++ b/src/_pytest/pastebin.py @@ -1,10 +1,11 @@ # mypy: allow-untyped-defs """Submit failure or test session information to a pastebin service.""" +from __future__ import annotations + from io import StringIO import tempfile from typing import IO -from typing import Union from _pytest.config import Config from _pytest.config import create_terminal_writer @@ -68,7 +69,7 @@ def pytest_unconfigure(config: Config) -> None: tr.write_line(f"pastebin session-log: {pastebinurl}\n") -def create_new_paste(contents: Union[str, bytes]) -> str: +def create_new_paste(contents: str | bytes) -> str: """Create a new paste using the bpaste.net service. :contents: Paste contents string. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index b11eea4e7ef..e4dc4eddc9c 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import atexit import contextlib from enum import Enum @@ -24,16 +26,9 @@ from types import ModuleType from typing import Any 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 import uuid import warnings @@ -71,12 +66,10 @@ def get_lock_path(path: _AnyPurePath) -> _AnyPurePath: def on_rm_rf_error( - func: Optional[Callable[..., Any]], + func: Callable[..., Any] | None, path: str, - excinfo: Union[ - BaseException, - Tuple[Type[BaseException], BaseException, Optional[types.TracebackType]], - ], + excinfo: BaseException + | tuple[type[BaseException], BaseException, types.TracebackType | None], *, start_path: Path, ) -> bool: @@ -172,7 +165,7 @@ def rm_rf(path: Path) -> None: shutil.rmtree(str(path), onerror=onerror) -def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]: +def find_prefixed(root: Path, prefix: str) -> Iterator[os.DirEntry[str]]: """Find all elements in root that begin with the prefix, case-insensitive.""" l_prefix = prefix.lower() for x in os.scandir(root): @@ -180,7 +173,7 @@ def find_prefixed(root: Path, prefix: str) -> Iterator["os.DirEntry[str]"]: yield x -def extract_suffixes(iter: Iterable["os.DirEntry[str]"], prefix: str) -> Iterator[str]: +def extract_suffixes(iter: Iterable[os.DirEntry[str]], prefix: str) -> Iterator[str]: """Return the parts of the paths following the prefix. :param iter: Iterator over path names. @@ -204,9 +197,7 @@ def parse_num(maybe_num: str) -> int: return -1 -def _force_symlink( - root: Path, target: Union[str, PurePath], link_to: Union[str, Path] -) -> None: +def _force_symlink(root: Path, target: str | PurePath, link_to: str | Path) -> None: """Helper to create the current symlink. It's full of race conditions that are reasonably OK to ignore @@ -420,7 +411,7 @@ def resolve_from_str(input: str, rootpath: Path) -> Path: return rootpath.joinpath(input) -def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: +def fnmatch_ex(pattern: str, path: str | os.PathLike[str]) -> bool: """A port of FNMatcher from py.path.common which works with PurePath() instances. The difference between this algorithm and PurePath.match() is that the @@ -456,14 +447,14 @@ def fnmatch_ex(pattern: str, path: Union[str, "os.PathLike[str]"]) -> bool: return fnmatch.fnmatch(name, pattern) -def parts(s: str) -> Set[str]: +def parts(s: str) -> set[str]: parts = s.split(sep) return {sep.join(parts[: i + 1]) or sep for i in range(len(parts))} def symlink_or_skip( - src: Union["os.PathLike[str]", str], - dst: Union["os.PathLike[str]", str], + src: os.PathLike[str] | str, + dst: os.PathLike[str] | str, **kwargs: Any, ) -> None: """Make a symlink, or skip the test in case symlinks are not supported.""" @@ -491,9 +482,9 @@ class ImportPathMismatchError(ImportError): def import_path( - path: Union[str, "os.PathLike[str]"], + path: str | os.PathLike[str], *, - mode: Union[str, ImportMode] = ImportMode.prepend, + mode: str | ImportMode = ImportMode.prepend, root: Path, consider_namespace_packages: bool, ) -> ModuleType: @@ -618,7 +609,7 @@ def import_path( def _import_module_using_spec( module_name: str, module_path: Path, module_location: Path, *, insert_modules: bool -) -> Optional[ModuleType]: +) -> ModuleType | None: """ Tries to import a module by its canonical name, path to the .py file, and its parent location. @@ -641,7 +632,7 @@ def _import_module_using_spec( # Attempt to import the parent module, seems is our responsibility: # https://github.com/python/cpython/blob/73906d5c908c1e0b73c5436faeff7d93698fc074/Lib/importlib/_bootstrap.py#L1308-L1311 parent_module_name, _, name = module_name.rpartition(".") - parent_module: Optional[ModuleType] = None + parent_module: ModuleType | None = None if parent_module_name: parent_module = sys.modules.get(parent_module_name) if parent_module is None: @@ -680,9 +671,7 @@ def _import_module_using_spec( return None -def spec_matches_module_path( - module_spec: Optional[ModuleSpec], module_path: Path -) -> bool: +def spec_matches_module_path(module_spec: ModuleSpec | None, module_path: Path) -> bool: """Return true if the given ModuleSpec can be used to import the given module path.""" if module_spec is None or module_spec.origin is None: return False @@ -734,7 +723,7 @@ def module_name_from_path(path: Path, root: Path) -> str: return ".".join(path_parts) -def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> None: +def insert_missing_modules(modules: dict[str, ModuleType], module_name: str) -> None: """ Used by ``import_path`` to create intermediate modules when using mode=importlib. @@ -772,7 +761,7 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) -> module_name = ".".join(module_parts) -def resolve_package_path(path: Path) -> Optional[Path]: +def resolve_package_path(path: Path) -> Path | None: """Return the Python package path by looking for the last directory upwards which still contains an __init__.py. @@ -791,7 +780,7 @@ def resolve_package_path(path: Path) -> Optional[Path]: def resolve_pkg_root_and_module_name( path: Path, *, consider_namespace_packages: bool = False -) -> Tuple[Path, str]: +) -> tuple[Path, str]: """ Return the path to the directory of the root package that contains the given Python file, and its module name: @@ -812,7 +801,7 @@ def resolve_pkg_root_and_module_name( Raises CouldNotResolvePathError if the given path does not belong to a package (missing any __init__.py files). """ - pkg_root: Optional[Path] = None + pkg_root: Path | None = None pkg_path = resolve_package_path(path) if pkg_path is not None: pkg_root = pkg_path.parent @@ -859,7 +848,7 @@ def is_importable(module_name: str, module_path: Path) -> bool: return spec_matches_module_path(spec, module_path) -def compute_module_name(root: Path, module_path: Path) -> Optional[str]: +def compute_module_name(root: Path, module_path: Path) -> str | None: """Compute a module name based on a path and a root anchor.""" try: path_without_suffix = module_path.with_suffix("") @@ -884,9 +873,9 @@ class CouldNotResolvePathError(Exception): def scandir( - path: Union[str, "os.PathLike[str]"], - sort_key: Callable[["os.DirEntry[str]"], object] = lambda entry: entry.name, -) -> List["os.DirEntry[str]"]: + path: str | os.PathLike[str], + sort_key: Callable[[os.DirEntry[str]], object] = lambda entry: entry.name, +) -> list[os.DirEntry[str]]: """Scan a directory recursively, in breadth-first order. The returned entries are sorted according to the given key. @@ -909,8 +898,8 @@ def scandir( def visit( - path: Union[str, "os.PathLike[str]"], recurse: Callable[["os.DirEntry[str]"], bool] -) -> Iterator["os.DirEntry[str]"]: + path: 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. @@ -924,7 +913,7 @@ def visit( yield from visit(entry.path, recurse) -def absolutepath(path: "Union[str, os.PathLike[str]]") -> Path: +def absolutepath(path: str | os.PathLike[str]) -> Path: """Convert a path to an absolute path using os.path.abspath. Prefer this over Path.resolve() (see #6523). @@ -933,7 +922,7 @@ def absolutepath(path: "Union[str, os.PathLike[str]]") -> Path: return Path(os.path.abspath(path)) -def commonpath(path1: Path, path2: Path) -> Optional[Path]: +def commonpath(path1: Path, path2: Path) -> Path | None: """Return the common part shared with the other path, or None if there is no common part. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 42f50900ada..e27648507e9 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -4,6 +4,8 @@ PYTEST_DONT_REWRITE """ +from __future__ import annotations + import collections.abc import contextlib from fnmatch import fnmatch @@ -21,22 +23,16 @@ import traceback from typing import Any from typing import Callable -from typing import Dict from typing import Final from typing import final from typing import Generator from typing import IO from typing import Iterable -from typing import List from typing import Literal -from typing import Optional from typing import overload from typing import Sequence from typing import TextIO -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import Union from weakref import WeakKeyDictionary from iniconfig import IniConfig @@ -123,7 +119,7 @@ def pytest_configure(config: Config) -> None: class LsofFdLeakChecker: - def get_open_files(self) -> List[Tuple[str, str]]: + def get_open_files(self) -> list[tuple[str, str]]: if sys.version_info >= (3, 11): # New in Python 3.11, ignores utf-8 mode encoding = locale.getencoding() @@ -199,7 +195,7 @@ def pytest_runtest_protocol(self, item: Item) -> Generator[None, object, object] @fixture -def _pytest(request: FixtureRequest) -> "PytestArg": +def _pytest(request: FixtureRequest) -> PytestArg: """Return a helper which offers a gethookrecorder(hook) method which returns a HookRecorder instance which helps to make assertions about called hooks.""" @@ -210,13 +206,13 @@ class PytestArg: def __init__(self, request: FixtureRequest) -> None: self._request = request - def gethookrecorder(self, hook) -> "HookRecorder": + def gethookrecorder(self, hook) -> HookRecorder: hookrecorder = HookRecorder(hook._pm) self._request.addfinalizer(hookrecorder.finish_recording) return hookrecorder -def get_public_names(values: Iterable[str]) -> List[str]: +def get_public_names(values: Iterable[str]) -> list[str]: """Only return names from iterator values without a leading underscore.""" return [x for x in values if x[0] != "_"] @@ -265,8 +261,8 @@ def __init__( check_ispytest(_ispytest) self._pluginmanager = pluginmanager - self.calls: List[RecordedHookCall] = [] - self.ret: Optional[Union[int, ExitCode]] = None + self.calls: list[RecordedHookCall] = [] + self.ret: int | ExitCode | None = None def before(hook_name: str, hook_impls, kwargs) -> None: self.calls.append(RecordedHookCall(hook_name, kwargs)) @@ -279,13 +275,13 @@ def after(outcome, hook_name: str, hook_impls, kwargs) -> None: def finish_recording(self) -> None: self._undo_wrapping() - def getcalls(self, names: Union[str, Iterable[str]]) -> List[RecordedHookCall]: + def getcalls(self, names: str | Iterable[str]) -> list[RecordedHookCall]: """Get all recorded calls to hooks with the given names (or name).""" if isinstance(names, str): names = names.split() return [call for call in self.calls if call._name in names] - def assert_contains(self, entries: Sequence[Tuple[str, str]]) -> None: + def assert_contains(self, entries: Sequence[tuple[str, str]]) -> None: __tracebackhide__ = True i = 0 entries = list(entries) @@ -327,42 +323,42 @@ def getcall(self, name: str) -> RecordedHookCall: @overload def getreports( self, - names: "Literal['pytest_collectreport']", + names: Literal["pytest_collectreport"], ) -> Sequence[CollectReport]: ... @overload def getreports( self, - names: "Literal['pytest_runtest_logreport']", + names: Literal["pytest_runtest_logreport"], ) -> Sequence[TestReport]: ... @overload def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: ... + ) -> Sequence[CollectReport | TestReport]: ... def getreports( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [x.report for x in self.getcalls(names)] def matchreport( self, inamepart: str = "", - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_runtest_logreport", "pytest_collectreport", ), - when: Optional[str] = None, - ) -> Union[CollectReport, TestReport]: + when: str | None = None, + ) -> CollectReport | TestReport: """Return a testreport whose dotted import path matches.""" values = [] for rep in self.getreports(names=names): @@ -387,31 +383,31 @@ def matchreport( @overload def getfailures( self, - names: "Literal['pytest_collectreport']", + names: Literal["pytest_collectreport"], ) -> Sequence[CollectReport]: ... @overload def getfailures( self, - names: "Literal['pytest_runtest_logreport']", + names: Literal["pytest_runtest_logreport"], ) -> Sequence[TestReport]: ... @overload def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: ... + ) -> Sequence[CollectReport | TestReport]: ... def getfailures( self, - names: Union[str, Iterable[str]] = ( + names: str | Iterable[str] = ( "pytest_collectreport", "pytest_runtest_logreport", ), - ) -> Sequence[Union[CollectReport, TestReport]]: + ) -> Sequence[CollectReport | TestReport]: return [rep for rep in self.getreports(names) if rep.failed] def getfailedcollections(self) -> Sequence[CollectReport]: @@ -419,10 +415,10 @@ def getfailedcollections(self) -> Sequence[CollectReport]: def listoutcomes( self, - ) -> Tuple[ + ) -> tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ]: passed = [] skipped = [] @@ -441,7 +437,7 @@ def listoutcomes( failed.append(rep) return passed, skipped, failed - def countoutcomes(self) -> List[int]: + def countoutcomes(self) -> list[int]: return [len(x) for x in self.listoutcomes()] def assertoutcome(self, passed: int = 0, skipped: int = 0, failed: int = 0) -> None: @@ -461,14 +457,14 @@ def clear(self) -> None: @fixture -def linecomp() -> "LineComp": +def linecomp() -> LineComp: """A :class: `LineComp` instance for checking that an input linearly contains a sequence of strings.""" return LineComp() @fixture(name="LineMatcher") -def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: +def LineMatcher_fixture(request: FixtureRequest) -> type[LineMatcher]: """A reference to the :class: `LineMatcher`. This is instantiable with a list of lines (without their trailing newlines). @@ -480,7 +476,7 @@ def LineMatcher_fixture(request: FixtureRequest) -> Type["LineMatcher"]: @fixture def pytester( request: FixtureRequest, tmp_path_factory: TempPathFactory, monkeypatch: MonkeyPatch -) -> "Pytester": +) -> Pytester: """ Facilities to write tests/configuration files, execute pytest in isolation, and match against expected output, perfect for black-box testing of pytest plugins. @@ -524,13 +520,13 @@ class RunResult: def __init__( self, - ret: Union[int, ExitCode], - outlines: List[str], - errlines: List[str], + ret: int | ExitCode, + outlines: list[str], + errlines: list[str], duration: float, ) -> None: try: - self.ret: Union[int, ExitCode] = ExitCode(ret) + self.ret: int | ExitCode = ExitCode(ret) """The return value.""" except ValueError: self.ret = ret @@ -555,7 +551,7 @@ def __repr__(self) -> str: % (self.ret, len(self.stdout.lines), len(self.stderr.lines), self.duration) ) - def parseoutcomes(self) -> Dict[str, int]: + def parseoutcomes(self) -> dict[str, int]: """Return a dictionary of outcome noun -> count from parsing the terminal output that the test process produced. @@ -568,7 +564,7 @@ def parseoutcomes(self) -> Dict[str, int]: return self.parse_summary_nouns(self.outlines) @classmethod - def parse_summary_nouns(cls, lines) -> Dict[str, int]: + def parse_summary_nouns(cls, lines) -> dict[str, int]: """Extract the nouns from a pytest terminal summary line. It always returns the plural noun for consistency:: @@ -599,8 +595,8 @@ def assert_outcomes( errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """ Assert that the specified outcomes appear with the respective @@ -626,7 +622,7 @@ def assert_outcomes( class SysModulesSnapshot: - def __init__(self, preserve: Optional[Callable[[str], bool]] = None) -> None: + def __init__(self, preserve: Callable[[str], bool] | None = None) -> None: self.__preserve = preserve self.__saved = dict(sys.modules) @@ -659,7 +655,7 @@ class Pytester: __test__ = False - CLOSE_STDIN: "Final" = NOTSET + CLOSE_STDIN: Final = NOTSET class TimeoutExpired(Exception): pass @@ -674,9 +670,9 @@ def __init__( ) -> None: check_ispytest(_ispytest) self._request = request - self._mod_collections: WeakKeyDictionary[ - Collector, List[Union[Item, Collector]] - ] = WeakKeyDictionary() + self._mod_collections: WeakKeyDictionary[Collector, list[Item | Collector]] = ( + WeakKeyDictionary() + ) if request.function: name: str = request.function.__name__ else: @@ -687,7 +683,7 @@ def __init__( #: :py:meth:`runpytest`. Initially this is an empty list but plugins can #: be added to the list. The type of items to add to the list depends on #: the method using them so refer to them for details. - self.plugins: List[Union[str, _PluggyPlugin]] = [] + self.plugins: list[str | _PluggyPlugin] = [] self._sys_path_snapshot = SysPathsSnapshot() self._sys_modules_snapshot = self.__take_sys_modules_snapshot() self._request.addfinalizer(self._finalize) @@ -755,8 +751,8 @@ def chdir(self) -> None: def _makefile( self, ext: str, - lines: Sequence[Union[Any, bytes]], - files: Dict[str, str], + lines: Sequence[Any | bytes], + files: dict[str, str], encoding: str = "utf-8", ) -> Path: items = list(files.items()) @@ -769,7 +765,7 @@ def _makefile( f"pytester.makefile expects a file extension, try .{ext} instead of {ext}" ) - def to_text(s: Union[Any, bytes]) -> str: + def to_text(s: Any | bytes) -> str: return s.decode(encoding) if isinstance(s, bytes) else str(s) if lines: @@ -892,9 +888,7 @@ def test_something(pytester): """ return self._makefile(".txt", args, kwargs) - def syspathinsert( - self, path: Optional[Union[str, "os.PathLike[str]"]] = None - ) -> None: + def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: """Prepend a directory to sys.path, defaults to :attr:`path`. This is undone automatically when this object dies at the end of each @@ -908,7 +902,7 @@ def syspathinsert( self._monkeypatch.syspath_prepend(str(path)) - def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + def mkdir(self, name: str | os.PathLike[str]) -> Path: """Create a new (sub)directory. :param name: @@ -920,7 +914,7 @@ def mkdir(self, name: Union[str, "os.PathLike[str]"]) -> Path: p.mkdir() return p - def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: + def mkpydir(self, name: str | os.PathLike[str]) -> Path: """Create a new python package. This creates a (sub)directory with an empty ``__init__.py`` file so it @@ -931,7 +925,7 @@ def mkpydir(self, name: Union[str, "os.PathLike[str]"]) -> Path: p.joinpath("__init__.py").touch() return p - def copy_example(self, name: Optional[str] = None) -> Path: + def copy_example(self, name: str | None = None) -> Path: """Copy file from project's directory into the testdir. :param name: @@ -976,9 +970,7 @@ def copy_example(self, name: Optional[str] = None) -> Path: f'example "{example_path}" is not found as a file or directory' ) - def getnode( - self, config: Config, arg: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getnode(self, config: Config, arg: str | os.PathLike[str]) -> Collector | Item: """Get the collection node of a file. :param config: @@ -997,9 +989,7 @@ def getnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def getpathnode( - self, path: Union[str, "os.PathLike[str]"] - ) -> Union[Collector, Item]: + def getpathnode(self, path: str | os.PathLike[str]) -> Collector | Item: """Return the collection node of a file. This is like :py:meth:`getnode` but uses :py:meth:`parseconfigure` to @@ -1019,7 +1009,7 @@ def getpathnode( config.hook.pytest_sessionfinish(session=session, exitstatus=ExitCode.OK) return res - def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: + def genitems(self, colitems: Sequence[Item | Collector]) -> list[Item]: """Generate all test items from a collection node. This recurses into the collection node and returns a list of all the @@ -1031,7 +1021,7 @@ def genitems(self, colitems: Sequence[Union[Item, Collector]]) -> List[Item]: The collected items. """ session = colitems[0].session - result: List[Item] = [] + result: list[Item] = [] for colitem in colitems: result.extend(session.genitems(colitem)) return result @@ -1065,7 +1055,7 @@ def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder: values = [*list(cmdlineargs), p] return self.inline_run(*values) - def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: + def inline_genitems(self, *args) -> tuple[list[Item], HookRecorder]: """Run ``pytest.main(['--collect-only'])`` in-process. Runs the :py:func:`pytest.main` function to run all of pytest inside @@ -1078,7 +1068,7 @@ def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]: def inline_run( self, - *args: Union[str, "os.PathLike[str]"], + *args: str | os.PathLike[str], plugins=(), no_reraise_ctrlc: bool = False, ) -> HookRecorder: @@ -1148,7 +1138,7 @@ class reprec: # type: ignore finalizer() def runpytest_inprocess( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any + self, *args: str | os.PathLike[str], **kwargs: Any ) -> RunResult: """Return result of running pytest in-process, providing a similar interface to what self.runpytest() provides.""" @@ -1191,9 +1181,7 @@ class reprec: # type: ignore res.reprec = reprec # type: ignore return res - def runpytest( - self, *args: Union[str, "os.PathLike[str]"], **kwargs: Any - ) -> RunResult: + def runpytest(self, *args: str | os.PathLike[str], **kwargs: Any) -> RunResult: """Run pytest inline or in a subprocess, depending on the command line option "--runpytest" and return a :py:class:`~pytest.RunResult`.""" new_args = self._ensure_basetemp(args) @@ -1204,8 +1192,8 @@ def runpytest( raise RuntimeError(f"Unrecognized runpytest option: {self._method}") def _ensure_basetemp( - self, args: Sequence[Union[str, "os.PathLike[str]"]] - ) -> List[Union[str, "os.PathLike[str]"]]: + self, args: Sequence[str | os.PathLike[str]] + ) -> list[str | os.PathLike[str]]: new_args = list(args) for x in new_args: if str(x).startswith("--basetemp"): @@ -1216,7 +1204,7 @@ def _ensure_basetemp( ) return new_args - def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfig(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest :class:`pytest.Config` instance from given commandline args. @@ -1240,7 +1228,7 @@ def parseconfig(self, *args: Union[str, "os.PathLike[str]"]) -> Config: self._request.addfinalizer(config._ensure_unconfigure) return config - def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: + def parseconfigure(self, *args: str | os.PathLike[str]) -> Config: """Return a new pytest configured Config instance. Returns a new :py:class:`pytest.Config` instance like @@ -1252,7 +1240,7 @@ def parseconfigure(self, *args: Union[str, "os.PathLike[str]"]) -> Config: return config def getitem( - self, source: Union[str, "os.PathLike[str]"], funcname: str = "test_func" + self, source: str | os.PathLike[str], funcname: str = "test_func" ) -> Item: """Return the test item for a test function. @@ -1273,7 +1261,7 @@ def getitem( return item assert 0, f"{funcname!r} item not found in module:\n{source}\nitems: {items}" - def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: + def getitems(self, source: str | os.PathLike[str]) -> list[Item]: """Return all test items collected from the module. Writes the source to a Python file and runs pytest's collection on @@ -1284,7 +1272,7 @@ def getitems(self, source: Union[str, "os.PathLike[str]"]) -> List[Item]: def getmodulecol( self, - source: Union[str, "os.PathLike[str]"], + source: str | os.PathLike[str], configargs=(), *, withinit: bool = False, @@ -1316,9 +1304,7 @@ def getmodulecol( self.config = config = self.parseconfigure(path, *configargs) return self.getnode(config, path) - def collect_by_name( - self, modcol: Collector, name: str - ) -> Optional[Union[Item, Collector]]: + def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | None: """Return the collection node for name from the module collection. Searches a module collection node for a collection node matching the @@ -1336,10 +1322,10 @@ def collect_by_name( def popen( self, - cmdargs: Sequence[Union[str, "os.PathLike[str]"]], - stdout: Union[int, TextIO] = subprocess.PIPE, - stderr: Union[int, TextIO] = subprocess.PIPE, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + cmdargs: Sequence[str | os.PathLike[str]], + stdout: int | TextIO = subprocess.PIPE, + stderr: int | TextIO = subprocess.PIPE, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, **kw, ): """Invoke :py:class:`subprocess.Popen`. @@ -1374,9 +1360,9 @@ def popen( def run( self, - *cmdargs: Union[str, "os.PathLike[str]"], - timeout: Optional[float] = None, - stdin: Union[NotSetType, bytes, IO[Any], int] = CLOSE_STDIN, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, ) -> RunResult: """Run a command with arguments. @@ -1462,10 +1448,10 @@ def _dump_lines(self, lines, fp): except UnicodeEncodeError: print(f"couldn't print to {fp} because of encoding") - def _getpytestargs(self) -> Tuple[str, ...]: + def _getpytestargs(self) -> tuple[str, ...]: return sys.executable, "-mpytest" - def runpython(self, script: "os.PathLike[str]") -> RunResult: + def runpython(self, script: os.PathLike[str]) -> RunResult: """Run a python script using sys.executable as interpreter.""" return self.run(sys.executable, script) @@ -1474,7 +1460,7 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: Union[str, "os.PathLike[str]"], timeout: Optional[float] = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1501,9 +1487,7 @@ def runpytest_subprocess( args = self._getpytestargs() + args return self.run(*args, timeout=timeout) - def spawn_pytest( - self, string: str, expect_timeout: float = 10.0 - ) -> "pexpect.spawn": + def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run pytest using pexpect. This makes sure to use the right pytest and sets up the temporary @@ -1517,7 +1501,7 @@ def spawn_pytest( cmd = f"{invoke} --basetemp={basetemp} {string}" return self.spawn(cmd, expect_timeout=expect_timeout) - def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn": + def spawn(self, cmd: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run a command using pexpect. The pexpect child is returned. @@ -1562,9 +1546,9 @@ class LineMatcher: ``text.splitlines()``. """ - def __init__(self, lines: List[str]) -> None: + def __init__(self, lines: list[str]) -> None: self.lines = lines - self._log_output: List[str] = [] + self._log_output: list[str] = [] def __str__(self) -> str: """Return the entire original text. @@ -1574,7 +1558,7 @@ def __str__(self) -> str: """ return "\n".join(self.lines) - def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]: + def _getlines(self, lines2: str | Sequence[str] | Source) -> Sequence[str]: if isinstance(lines2, str): lines2 = Source(lines2) if isinstance(lines2, Source): diff --git a/src/_pytest/pytester_assertions.py b/src/_pytest/pytester_assertions.py index d20c2bb5999..d543798f75a 100644 --- a/src/_pytest/pytester_assertions.py +++ b/src/_pytest/pytester_assertions.py @@ -4,21 +4,19 @@ # contain them itself, since it is imported by the `pytest` module, # hence cannot be subject to assertion rewriting, which requires a # module to not be already imported. -from typing import Dict -from typing import Optional +from __future__ import annotations + from typing import Sequence -from typing import Tuple -from typing import Union from _pytest.reports import CollectReport from _pytest.reports import TestReport def assertoutcome( - outcomes: Tuple[ + outcomes: tuple[ Sequence[TestReport], - Sequence[Union[CollectReport, TestReport]], - Sequence[Union[CollectReport, TestReport]], + Sequence[CollectReport | TestReport], + Sequence[CollectReport | TestReport], ], passed: int = 0, skipped: int = 0, @@ -37,15 +35,15 @@ def assertoutcome( def assert_outcomes( - outcomes: Dict[str, int], + outcomes: dict[str, int], passed: int = 0, skipped: int = 0, failed: int = 0, errors: int = 0, xpassed: int = 0, xfailed: int = 0, - warnings: Optional[int] = None, - deselected: Optional[int] = None, + warnings: int | None = None, + deselected: int | None = None, ) -> None: """Assert that the specified outcomes appear with the respective numbers (0 means it didn't occur) in the text output from a test run.""" diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 4887614de3b..2904c3a1e0f 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Python test discovery, setup and run of test functions.""" +from __future__ import annotations + import abc from collections import Counter from collections import defaultdict @@ -20,16 +22,11 @@ from typing import Generator from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import Mapping -from typing import Optional from typing import Pattern from typing import Sequence -from typing import Set -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import warnings import _pytest @@ -113,7 +110,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_generate_tests(metafunc: "Metafunc") -> None: +def pytest_generate_tests(metafunc: Metafunc) -> None: for marker in metafunc.definition.iter_markers(name="parametrize"): metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) @@ -153,7 +150,7 @@ def async_warn_and_skip(nodeid: str) -> None: @hookimpl(trylast=True) -def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: +def pytest_pyfunc_call(pyfuncitem: Function) -> object | None: testfunction = pyfuncitem.obj if is_async_function(testfunction): async_warn_and_skip(pyfuncitem.nodeid) @@ -174,7 +171,7 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]: def pytest_collect_directory( path: Path, parent: nodes.Collector -) -> Optional[nodes.Collector]: +) -> nodes.Collector | None: pkginit = path / "__init__.py" try: has_pkginit = pkginit.is_file() @@ -186,7 +183,7 @@ def pytest_collect_directory( return None -def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]: +def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Module | None: if file_path.suffix == ".py": if not parent.session.isinitpath(file_path): if not path_matches_patterns( @@ -206,14 +203,14 @@ def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool: return any(fnmatch_ex(pattern, path) for pattern in patterns) -def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module": +def pytest_pycollect_makemodule(module_path: Path, parent) -> Module: return Module.from_parent(parent, path=module_path) @hookimpl(trylast=True) def pytest_pycollect_makeitem( - collector: Union["Module", "Class"], name: str, obj: object -) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]: + collector: Module | Class, name: str, obj: object +) -> None | nodes.Item | nodes.Collector | list[nodes.Item | nodes.Collector]: assert isinstance(collector, (Class, Module)), type(collector) # Nothing was collected elsewhere, let's do it here. if safe_isclass(obj): @@ -320,7 +317,7 @@ def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> parts.reverse() return ".".join(parts) - def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]: + def reportinfo(self) -> tuple[os.PathLike[str] | str, int | None, str]: # XXX caching? path, lineno = getfslineno(self.obj) modpath = self.getmodpath() @@ -394,7 +391,7 @@ def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool: return True return False - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not getattr(self.obj, "__test__", True): return [] @@ -406,11 +403,11 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: # In each class, nodes should be definition ordered. # __dict__ is definition ordered. - seen: Set[str] = set() - dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = [] + seen: set[str] = set() + dict_values: list[list[nodes.Item | nodes.Collector]] = [] ihook = self.ihook for dic in dicts: - values: List[Union[nodes.Item, nodes.Collector]] = [] + values: list[nodes.Item | nodes.Collector] = [] # Note: seems like the dict can change during iteration - # be careful not to remove the list() without consideration. for name, obj in list(dic.items()): @@ -437,7 +434,7 @@ def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: result.extend(values) return result - def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]: + def _genfunctions(self, name: str, funcobj) -> Iterator[Function]: modulecol = self.getparent(Module) assert modulecol is not None module = modulecol.obj @@ -548,7 +545,7 @@ class Module(nodes.File, PyCollector): def _getobj(self): return importtestmodule(self.path, self.config) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: self._register_setup_module_fixture() self._register_setup_function_fixture() self.session._fixturemanager.parsefactories(self) @@ -642,13 +639,13 @@ class Package(nodes.Directory): def __init__( self, - fspath: Optional[LEGACY_PATH], + fspath: LEGACY_PATH | None, parent: nodes.Collector, # NOTE: following args are unused: config=None, session=None, nodeid=None, - path: Optional[Path] = None, + path: Path | None = None, ) -> None: # NOTE: Could be just the following, but kept as-is for compat. # super().__init__(self, fspath, parent=parent) @@ -680,13 +677,13 @@ def setup(self) -> None: func = partial(_call_with_optional_argument, teardown_module, init_mod) self.addfinalizer(func) - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: # Always collect __init__.py first. - def sort_key(entry: "os.DirEntry[str]") -> object: + def sort_key(entry: os.DirEntry[str]) -> object: return (entry.name != "__init__.py", entry.name) config = self.config - col: Optional[nodes.Collector] + col: nodes.Collector | None cols: Sequence[nodes.Collector] ihook = self.ihook for direntry in scandir(self.path, sort_key): @@ -720,12 +717,12 @@ def _call_with_optional_argument(func, arg) -> None: func() -def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]: +def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> object | None: """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. """ for name in names: - meth: Optional[object] = getattr(obj, name, None) + meth: object | None = getattr(obj, name, None) if meth is not None and fixtures.getfixturemarker(meth) is None: return meth return None @@ -735,14 +732,14 @@ class Class(PyCollector): """Collector for test methods (and nested classes) in a Python class.""" @classmethod - def from_parent(cls, parent, *, name, obj=None, **kw) -> "Self": # type: ignore[override] + def from_parent(cls, parent, *, name, obj=None, **kw) -> Self: # type: ignore[override] """The public constructor.""" return super().from_parent(name=name, parent=parent, **kw) def newinstance(self): return self.obj() - def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]: + def collect(self) -> Iterable[nodes.Item | nodes.Collector]: if not safe_getattr(self.obj, "__test__", True): return [] if hasinit(self.obj): @@ -872,21 +869,21 @@ class IdMaker: parametersets: Sequence[ParameterSet] # Optionally, a user-provided callable to make IDs for parameters in a # ParameterSet. - idfn: Optional[Callable[[Any], Optional[object]]] + idfn: Callable[[Any], object | None] | None # Optionally, explicit IDs for ParameterSets by index. - ids: Optional[Sequence[Optional[object]]] + ids: Sequence[object | None] | None # Optionally, the pytest config. # Used for controlling ASCII escaping, and for calling the # :hook:`pytest_make_parametrize_id` hook. - config: Optional[Config] + config: Config | None # Optionally, the ID of the node being parametrized. # Used only for clearer error messages. - nodeid: Optional[str] + nodeid: str | None # Optionally, the ID of the function being parametrized. # Used only for clearer error messages. - func_name: Optional[str] + func_name: str | None - def make_unique_parameterset_ids(self) -> List[str]: + def make_unique_parameterset_ids(self) -> list[str]: """Make a unique identifier for each ParameterSet, that may be used to identify the parametrization in a node ID. @@ -903,7 +900,7 @@ def make_unique_parameterset_ids(self) -> List[str]: # Record the number of occurrences of each ID. id_counts = Counter(resolved_ids) # Map the ID to its next suffix. - id_suffixes: Dict[str, int] = defaultdict(int) + id_suffixes: dict[str, int] = defaultdict(int) # Suffix non-unique IDs to make them unique. for index, id in enumerate(resolved_ids): if id_counts[id] > 1: @@ -950,9 +947,7 @@ def _idval(self, val: object, argname: str, idx: int) -> str: return idval return self._idval_from_argname(argname, idx) - def _idval_from_function( - self, val: object, argname: str, idx: int - ) -> Optional[str]: + def _idval_from_function(self, val: object, argname: str, idx: int) -> str | None: """Try to make an ID for a parameter in a ParameterSet using the user-provided id callable, if given.""" if self.idfn is None: @@ -968,17 +963,17 @@ def _idval_from_function( return None return self._idval_from_value(id) - def _idval_from_hook(self, val: object, argname: str) -> Optional[str]: + def _idval_from_hook(self, val: object, argname: str) -> str | None: """Try to make an ID for a parameter in a ParameterSet by calling the :hook:`pytest_make_parametrize_id` hook.""" if self.config: - id: Optional[str] = self.config.hook.pytest_make_parametrize_id( + id: str | None = self.config.hook.pytest_make_parametrize_id( config=self.config, val=val, argname=argname ) return id return None - def _idval_from_value(self, val: object) -> Optional[str]: + def _idval_from_value(self, val: object) -> str | None: """Try to make an ID for a parameter in a ParameterSet from its value, if the value type is supported.""" if isinstance(val, (str, bytes)): @@ -1036,15 +1031,15 @@ class CallSpec2: # arg name -> arg value which will be passed to a fixture or pseudo-fixture # of the same name. (indirect or direct parametrization respectively) - params: Dict[str, object] = dataclasses.field(default_factory=dict) + params: dict[str, object] = dataclasses.field(default_factory=dict) # arg name -> arg index. - indices: Dict[str, int] = dataclasses.field(default_factory=dict) + indices: dict[str, int] = dataclasses.field(default_factory=dict) # Used for sorting parametrized resources. _arg2scope: Mapping[str, Scope] = dataclasses.field(default_factory=dict) # Parts which will be added to the item's name in `[..]` separated by "-". _idlist: Sequence[str] = dataclasses.field(default_factory=tuple) # Marks which will be applied to the item. - marks: List[Mark] = dataclasses.field(default_factory=list) + marks: list[Mark] = dataclasses.field(default_factory=list) def setmulti( self, @@ -1052,10 +1047,10 @@ def setmulti( argnames: Iterable[str], valset: Iterable[object], id: str, - marks: Iterable[Union[Mark, MarkDecorator]], + marks: Iterable[Mark | MarkDecorator], scope: Scope, param_index: int, - ) -> "CallSpec2": + ) -> CallSpec2: params = self.params.copy() indices = self.indices.copy() arg2scope = dict(self._arg2scope) @@ -1103,7 +1098,7 @@ class Metafunc: def __init__( self, - definition: "FunctionDefinition", + definition: FunctionDefinition, fixtureinfo: fixtures.FuncFixtureInfo, config: Config, cls=None, @@ -1134,19 +1129,17 @@ def __init__( self._arg2fixturedefs = fixtureinfo.name2fixturedefs # Result of parametrize(). - self._calls: List[CallSpec2] = [] + self._calls: list[CallSpec2] = [] def parametrize( self, - argnames: Union[str, Sequence[str]], - argvalues: Iterable[Union[ParameterSet, Sequence[object], object]], - indirect: Union[bool, Sequence[str]] = False, - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ] = None, - scope: Optional[_ScopeName] = None, + argnames: str | Sequence[str], + argvalues: Iterable[ParameterSet | Sequence[object] | object], + indirect: bool | Sequence[str] = False, + ids: Iterable[object | None] | Callable[[Any], object | None] | None = None, + scope: _ScopeName | None = None, *, - _param_mark: Optional[Mark] = None, + _param_mark: Mark | None = None, ) -> None: """Add new invocations to the underlying test function using the list of argvalues for the given argnames. Parametrization is performed @@ -1275,7 +1268,7 @@ def parametrize( if node is None: name2pseudofixturedef = None else: - default: Dict[str, FixtureDef[Any]] = {} + default: dict[str, FixtureDef[Any]] = {} name2pseudofixturedef = node.stash.setdefault( name2pseudofixturedef_key, default ) @@ -1322,12 +1315,10 @@ def parametrize( def _resolve_parameter_set_ids( self, argnames: Sequence[str], - ids: Optional[ - Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]] - ], + ids: Iterable[object | None] | Callable[[Any], object | None] | None, parametersets: Sequence[ParameterSet], nodeid: str, - ) -> List[str]: + ) -> list[str]: """Resolve the actual ids for the given parameter sets. :param argnames: @@ -1365,10 +1356,10 @@ def _resolve_parameter_set_ids( def _validate_ids( self, - ids: Iterable[Optional[object]], + ids: Iterable[object | None], parametersets: Sequence[ParameterSet], func_name: str, - ) -> List[Optional[object]]: + ) -> list[object | None]: try: num_ids = len(ids) # type: ignore[arg-type] except TypeError: @@ -1388,8 +1379,8 @@ def _validate_ids( def _resolve_args_directness( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], - ) -> Dict[str, Literal["indirect", "direct"]]: + indirect: bool | Sequence[str], + ) -> dict[str, Literal["indirect", "direct"]]: """Resolve if each parametrized argument must be considered an indirect parameter to a fixture of the same name, or a direct parameter to the parametrized function, based on the ``indirect`` parameter of the @@ -1402,7 +1393,7 @@ def _resolve_args_directness( :returns A dict mapping each arg name to either "indirect" or "direct". """ - arg_directness: Dict[str, Literal["indirect", "direct"]] + arg_directness: dict[str, Literal["indirect", "direct"]] if isinstance(indirect, bool): arg_directness = dict.fromkeys( argnames, "indirect" if indirect else "direct" @@ -1427,7 +1418,7 @@ def _resolve_args_directness( def _validate_if_using_arg_names( self, argnames: Sequence[str], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> None: """Check if all argnames are being used, by default values, or directly/indirectly. @@ -1458,7 +1449,7 @@ def _validate_if_using_arg_names( def _find_parametrized_scope( argnames: Sequence[str], arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]], - indirect: Union[bool, Sequence[str]], + indirect: bool | Sequence[str], ) -> Scope: """Find the most appropriate scope for a parametrized call based on its arguments. @@ -1487,7 +1478,7 @@ def _find_parametrized_scope( return Scope.Function -def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str: +def _ascii_escaped_by_config(val: str | bytes, config: Config | None) -> str: if config is None: escape_option = False else: @@ -1536,13 +1527,13 @@ def __init__( self, name: str, parent, - config: Optional[Config] = None, - callspec: Optional[CallSpec2] = None, + config: Config | None = None, + callspec: CallSpec2 | None = None, callobj=NOTSET, - keywords: Optional[Mapping[str, Any]] = None, - session: Optional[Session] = None, - fixtureinfo: Optional[FuncFixtureInfo] = None, - originalname: Optional[str] = None, + keywords: Mapping[str, Any] | None = None, + session: Session | None = None, + fixtureinfo: FuncFixtureInfo | None = None, + originalname: str | None = None, ) -> None: super().__init__(name, parent, config=config, session=session) @@ -1585,12 +1576,12 @@ def __init__( # todo: determine sound type limitations @classmethod - def from_parent(cls, parent, **kw) -> "Self": + def from_parent(cls, parent, **kw) -> Self: """The public constructor.""" return super().from_parent(parent=parent, **kw) def _initrequest(self) -> None: - self.funcargs: Dict[str, object] = {} + self.funcargs: dict[str, object] = {} self._request = fixtures.TopRequest(self, _ispytest=True) @property @@ -1671,7 +1662,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback: def repr_failure( # type: ignore[override] self, excinfo: ExceptionInfo[BaseException], - ) -> Union[str, TerminalRepr]: + ) -> str | TerminalRepr: style = self.config.getoption("tbstyle", "auto") if style == "auto": style = "long" diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index bfc8dc33445..c1e851391b0 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from collections.abc import Collection from collections.abc import Sized from decimal import Decimal @@ -11,9 +13,7 @@ from typing import cast from typing import ContextManager from typing import final -from typing import List from typing import Mapping -from typing import Optional from typing import overload from typing import Pattern from typing import Sequence @@ -21,7 +21,6 @@ from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union import _pytest._code from _pytest.outcomes import fail @@ -33,12 +32,12 @@ def _compare_approx( full_object: object, - message_data: Sequence[Tuple[str, str, str]], + message_data: Sequence[tuple[str, str, str]], number_of_elements: int, different_ids: Sequence[object], max_abs_diff: float, max_rel_diff: float, -) -> List[str]: +) -> list[str]: message_list = list(message_data) message_list.insert(0, ("Index", "Obtained", "Expected")) max_sizes = [0, 0, 0] @@ -79,7 +78,7 @@ def __init__(self, expected, rel=None, abs=None, nan_ok: bool = False) -> None: def __repr__(self) -> str: raise NotImplementedError - def _repr_compare(self, other_side: Any) -> List[str]: + def _repr_compare(self, other_side: Any) -> list[str]: return [ "comparison failed", f"Obtained: {other_side}", @@ -103,7 +102,7 @@ def __bool__(self): def __ne__(self, actual) -> bool: return not (actual == self) - def _approx_scalar(self, x) -> "ApproxScalar": + def _approx_scalar(self, x) -> ApproxScalar: if isinstance(x, Decimal): return ApproxDecimal(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) return ApproxScalar(x, rel=self.rel, abs=self.abs, nan_ok=self.nan_ok) @@ -144,12 +143,12 @@ def __repr__(self) -> str: ) return f"approx({list_scalars!r})" - def _repr_compare(self, other_side: Union["ndarray", List[Any]]) -> List[str]: + def _repr_compare(self, other_side: ndarray | list[Any]) -> list[str]: import itertools import math def get_value_from_nested_list( - nested_list: List[Any], nd_index: Tuple[Any, ...] + nested_list: list[Any], nd_index: tuple[Any, ...] ) -> Any: """ Helper function to get the value out of a nested list, given an n-dimensional index. @@ -246,7 +245,7 @@ class ApproxMapping(ApproxBase): def __repr__(self) -> str: return f"approx({({k: self._approx_scalar(v) for k, v in self.expected.items()})!r})" - def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: + def _repr_compare(self, other_side: Mapping[object, float]) -> list[str]: import math approx_side_as_map = { @@ -321,7 +320,7 @@ def __repr__(self) -> str: seq_type = list return f"approx({seq_type(self._approx_scalar(x) for x in self.expected)!r})" - def _repr_compare(self, other_side: Sequence[float]) -> List[str]: + def _repr_compare(self, other_side: Sequence[float]) -> list[str]: import math if len(self.expected) != len(other_side): @@ -386,8 +385,8 @@ class ApproxScalar(ApproxBase): # Using Real should be better than this Union, but not possible yet: # https://github.com/python/typeshed/pull/3108 - DEFAULT_ABSOLUTE_TOLERANCE: Union[float, Decimal] = 1e-12 - DEFAULT_RELATIVE_TOLERANCE: Union[float, Decimal] = 1e-6 + DEFAULT_ABSOLUTE_TOLERANCE: float | Decimal = 1e-12 + DEFAULT_RELATIVE_TOLERANCE: float | Decimal = 1e-6 def __repr__(self) -> str: """Return a string communicating both the expected value and the @@ -717,7 +716,7 @@ def approx(expected, rel=None, abs=None, nan_ok: bool = False) -> ApproxBase: __tracebackhide__ = True if isinstance(expected, Decimal): - cls: Type[ApproxBase] = ApproxDecimal + cls: type[ApproxBase] = ApproxDecimal elif isinstance(expected, Mapping): cls = ApproxMapping elif _is_numpy_array(expected): @@ -750,7 +749,7 @@ def _is_numpy_array(obj: object) -> bool: return _as_numpy_array(obj) is not None -def _as_numpy_array(obj: object) -> Optional["ndarray"]: +def _as_numpy_array(obj: object) -> ndarray | None: """ Return an ndarray if the given object is implicitly convertible to ndarray, and numpy is already imported, otherwise None. @@ -776,15 +775,15 @@ def _as_numpy_array(obj: object) -> Optional["ndarray"]: @overload def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "RaisesContext[E]": ... + match: str | Pattern[str] | None = ..., +) -> RaisesContext[E]: ... @overload def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], func: Callable[..., Any], *args: Any, **kwargs: Any, @@ -792,8 +791,8 @@ def raises( def raises( - expected_exception: Union[Type[E], Tuple[Type[E], ...]], *args: Any, **kwargs: Any -) -> Union["RaisesContext[E]", _pytest._code.ExceptionInfo[E]]: + expected_exception: type[E] | tuple[type[E], ...], *args: Any, **kwargs: Any +) -> RaisesContext[E] | _pytest._code.ExceptionInfo[E]: r"""Assert that a code block/function call raises an exception type, or one of its subclasses. :param expected_exception: @@ -941,7 +940,7 @@ def raises( f"any special code to say 'this should never raise an exception'." ) if isinstance(expected_exception, type): - expected_exceptions: Tuple[Type[E], ...] = (expected_exception,) + expected_exceptions: tuple[type[E], ...] = (expected_exception,) else: expected_exceptions = expected_exception for exc in expected_exceptions: @@ -953,7 +952,7 @@ def raises( message = f"DID NOT RAISE {expected_exception}" if not args: - match: Optional[Union[str, Pattern[str]]] = kwargs.pop("match", None) + match: str | Pattern[str] | None = kwargs.pop("match", None) if kwargs: msg = "Unexpected keyword arguments passed to pytest.raises: " msg += ", ".join(sorted(kwargs)) @@ -979,14 +978,14 @@ def raises( class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, - expected_exception: Union[Type[E], Tuple[Type[E], ...]], + expected_exception: type[E] | tuple[type[E], ...], message: str, - match_expr: Optional[Union[str, Pattern[str]]] = None, + match_expr: str | Pattern[str] | None = None, ) -> None: self.expected_exception = expected_exception self.message = message self.match_expr = match_expr - self.excinfo: Optional[_pytest._code.ExceptionInfo[E]] = None + self.excinfo: _pytest._code.ExceptionInfo[E] | None = None def __enter__(self) -> _pytest._code.ExceptionInfo[E]: self.excinfo = _pytest._code.ExceptionInfo.for_later() @@ -994,9 +993,9 @@ def __enter__(self) -> _pytest._code.ExceptionInfo[E]: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> bool: __tracebackhide__ = True if exc_type is None: diff --git a/src/_pytest/python_path.py b/src/_pytest/python_path.py index cceabbca12a..6e33c8a39f2 100644 --- a/src/_pytest/python_path.py +++ b/src/_pytest/python_path.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys import pytest diff --git a/src/_pytest/recwarn.py b/src/_pytest/recwarn.py index 63e7a4bd6dc..3fc00d94736 100644 --- a/src/_pytest/recwarn.py +++ b/src/_pytest/recwarn.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Record warnings during test function execution.""" +from __future__ import annotations + from pprint import pformat import re from types import TracebackType @@ -9,14 +11,15 @@ from typing import final from typing import Generator from typing import Iterator -from typing import List -from typing import Optional from typing import overload from typing import Pattern -from typing import Tuple -from typing import Type +from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union + + +if TYPE_CHECKING: + from typing_extensions import Self + import warnings from _pytest.deprecated import check_ispytest @@ -29,7 +32,7 @@ @fixture -def recwarn() -> Generator["WarningsRecorder", None, None]: +def recwarn() -> Generator[WarningsRecorder, None, None]: """Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. See https://docs.pytest.org/en/latest/how-to/capture-warnings.html for information @@ -42,9 +45,7 @@ def recwarn() -> Generator["WarningsRecorder", None, None]: @overload -def deprecated_call( - *, match: Optional[Union[str, Pattern[str]]] = ... -) -> "WarningsRecorder": ... +def deprecated_call(*, match: str | Pattern[str] | None = ...) -> WarningsRecorder: ... @overload @@ -52,8 +53,8 @@ def deprecated_call(func: Callable[..., T], *args: Any, **kwargs: Any) -> T: ... def deprecated_call( - func: Optional[Callable[..., Any]] = None, *args: Any, **kwargs: Any -) -> Union["WarningsRecorder", Any]: + func: Callable[..., Any] | None = None, *args: Any, **kwargs: Any +) -> WarningsRecorder | Any: """Assert that code produces a ``DeprecationWarning`` or ``PendingDeprecationWarning`` or ``FutureWarning``. This function can be used as a context manager:: @@ -87,15 +88,15 @@ def deprecated_call( @overload def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = ..., + expected_warning: type[Warning] | tuple[type[Warning], ...] = ..., *, - match: Optional[Union[str, Pattern[str]]] = ..., -) -> "WarningsChecker": ... + match: str | Pattern[str] | None = ..., +) -> WarningsChecker: ... @overload def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]], + expected_warning: type[Warning] | tuple[type[Warning], ...], func: Callable[..., T], *args: Any, **kwargs: Any, @@ -103,11 +104,11 @@ def warns( def warns( - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, *args: Any, - match: Optional[Union[str, Pattern[str]]] = None, + match: str | Pattern[str] | None = None, **kwargs: Any, -) -> Union["WarningsChecker", Any]: +) -> WarningsChecker | Any: r"""Assert that code raises a particular class of warning. Specifically, the parameter ``expected_warning`` can be a warning class or tuple @@ -183,18 +184,18 @@ def __init__(self, *, _ispytest: bool = False) -> None: check_ispytest(_ispytest) super().__init__(record=True) self._entered = False - self._list: List[warnings.WarningMessage] = [] + self._list: list[warnings.WarningMessage] = [] @property - def list(self) -> List["warnings.WarningMessage"]: + def list(self) -> list[warnings.WarningMessage]: """The list of recorded warnings.""" return self._list - def __getitem__(self, i: int) -> "warnings.WarningMessage": + def __getitem__(self, i: int) -> warnings.WarningMessage: """Get a recorded warning by index.""" return self._list[i] - def __iter__(self) -> Iterator["warnings.WarningMessage"]: + def __iter__(self) -> Iterator[warnings.WarningMessage]: """Iterate through the recorded warnings.""" return iter(self._list) @@ -202,12 +203,12 @@ def __len__(self) -> int: """The number of recorded warnings.""" return len(self._list) - def pop(self, cls: Type[Warning] = Warning) -> "warnings.WarningMessage": + def pop(self, cls: type[Warning] = Warning) -> warnings.WarningMessage: """Pop the first recorded warning which is an instance of ``cls``, but not an instance of a child class of any other match. Raises ``AssertionError`` if there is no match. """ - best_idx: Optional[int] = None + best_idx: int | None = None for i, w in enumerate(self._list): if w.category == cls: return self._list.pop(i) # exact match, stop looking @@ -225,9 +226,7 @@ def clear(self) -> None: """Clear the list of recorded warnings.""" self._list[:] = [] - # Type ignored because it doesn't exactly warnings.catch_warnings.__enter__ - # -- it returns a List but we only emulate one. - def __enter__(self) -> "WarningsRecorder": # type: ignore + def __enter__(self) -> Self: if self._entered: __tracebackhide__ = True raise RuntimeError(f"Cannot enter {self!r} twice") @@ -240,9 +239,9 @@ def __enter__(self) -> "WarningsRecorder": # type: ignore def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: if not self._entered: __tracebackhide__ = True @@ -259,8 +258,8 @@ def __exit__( class WarningsChecker(WarningsRecorder): def __init__( self, - expected_warning: Union[Type[Warning], Tuple[Type[Warning], ...]] = Warning, - match_expr: Optional[Union[str, Pattern[str]]] = None, + expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning, + match_expr: str | Pattern[str] | None = None, *, _ispytest: bool = False, ) -> None: @@ -291,9 +290,9 @@ def matches(self, warning: warnings.WarningMessage) -> bool: def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: super().__exit__(exc_type, exc_val, exc_tb) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 2064183d0f7..2f39adbfa6f 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -1,24 +1,19 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses from io import StringIO import os from pprint import pprint from typing import Any from typing import cast -from typing import Dict from typing import final from typing import Iterable from typing import Iterator -from typing import List from typing import Literal from typing import Mapping from typing import NoReturn -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING -from typing import TypeVar -from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo @@ -39,6 +34,8 @@ if TYPE_CHECKING: + from typing_extensions import Self + from _pytest.runner import CallInfo @@ -54,16 +51,13 @@ def getworkerinfoline(node): return s -_R = TypeVar("_R", bound="BaseReport") - - class BaseReport: - when: Optional[str] - location: Optional[Tuple[str, Optional[int], str]] - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ] - sections: List[Tuple[str, str]] + when: str | None + location: tuple[str, int | None, str] | None + longrepr: ( + None | ExceptionInfo[BaseException] | tuple[str, int, str] | str | TerminalRepr + ) + sections: list[tuple[str, str]] nodeid: str outcome: Literal["passed", "failed", "skipped"] @@ -94,7 +88,7 @@ def toterminal(self, out: TerminalWriter) -> None: s = "" out.line(s) - def get_sections(self, prefix: str) -> Iterator[Tuple[str, str]]: + def get_sections(self, prefix: str) -> Iterator[tuple[str, str]]: for name, content in self.sections: if name.startswith(prefix): yield prefix, content @@ -176,7 +170,7 @@ def count_towards_summary(self) -> bool: return True @property - def head_line(self) -> Optional[str]: + def head_line(self) -> str | None: """**Experimental** The head line shown with longrepr output for this report, more commonly during traceback representation during failures:: @@ -202,7 +196,7 @@ def _get_verbose_word(self, config: Config): ) return verbose - def _to_json(self) -> Dict[str, Any]: + def _to_json(self) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -213,7 +207,7 @@ def _to_json(self) -> Dict[str, Any]: return _report_to_json(self) @classmethod - def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: + def _from_json(cls, reportdict: dict[str, object]) -> Self: """Create either a TestReport or CollectReport, depending on the calling class. It is the callers responsibility to know which class to pass here. @@ -227,7 +221,7 @@ def _from_json(cls: Type[_R], reportdict: Dict[str, object]) -> _R: def _report_unserialization_failure( - type_name: str, report_class: Type[BaseReport], reportdict + type_name: str, report_class: type[BaseReport], reportdict ) -> NoReturn: url = "https://github.com/pytest-dev/pytest/issues" stream = StringIO() @@ -256,18 +250,20 @@ class TestReport(BaseReport): def __init__( self, nodeid: str, - location: Tuple[str, Optional[int], str], + location: tuple[str, int | None, str], keywords: Mapping[str, Any], outcome: Literal["passed", "failed", "skipped"], - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], + longrepr: None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr, when: Literal["setup", "call", "teardown"], - sections: Iterable[Tuple[str, str]] = (), + sections: Iterable[tuple[str, str]] = (), duration: float = 0, start: float = 0, stop: float = 0, - user_properties: Optional[Iterable[Tuple[str, object]]] = None, + user_properties: Iterable[tuple[str, object]] | None = None, **extra, ) -> None: #: Normalized collection nodeid. @@ -278,7 +274,7 @@ def __init__( #: 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 + self.location: tuple[str, int | None, str] = location #: A name -> value dictionary containing all keywords and #: markers associated with a test invocation. @@ -317,7 +313,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} {self.nodeid!r} when={self.when!r} outcome={self.outcome!r}>" @classmethod - def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": + def from_item_and_call(cls, item: Item, call: CallInfo[None]) -> TestReport: """Create and fill a TestReport with standard item and call info. :param item: The item. @@ -334,13 +330,13 @@ def from_item_and_call(cls, item: Item, call: "CallInfo[None]") -> "TestReport": sections = [] if not call.excinfo: outcome: Literal["passed", "failed", "skipped"] = "passed" - longrepr: Union[ - None, - ExceptionInfo[BaseException], - Tuple[str, int, str], - str, - TerminalRepr, - ] = None + longrepr: ( + None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr + ) = None else: if not isinstance(excinfo, ExceptionInfo): outcome = "failed" @@ -394,12 +390,14 @@ class CollectReport(BaseReport): def __init__( self, nodeid: str, - outcome: "Literal['passed', 'failed', 'skipped']", - longrepr: Union[ - None, ExceptionInfo[BaseException], Tuple[str, int, str], str, TerminalRepr - ], - result: Optional[List[Union[Item, Collector]]], - sections: Iterable[Tuple[str, str]] = (), + outcome: Literal["passed", "failed", "skipped"], + longrepr: None + | ExceptionInfo[BaseException] + | tuple[str, int, str] + | str + | TerminalRepr, + result: list[Item | Collector] | None, + sections: Iterable[tuple[str, str]] = (), **extra, ) -> None: #: Normalized collection nodeid. @@ -425,7 +423,7 @@ def __init__( @property def location( # type:ignore[override] self, - ) -> Optional[Tuple[str, Optional[int], str]]: + ) -> tuple[str, int | None, str] | None: return (self.fspath, None, self.fspath) def __repr__(self) -> str: @@ -441,8 +439,8 @@ def toterminal(self, out: TerminalWriter) -> None: def pytest_report_to_serializable( - report: Union[CollectReport, TestReport], -) -> Optional[Dict[str, Any]]: + report: CollectReport | TestReport, +) -> dict[str, Any] | None: if isinstance(report, (TestReport, CollectReport)): data = report._to_json() data["$report_type"] = report.__class__.__name__ @@ -452,8 +450,8 @@ def pytest_report_to_serializable( def pytest_report_from_serializable( - data: Dict[str, Any], -) -> Optional[Union[CollectReport, TestReport]]: + data: dict[str, Any], +) -> CollectReport | TestReport | None: if "$report_type" in data: if data["$report_type"] == "TestReport": return TestReport._from_json(data) @@ -465,7 +463,7 @@ def pytest_report_from_serializable( return None -def _report_to_json(report: BaseReport) -> Dict[str, Any]: +def _report_to_json(report: BaseReport) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, suitable for serialization. @@ -473,8 +471,8 @@ def _report_to_json(report: BaseReport) -> Dict[str, Any]: """ def serialize_repr_entry( - entry: Union[ReprEntry, ReprEntryNative], - ) -> Dict[str, Any]: + entry: ReprEntry | ReprEntryNative, + ) -> dict[str, Any]: data = dataclasses.asdict(entry) for key, value in data.items(): if hasattr(value, "__dict__"): @@ -482,7 +480,7 @@ def serialize_repr_entry( entry_data = {"type": type(entry).__name__, "data": data} return entry_data - def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: + def serialize_repr_traceback(reprtraceback: ReprTraceback) -> dict[str, Any]: result = dataclasses.asdict(reprtraceback) result["reprentries"] = [ serialize_repr_entry(x) for x in reprtraceback.reprentries @@ -490,18 +488,18 @@ def serialize_repr_traceback(reprtraceback: ReprTraceback) -> Dict[str, Any]: return result def serialize_repr_crash( - reprcrash: Optional[ReprFileLocation], - ) -> Optional[Dict[str, Any]]: + reprcrash: ReprFileLocation | None, + ) -> dict[str, Any] | None: if reprcrash is not None: return dataclasses.asdict(reprcrash) else: return None - def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: + def serialize_exception_longrepr(rep: BaseReport) -> dict[str, Any]: assert rep.longrepr is not None # TODO: Investigate whether the duck typing is really necessary here. longrepr = cast(ExceptionRepr, rep.longrepr) - result: Dict[str, Any] = { + result: dict[str, Any] = { "reprcrash": serialize_repr_crash(longrepr.reprcrash), "reprtraceback": serialize_repr_traceback(longrepr.reprtraceback), "sections": longrepr.sections, @@ -538,7 +536,7 @@ def serialize_exception_longrepr(rep: BaseReport) -> Dict[str, Any]: return d -def _report_kwargs_from_json(reportdict: Dict[str, Any]) -> Dict[str, Any]: +def _report_kwargs_from_json(reportdict: dict[str, Any]) -> dict[str, Any]: """Return **kwargs that can be used to construct a TestReport or CollectReport instance. @@ -559,7 +557,7 @@ def deserialize_repr_entry(entry_data): if data["reprlocals"]: reprlocals = ReprLocals(data["reprlocals"]["lines"]) - reprentry: Union[ReprEntry, ReprEntryNative] = ReprEntry( + reprentry: ReprEntry | ReprEntryNative = ReprEntry( lines=data["lines"], reprfuncargs=reprfuncargs, reprlocals=reprlocals, @@ -578,7 +576,7 @@ def deserialize_repr_traceback(repr_traceback_dict): ] return ReprTraceback(**repr_traceback_dict) - def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): + def deserialize_repr_crash(repr_crash_dict: dict[str, Any] | None): if repr_crash_dict is not None: return ReprFileLocation(**repr_crash_dict) else: @@ -605,8 +603,8 @@ def deserialize_repr_crash(repr_crash_dict: Optional[Dict[str, Any]]): description, ) ) - exception_info: Union[ExceptionChainRepr, ReprExceptionInfo] = ( - ExceptionChainRepr(chain) + exception_info: ExceptionChainRepr | ReprExceptionInfo = ExceptionChainRepr( + chain ) else: exception_info = ReprExceptionInfo( diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e5af60e388b..bf30a7d2d27 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Basic collect and runtest protocol implementations.""" +from __future__ import annotations + import bdb import dataclasses import os @@ -8,17 +10,11 @@ import types from typing import Callable from typing import cast -from typing import Dict from typing import final from typing import Generic -from typing import List from typing import Literal -from typing import Optional -from typing import Tuple -from typing import Type from typing import TYPE_CHECKING from typing import TypeVar -from typing import Union from .reports import BaseReport from .reports import CollectErrorRepr @@ -72,7 +68,7 @@ def pytest_addoption(parser: Parser) -> None: ) -def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: +def pytest_terminal_summary(terminalreporter: TerminalReporter) -> None: durations = terminalreporter.config.option.durations durations_min = terminalreporter.config.option.durations_min verbose = terminalreporter.config.getvalue("verbose") @@ -103,15 +99,15 @@ def pytest_terminal_summary(terminalreporter: "TerminalReporter") -> None: tr.write_line(f"{rep.duration:02.2f}s {rep.when:<8} {rep.nodeid}") -def pytest_sessionstart(session: "Session") -> None: +def pytest_sessionstart(session: Session) -> None: session._setupstate = SetupState() -def pytest_sessionfinish(session: "Session") -> None: +def pytest_sessionfinish(session: Session) -> None: session._setupstate.teardown_exact(None) -def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: +def pytest_runtest_protocol(item: Item, nextitem: Item | None) -> bool: ihook = item.ihook ihook.pytest_runtest_logstart(nodeid=item.nodeid, location=item.location) runtestprotocol(item, nextitem=nextitem) @@ -120,8 +116,8 @@ def pytest_runtest_protocol(item: Item, nextitem: Optional[Item]) -> bool: def runtestprotocol( - item: Item, log: bool = True, nextitem: Optional[Item] = None -) -> List[TestReport]: + item: Item, log: bool = True, nextitem: Item | None = None +) -> list[TestReport]: hasrequest = hasattr(item, "_request") if hasrequest and not item._request: # type: ignore[attr-defined] # This only happens if the item is re-run, as is done by @@ -188,14 +184,14 @@ def pytest_runtest_call(item: Item) -> None: raise -def pytest_runtest_teardown(item: Item, nextitem: Optional[Item]) -> None: +def pytest_runtest_teardown(item: Item, nextitem: Item | None) -> None: _update_current_test_var(item, "teardown") item.session._setupstate.teardown_exact(nextitem) _update_current_test_var(item, None) def _update_current_test_var( - item: Item, when: Optional[Literal["setup", "call", "teardown"]] + item: Item, when: Literal["setup", "call", "teardown"] | None ) -> None: """Update :envvar:`PYTEST_CURRENT_TEST` to reflect the current item and stage. @@ -211,7 +207,7 @@ def _update_current_test_var( os.environ.pop(var_name) -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if report.when in ("setup", "teardown"): if report.failed: # category, shortletter, verbose-word @@ -239,7 +235,7 @@ def call_and_report( runtest_hook = ihook.pytest_runtest_teardown else: assert False, f"Unhandled runtest hook case: {when}" - reraise: Tuple[Type[BaseException], ...] = (Exit,) + reraise: tuple[type[BaseException], ...] = (Exit,) if not item.config.getoption("usepdb", False): reraise += (KeyboardInterrupt,) call = CallInfo.from_call( @@ -253,7 +249,7 @@ def call_and_report( return report -def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> bool: +def check_interactive_exception(call: CallInfo[object], report: BaseReport) -> bool: """Check whether the call raised an exception that should be reported as interactive.""" if call.excinfo is None: @@ -276,9 +272,9 @@ def check_interactive_exception(call: "CallInfo[object]", report: BaseReport) -> class CallInfo(Generic[TResult]): """Result/Exception info of a function invocation.""" - _result: Optional[TResult] + _result: TResult | None #: The captured exception of the call, if it raised. - excinfo: Optional[ExceptionInfo[BaseException]] + excinfo: ExceptionInfo[BaseException] | None #: The system time when the call started, in seconds since the epoch. start: float #: The system time when the call ended, in seconds since the epoch. @@ -290,8 +286,8 @@ class CallInfo(Generic[TResult]): def __init__( self, - result: Optional[TResult], - excinfo: Optional[ExceptionInfo[BaseException]], + result: TResult | None, + excinfo: ExceptionInfo[BaseException] | None, start: float, stop: float, duration: float, @@ -325,10 +321,8 @@ def from_call( cls, func: Callable[[], TResult], when: Literal["collect", "setup", "call", "teardown"], - reraise: Optional[ - Union[Type[BaseException], Tuple[Type[BaseException], ...]] - ] = None, - ) -> "CallInfo[TResult]": + reraise: type[BaseException] | tuple[type[BaseException], ...] | None = None, + ) -> CallInfo[TResult]: """Call func, wrapping the result in a CallInfo. :param func: @@ -343,7 +337,7 @@ def from_call( start = timing.time() precise_start = timing.perf_counter() try: - result: Optional[TResult] = func() + result: TResult | None = func() except BaseException: excinfo = ExceptionInfo.from_current() if reraise is not None and isinstance(excinfo.value, reraise): @@ -374,7 +368,7 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]) -> TestReport: def pytest_make_collect_report(collector: Collector) -> CollectReport: - def collect() -> List[Union[Item, Collector]]: + def collect() -> list[Item | Collector]: # Before collecting, if this is a Directory, load the conftests. # If a conftest import fails to load, it is considered a collection # error of the Directory collector. This is why it's done inside of the @@ -396,7 +390,7 @@ def collect() -> List[Union[Item, Collector]]: call = CallInfo.from_call( collect, "collect", reraise=(KeyboardInterrupt, SystemExit) ) - longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None + longrepr: None | tuple[str, int, str] | str | TerminalRepr = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" else: @@ -490,18 +484,13 @@ class SetupState: def __init__(self) -> None: # The stack is in the dict insertion order. - self.stack: Dict[ + self.stack: dict[ Node, - Tuple[ + tuple[ # Node's finalizers. - List[Callable[[], object]], + list[Callable[[], object]], # Node's exception and original traceback, if its setup raised. - Optional[ - Tuple[ - Union[OutcomeException, Exception], - Optional[types.TracebackType], - ] - ], + tuple[OutcomeException | Exception, types.TracebackType | None] | None, ], ] = {} @@ -536,7 +525,7 @@ def addfinalizer(self, finalizer: Callable[[], object], node: Node) -> None: assert node in self.stack, (node, self.stack) self.stack[node][0].append(finalizer) - def teardown_exact(self, nextitem: Optional[Item]) -> None: + def teardown_exact(self, nextitem: Item | None) -> None: """Teardown the current stack up until reaching nodes that nextitem also descends from. @@ -544,7 +533,7 @@ def teardown_exact(self, nextitem: Optional[Item]) -> None: stack is torn down. """ needed_collectors = nextitem and nextitem.listchain() or [] - exceptions: List[BaseException] = [] + exceptions: list[BaseException] = [] while self.stack: if list(self.stack.keys()) == needed_collectors[: len(self.stack)]: break diff --git a/src/_pytest/scope.py b/src/_pytest/scope.py index 2c6e23208f2..976a3ba242e 100644 --- a/src/_pytest/scope.py +++ b/src/_pytest/scope.py @@ -8,10 +8,11 @@ Also this makes the module light to import, as it should. """ +from __future__ import annotations + from enum import Enum from functools import total_ordering from typing import Literal -from typing import Optional _ScopeName = Literal["session", "package", "module", "class", "function"] @@ -38,29 +39,29 @@ class Scope(Enum): Package: _ScopeName = "package" Session: _ScopeName = "session" - def next_lower(self) -> "Scope": + def next_lower(self) -> Scope: """Return the next lower scope.""" index = _SCOPE_INDICES[self] if index == 0: raise ValueError(f"{self} is the lower-most scope") return _ALL_SCOPES[index - 1] - def next_higher(self) -> "Scope": + def next_higher(self) -> Scope: """Return the next higher scope.""" index = _SCOPE_INDICES[self] if index == len(_SCOPE_INDICES) - 1: raise ValueError(f"{self} is the upper-most scope") return _ALL_SCOPES[index + 1] - def __lt__(self, other: "Scope") -> bool: + def __lt__(self, other: Scope) -> bool: self_index = _SCOPE_INDICES[self] other_index = _SCOPE_INDICES[other] return self_index < other_index @classmethod def from_user( - cls, scope_name: _ScopeName, descr: str, where: Optional[str] = None - ) -> "Scope": + cls, scope_name: _ScopeName, descr: str, where: str | None = None + ) -> Scope: """ Given a scope name from the user, return the equivalent Scope enum. Should be used whenever we want to convert a user provided scope name to its enum object. diff --git a/src/_pytest/setuponly.py b/src/_pytest/setuponly.py index 39ab28b466b..de297f408d3 100644 --- a/src/_pytest/setuponly.py +++ b/src/_pytest/setuponly.py @@ -1,6 +1,6 @@ +from __future__ import annotations + from typing import Generator -from typing import Optional -from typing import Union from _pytest._io.saferepr import saferepr from _pytest.config import Config @@ -96,7 +96,7 @@ def _show_fixture_action( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setuponly: config.option.setupshow = True return None diff --git a/src/_pytest/setupplan.py b/src/_pytest/setupplan.py index 13c0df84ea1..4e124cce243 100644 --- a/src/_pytest/setupplan.py +++ b/src/_pytest/setupplan.py @@ -1,5 +1,4 @@ -from typing import Optional -from typing import Union +from __future__ import annotations from _pytest.config import Config from _pytest.config import ExitCode @@ -23,7 +22,7 @@ def pytest_addoption(parser: Parser) -> None: @pytest.hookimpl(tryfirst=True) def pytest_fixture_setup( fixturedef: FixtureDef[object], request: SubRequest -) -> Optional[object]: +) -> object | None: # Will return a dummy fixture if the setuponly option is provided. if request.config.option.setupplan: my_cache_key = fixturedef.cache_key(request) @@ -33,7 +32,7 @@ def pytest_fixture_setup( @pytest.hookimpl(tryfirst=True) -def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: +def pytest_cmdline_main(config: Config) -> int | ExitCode | None: if config.option.setupplan: config.option.setuponly = True config.option.setupshow = True diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index 54500b2851b..08fcb283eb2 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Support for skip/xfail functions and markers.""" +from __future__ import annotations + from collections.abc import Mapping import dataclasses import os @@ -9,8 +11,6 @@ import traceback from typing import Generator from typing import Optional -from typing import Tuple -from typing import Type from _pytest.config import Config from _pytest.config import hookimpl @@ -84,7 +84,7 @@ def nop(*args, **kwargs): ) -def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]: +def evaluate_condition(item: Item, mark: Mark, condition: object) -> tuple[bool, str]: """Evaluate a single skipif/xfail condition. If an old-style string condition is given, it is eval()'d, otherwise the @@ -164,7 +164,7 @@ class Skip: reason: str = "unconditional skip" -def evaluate_skip_marks(item: Item) -> Optional[Skip]: +def evaluate_skip_marks(item: Item) -> Skip | None: """Evaluate skip and skipif marks on item, returning Skip if triggered.""" for mark in item.iter_markers(name="skipif"): if "condition" not in mark.kwargs: @@ -201,10 +201,10 @@ class Xfail: reason: str run: bool strict: bool - raises: Optional[Tuple[Type[BaseException], ...]] + raises: tuple[type[BaseException], ...] | None -def evaluate_xfail_marks(item: Item) -> Optional[Xfail]: +def evaluate_xfail_marks(item: Item) -> Xfail | None: """Evaluate xfail marks on item, returning Xfail if triggered.""" for mark in item.iter_markers(name="xfail"): run = mark.kwargs.get("run", True) @@ -292,7 +292,7 @@ def pytest_runtest_makereport( return rep -def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str] | None: if hasattr(report, "wasxfail"): if report.skipped: return "xfailed", "x", "XFAIL" diff --git a/src/_pytest/stash.py b/src/_pytest/stash.py index a4b829fc6dd..6a9ff884e04 100644 --- a/src/_pytest/stash.py +++ b/src/_pytest/stash.py @@ -1,9 +1,9 @@ +from __future__ import annotations + from typing import Any from typing import cast -from typing import Dict from typing import Generic from typing import TypeVar -from typing import Union __all__ = ["Stash", "StashKey"] @@ -70,7 +70,7 @@ class Stash: __slots__ = ("_storage",) def __init__(self) -> None: - self._storage: Dict[StashKey[Any], object] = {} + self._storage: dict[StashKey[Any], object] = {} def __setitem__(self, key: StashKey[T], value: T) -> None: """Set a value for key.""" @@ -83,7 +83,7 @@ def __getitem__(self, key: StashKey[T]) -> T: """ return cast(T, self._storage[key]) - def get(self, key: StashKey[T], default: D) -> Union[T, D]: + def get(self, key: StashKey[T], default: D) -> T | D: """Get the value for key, or return default if the key wasn't set before.""" try: diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 1e3a09d9678..bd906ce63c1 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -1,5 +1,4 @@ -from typing import List -from typing import Optional +from __future__ import annotations from _pytest import nodes from _pytest.cacheprovider import Cache @@ -55,18 +54,18 @@ def pytest_sessionfinish(session: Session) -> None: class StepwisePlugin: def __init__(self, config: Config) -> None: self.config = config - self.session: Optional[Session] = None + self.session: Session | None = None self.report_status = "" assert config.cache is not None self.cache: Cache = config.cache - self.lastfailed: Optional[str] = self.cache.get(STEPWISE_CACHE_DIR, None) + self.lastfailed: str | None = self.cache.get(STEPWISE_CACHE_DIR, None) self.skip: bool = config.getoption("stepwise_skip") def pytest_sessionstart(self, session: Session) -> None: self.session = session def pytest_collection_modifyitems( - self, config: Config, items: List[nodes.Item] + self, config: Config, items: list[nodes.Item] ) -> None: if not self.lastfailed: self.report_status = "no previously failed tests, not skipping." @@ -113,7 +112,7 @@ def pytest_runtest_logreport(self, report: TestReport) -> None: if report.nodeid == self.lastfailed: self.lastfailed = None - def pytest_report_collectionfinish(self) -> Optional[str]: + def pytest_report_collectionfinish(self) -> str | None: if self.config.getoption("verbose") >= 0 and self.report_status: return f"stepwise: {self.report_status}" return None diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 2eef8d1f227..26c573f583e 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -4,6 +4,8 @@ This is a good source for looking at the various reporting hooks. """ +from __future__ import annotations + import argparse from collections import Counter import dataclasses @@ -17,20 +19,14 @@ from typing import Any from typing import Callable from typing import ClassVar -from typing import Dict from typing import final from typing import Generator -from typing import List from typing import Literal from typing import Mapping from typing import NamedTuple -from typing import Optional from typing import Sequence -from typing import Set from typing import TextIO -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union import warnings import pluggy @@ -90,7 +86,7 @@ def __init__( dest: str, default: object = None, required: bool = False, - help: Optional[str] = None, + help: str | None = None, ) -> None: super().__init__( option_strings=option_strings, @@ -105,8 +101,8 @@ def __call__( self, parser: argparse.ArgumentParser, namespace: argparse.Namespace, - values: Union[str, Sequence[object], None], - option_string: Optional[str] = None, + values: str | Sequence[object] | None, + option_string: str | None = None, ) -> None: new_count = getattr(namespace, self.dest, 0) - 1 setattr(namespace, self.dest, new_count) @@ -131,7 +127,7 @@ class TestShortLogReport(NamedTuple): category: str letter: str - word: Union[str, Tuple[str, Mapping[str, bool]]] + word: str | tuple[str, Mapping[str, bool]] def pytest_addoption(parser: Parser) -> None: @@ -311,7 +307,7 @@ def getreportopt(config: Config) -> str: @hookimpl(trylast=True) # after _pytest.runner -def pytest_report_teststatus(report: BaseReport) -> Tuple[str, str, str]: +def pytest_report_teststatus(report: BaseReport) -> tuple[str, str, str]: letter = "F" if report.passed: letter = "." @@ -339,12 +335,12 @@ class WarningReport: """ message: str - nodeid: Optional[str] = None - fslocation: Optional[Tuple[str, int]] = None + nodeid: str | None = None + fslocation: tuple[str, int] | None = None count_towards_summary: ClassVar = True - def get_location(self, config: Config) -> Optional[str]: + def get_location(self, config: Config) -> str | None: """Return the more user-friendly information about the location of a warning, or None.""" if self.nodeid: return self.nodeid @@ -357,31 +353,31 @@ def get_location(self, config: Config) -> Optional[str]: @final class TerminalReporter: - def __init__(self, config: Config, file: Optional[TextIO] = None) -> None: + def __init__(self, config: Config, file: TextIO | None = None) -> None: import _pytest.config self.config = config self._numcollected = 0 - self._session: Optional[Session] = None - self._showfspath: Optional[bool] = None + self._session: Session | None = None + self._showfspath: bool | None = None - self.stats: Dict[str, List[Any]] = {} - self._main_color: Optional[str] = None - self._known_types: Optional[List[str]] = None + self.stats: dict[str, list[Any]] = {} + self._main_color: str | None = None + self._known_types: list[str] | None = None self.startpath = config.invocation_params.dir if file is None: file = sys.stdout self._tw = _pytest.config.create_terminal_writer(config, file) self._screen_width = self._tw.fullwidth - self.currentfspath: Union[None, Path, str, int] = None + self.currentfspath: None | Path | str | int = None self.reportchars = getreportopt(config) self.hasmarkup = self._tw.hasmarkup self.isatty = file.isatty() - self._progress_nodeids_reported: Set[str] = set() + self._progress_nodeids_reported: set[str] = set() self._show_progress_info = self._determine_show_progress_info() - self._collect_report_last_write: Optional[float] = None - self._already_displayed_warnings: Optional[int] = None - self._keyboardinterrupt_memo: Optional[ExceptionRepr] = None + self._collect_report_last_write: float | None = None + self._already_displayed_warnings: int | None = None + self._keyboardinterrupt_memo: ExceptionRepr | None = None def _determine_show_progress_info(self) -> Literal["progress", "count", False]: """Return whether we should display progress information based on the current config.""" @@ -428,7 +424,7 @@ def showfspath(self) -> bool: return self._showfspath @showfspath.setter - def showfspath(self, value: Optional[bool]) -> None: + def showfspath(self, value: bool | None) -> None: self._showfspath = value @property @@ -492,7 +488,7 @@ def write(self, content: str, *, flush: bool = False, **markup: bool) -> None: def flush(self) -> None: self._tw.flush() - def write_line(self, line: Union[str, bytes], **markup: bool) -> None: + def write_line(self, line: str | bytes, **markup: bool) -> None: if not isinstance(line, str): line = str(line, errors="replace") self.ensure_newline() @@ -519,8 +515,8 @@ def rewrite(self, line: str, **markup: bool) -> None: def write_sep( self, sep: str, - title: Optional[str] = None, - fullwidth: Optional[int] = None, + title: str | None = None, + fullwidth: int | None = None, **markup: bool, ) -> None: self.ensure_newline() @@ -570,7 +566,7 @@ def pytest_deselected(self, items: Sequence[Item]) -> None: self._add_stats("deselected", items) def pytest_runtest_logstart( - self, nodeid: str, location: Tuple[str, Optional[int], str] + self, nodeid: str, location: tuple[str, int | None, str] ) -> None: fspath, lineno, domain = location # Ensure that the path is printed before the @@ -777,7 +773,7 @@ def report_collect(self, final: bool = False) -> None: self.write_line(line) @hookimpl(trylast=True) - def pytest_sessionstart(self, session: "Session") -> None: + def pytest_sessionstart(self, session: Session) -> None: self._session = session self._sessionstarttime = timing.time() if not self.showheader: @@ -804,7 +800,7 @@ def pytest_sessionstart(self, session: "Session") -> None: self._write_report_lines_from_hooks(lines) def _write_report_lines_from_hooks( - self, lines: Sequence[Union[str, Sequence[str]]] + self, lines: Sequence[str | Sequence[str]] ) -> None: for line_or_lines in reversed(lines): if isinstance(line_or_lines, str): @@ -813,14 +809,14 @@ def _write_report_lines_from_hooks( for line in line_or_lines: self.write_line(line) - def pytest_report_header(self, config: Config) -> List[str]: + def pytest_report_header(self, config: Config) -> list[str]: result = [f"rootdir: {config.rootpath}"] if config.inipath: result.append("configfile: " + bestrelpath(config.rootpath, config.inipath)) if config.args_source == Config.ArgsSource.TESTPATHS: - testpaths: List[str] = config.getini("testpaths") + testpaths: list[str] = config.getini("testpaths") result.append("testpaths: {}".format(", ".join(testpaths))) plugininfo = config.pluginmanager.list_plugin_distinfo() @@ -830,7 +826,7 @@ def pytest_report_header(self, config: Config) -> List[str]: ) return result - def pytest_collection_finish(self, session: "Session") -> None: + def pytest_collection_finish(self, session: Session) -> None: self.report_collect(True) lines = self.config.hook.pytest_report_collectionfinish( @@ -863,7 +859,7 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: for item in items: self._tw.line(item.nodeid) return - stack: List[Node] = [] + stack: list[Node] = [] indent = "" for item in items: needed_collectors = item.listchain()[1:] # strip root node @@ -884,7 +880,7 @@ def _printcollecteditems(self, items: Sequence[Item]) -> None: @hookimpl(wrapper=True) def pytest_sessionfinish( - self, session: "Session", exitstatus: Union[int, ExitCode] + self, session: Session, exitstatus: int | ExitCode ) -> Generator[None, None, None]: result = yield self._tw.line("") @@ -948,7 +944,7 @@ def _report_keyboardinterrupt(self) -> None: ) def _locationline( - self, nodeid: str, fspath: str, lineno: Optional[int], domain: str + self, nodeid: str, fspath: str, lineno: int | None, domain: str ) -> str: def mkrel(nodeid: str) -> str: line = self.config.cwd_relative_nodeid(nodeid) @@ -993,7 +989,7 @@ def getreports(self, name: str): def summary_warnings(self) -> None: if self.hasopt("w"): - all_warnings: Optional[List[WarningReport]] = self.stats.get("warnings") + all_warnings: list[WarningReport] | None = self.stats.get("warnings") if not all_warnings: return @@ -1006,11 +1002,11 @@ def summary_warnings(self) -> None: if not warning_reports: return - reports_grouped_by_message: Dict[str, List[WarningReport]] = {} + reports_grouped_by_message: dict[str, list[WarningReport]] = {} for wr in warning_reports: reports_grouped_by_message.setdefault(wr.message, []).append(wr) - def collapsed_location_report(reports: List[WarningReport]) -> str: + def collapsed_location_report(reports: list[WarningReport]) -> str: locations = [] for w in reports: location = w.get_location(self.config) @@ -1056,7 +1052,7 @@ def summary_passes_combined( ) -> None: if self.config.option.tbstyle != "no": if self.hasopt(needed_opt): - reports: List[TestReport] = self.getreports(which_reports) + reports: list[TestReport] = self.getreports(which_reports) if not reports: return self.write_sep("=", sep_title) @@ -1067,7 +1063,7 @@ def summary_passes_combined( self._outrep_summary(rep) self._handle_teardown_sections(rep.nodeid) - def _get_teardown_reports(self, nodeid: str) -> List[TestReport]: + def _get_teardown_reports(self, nodeid: str) -> list[TestReport]: reports = self.getreports("") return [ report @@ -1107,11 +1103,11 @@ def summary_failures_combined( sep_title: str, *, style: str, - needed_opt: Optional[str] = None, + needed_opt: str | None = None, ) -> None: if style != "no": if not needed_opt or self.hasopt(needed_opt): - reports: List[BaseReport] = self.getreports(which_reports) + reports: list[BaseReport] = self.getreports(which_reports) if not reports: return self.write_sep("=", sep_title) @@ -1128,7 +1124,7 @@ def summary_failures_combined( def summary_errors(self) -> None: if self.config.option.tbstyle != "no": - reports: List[BaseReport] = self.getreports("error") + reports: list[BaseReport] = self.getreports("error") if not reports: return self.write_sep("=", "ERRORS") @@ -1195,7 +1191,7 @@ def short_test_summary(self) -> None: if not self.reportchars: return - def show_simple(lines: List[str], *, stat: str) -> None: + def show_simple(lines: list[str], *, stat: str) -> None: failed = self.stats.get(stat, []) if not failed: return @@ -1207,7 +1203,7 @@ def show_simple(lines: List[str], *, stat: str) -> None: ) lines.append(line) - def show_xfailed(lines: List[str]) -> None: + def show_xfailed(lines: list[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: verbose_word = rep._get_verbose_word(self.config) @@ -1222,7 +1218,7 @@ def show_xfailed(lines: List[str]) -> None: lines.append(line) - def show_xpassed(lines: List[str]) -> None: + def show_xpassed(lines: list[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: verbose_word = rep._get_verbose_word(self.config) @@ -1236,8 +1232,8 @@ def show_xpassed(lines: List[str]) -> None: line += " - " + str(reason) lines.append(line) - def show_skipped(lines: List[str]) -> None: - skipped: List[CollectReport] = self.stats.get("skipped", []) + def show_skipped(lines: list[str]) -> None: + skipped: list[CollectReport] = self.stats.get("skipped", []) fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: return @@ -1256,7 +1252,7 @@ def show_skipped(lines: List[str]) -> None: else: lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) - REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = { + REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { "x": show_xfailed, "X": show_xpassed, "f": partial(show_simple, stat="failed"), @@ -1265,7 +1261,7 @@ def show_skipped(lines: List[str]) -> None: "E": partial(show_simple, stat="error"), } - lines: List[str] = [] + lines: list[str] = [] for char in self.reportchars: action = REPORTCHAR_ACTIONS.get(char) if action: # skipping e.g. "P" (passed with output) here. @@ -1276,7 +1272,7 @@ def show_skipped(lines: List[str]) -> None: for line in lines: self.write_line(line) - def _get_main_color(self) -> Tuple[str, List[str]]: + def _get_main_color(self) -> tuple[str, list[str]]: if self._main_color is None or self._known_types is None or self._is_last_item: self._set_main_color() assert self._main_color @@ -1296,7 +1292,7 @@ def _determine_main_color(self, unknown_type_seen: bool) -> str: return main_color def _set_main_color(self) -> None: - unknown_types: List[str] = [] + unknown_types: list[str] = [] for found_type in self.stats: if found_type: # setup/teardown reports have an empty key, ignore them if found_type not in KNOWN_TYPES and found_type not in unknown_types: @@ -1304,7 +1300,7 @@ def _set_main_color(self) -> None: self._known_types = list(KNOWN_TYPES) + unknown_types self._main_color = self._determine_main_color(bool(unknown_types)) - def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + def build_summary_stats_line(self) -> tuple[list[tuple[str, dict[str, bool]]], str]: """ Build the parts used in the last summary stats line. @@ -1329,14 +1325,14 @@ def build_summary_stats_line(self) -> Tuple[List[Tuple[str, Dict[str, bool]]], s else: return self._build_normal_summary_stats_line() - def _get_reports_to_display(self, key: str) -> List[Any]: + def _get_reports_to_display(self, key: str) -> list[Any]: """Get test/collection reports for the given status key, such as `passed` or `error`.""" reports = self.stats.get(key, []) return [x for x in reports if getattr(x, "count_towards_summary", True)] def _build_normal_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: main_color, known_types = self._get_main_color() parts = [] @@ -1355,7 +1351,7 @@ def _build_normal_summary_stats_line( def _build_collect_only_summary_stats_line( self, - ) -> Tuple[List[Tuple[str, Dict[str, bool]]], str]: + ) -> tuple[list[tuple[str, dict[str, bool]]], str]: deselected = len(self._get_reports_to_display("deselected")) errors = len(self._get_reports_to_display("error")) @@ -1396,7 +1392,7 @@ def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport return path -def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]: +def _format_trimmed(format: str, msg: str, available_width: int) -> str | None: """Format msg into format, ellipsizing it if doesn't fit in available_width. Returns None if even the ellipsis can't fit. @@ -1422,7 +1418,7 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str def _get_line_with_reprcrash_message( - config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool] + config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" verbose_word = rep._get_verbose_word(config) @@ -1452,8 +1448,8 @@ def _get_line_with_reprcrash_message( def _folded_skips( startpath: Path, skipped: Sequence[CollectReport], -) -> List[Tuple[int, str, Optional[int], str]]: - d: Dict[Tuple[str, Optional[int], str], List[CollectReport]] = {} +) -> list[tuple[int, str, int | None, str]]: + d: dict[tuple[str, int | None, str], list[CollectReport]] = {} for event in skipped: assert event.longrepr is not None assert isinstance(event.longrepr, tuple), (event, event.longrepr) @@ -1470,11 +1466,11 @@ def _folded_skips( and "skip" in keywords and "pytestmark" not in keywords ): - key: Tuple[str, Optional[int], str] = (fspath, None, reason) + key: tuple[str, int | None, str] = (fspath, None, reason) else: key = (fspath, lineno, reason) d.setdefault(key, []).append(event) - values: List[Tuple[int, str, Optional[int], str]] = [] + values: list[tuple[int, str, int | None, str]] = [] for key, events in d.items(): values.append((len(events), *key)) return values @@ -1489,7 +1485,7 @@ def _folded_skips( _color_for_type_default = "yellow" -def pluralize(count: int, noun: str) -> Tuple[int, str]: +def pluralize(count: int, noun: str) -> tuple[int, str]: # No need to pluralize words such as `failed` or `passed`. if noun not in ["error", "warnings", "test"]: return count, noun @@ -1502,8 +1498,8 @@ def pluralize(count: int, noun: str) -> Tuple[int, str]: return count, noun + "s" if count != 1 else noun -def _plugin_nameversions(plugininfo) -> List[str]: - values: List[str] = [] +def _plugin_nameversions(plugininfo) -> list[str]: + values: list[str] = [] for plugin, dist in plugininfo: # Gets us name and version! name = f"{dist.project_name}-{dist.version}" diff --git a/src/_pytest/threadexception.py b/src/_pytest/threadexception.py index 603a1777c92..d78c32c852f 100644 --- a/src/_pytest/threadexception.py +++ b/src/_pytest/threadexception.py @@ -1,16 +1,21 @@ +from __future__ import annotations + import threading import traceback from types import TracebackType from typing import Any from typing import Callable from typing import Generator -from typing import Optional -from typing import Type +from typing import TYPE_CHECKING import warnings import pytest +if TYPE_CHECKING: + from typing_extensions import Self + + # Copied from cpython/Lib/test/support/threading_helper.py, with modifications. class catch_threading_exception: """Context manager catching threading.Thread exception using @@ -34,22 +39,22 @@ class catch_threading_exception: """ def __init__(self) -> None: - self.args: Optional[threading.ExceptHookArgs] = None - self._old_hook: Optional[Callable[[threading.ExceptHookArgs], Any]] = None + self.args: threading.ExceptHookArgs | None = None + self._old_hook: Callable[[threading.ExceptHookArgs], Any] | None = None - def _hook(self, args: "threading.ExceptHookArgs") -> None: + def _hook(self, args: threading.ExceptHookArgs) -> None: self.args = args - def __enter__(self) -> "catch_threading_exception": + def __enter__(self) -> Self: self._old_hook = threading.excepthook threading.excepthook = self._hook return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: assert self._old_hook is not None threading.excepthook = self._old_hook diff --git a/src/_pytest/timing.py b/src/_pytest/timing.py index 0541dc8e0a1..b23c7f69e2d 100644 --- a/src/_pytest/timing.py +++ b/src/_pytest/timing.py @@ -6,6 +6,8 @@ Fixture "mock_timing" also interacts with this module for pytest's own tests. """ +from __future__ import annotations + from time import perf_counter from time import sleep from time import time diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index 72efed3e87a..91109ea69ef 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Support for providing temporary directories to test functions.""" +from __future__ import annotations + import dataclasses import os from pathlib import Path @@ -12,8 +14,6 @@ from typing import final from typing import Generator from typing import Literal -from typing import Optional -from typing import Union from .pathlib import cleanup_dead_symlinks from .pathlib import LOCK_TIMEOUT @@ -46,20 +46,20 @@ class TempPathFactory: The base directory can be configured using the ``--basetemp`` option. """ - _given_basetemp: Optional[Path] + _given_basetemp: Path | None # pluggy TagTracerSub, not currently exposed, so Any. _trace: Any - _basetemp: Optional[Path] + _basetemp: Path | None _retention_count: int _retention_policy: RetentionType def __init__( self, - given_basetemp: Optional[Path], + given_basetemp: Path | None, retention_count: int, retention_policy: RetentionType, trace, - basetemp: Optional[Path] = None, + basetemp: Path | None = None, *, _ispytest: bool = False, ) -> None: @@ -82,7 +82,7 @@ def from_config( config: Config, *, _ispytest: bool = False, - ) -> "TempPathFactory": + ) -> TempPathFactory: """Create a factory according to pytest configuration. :meta private: @@ -198,7 +198,7 @@ def getbasetemp(self) -> Path: return basetemp -def get_user() -> Optional[str]: +def get_user() -> str | None: """Return the current user name, or None if getuser() does not work in the current environment (see #1010).""" try: @@ -286,7 +286,7 @@ def tmp_path( del request.node.stash[tmppath_result_key] -def pytest_sessionfinish(session, exitstatus: Union[int, ExitCode]): +def pytest_sessionfinish(session, exitstatus: 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. """ @@ -317,6 +317,6 @@ def pytest_runtest_makereport( ) -> Generator[None, TestReport, TestReport]: rep = yield assert rep.when is not None - empty: Dict[str, bool] = {} + empty: dict[str, bool] = {} item.stash.setdefault(tmppath_result_key, empty)[rep.when] = rep.passed return rep diff --git a/src/_pytest/unittest.py b/src/_pytest/unittest.py index 0f201d0f3f5..aefea1333d9 100644 --- a/src/_pytest/unittest.py +++ b/src/_pytest/unittest.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Discover and run std-library "unittest" style tests.""" +from __future__ import annotations + import inspect import sys import traceback @@ -9,8 +11,6 @@ from typing import Callable from typing import Generator from typing import Iterable -from typing import List -from typing import Optional from typing import Tuple from typing import Type from typing import TYPE_CHECKING @@ -49,8 +49,8 @@ def pytest_pycollect_makeitem( - collector: Union[Module, Class], name: str, obj: object -) -> Optional["UnitTestCase"]: + collector: Module | Class, name: str, obj: object +) -> UnitTestCase | None: try: # Has unittest been imported? ut = sys.modules["unittest"] @@ -81,7 +81,7 @@ def newinstance(self): # it. return self.obj("runTest") - def collect(self) -> Iterable[Union[Item, Collector]]: + def collect(self) -> Iterable[Item | Collector]: from unittest import TestLoader cls = self.obj @@ -201,7 +201,7 @@ def unittest_setup_method_fixture( class TestCaseFunction(Function): nofuncargs = True - _excinfo: Optional[List[_pytest._code.ExceptionInfo[BaseException]]] = None + _excinfo: list[_pytest._code.ExceptionInfo[BaseException]] | None = None def _getinstance(self): assert isinstance(self.parent, UnitTestCase) @@ -215,7 +215,7 @@ def _testcase(self): def setup(self) -> None: # A bound method to be called during teardown() if set (see 'runtest()'). - self._explicit_tearDown: Optional[Callable[[], None]] = None + self._explicit_tearDown: Callable[[], None] | None = None super().setup() def teardown(self) -> None: @@ -226,7 +226,7 @@ def teardown(self) -> None: del self._instance super().teardown() - def startTest(self, testcase: "unittest.TestCase") -> None: + def startTest(self, testcase: unittest.TestCase) -> None: pass def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: @@ -265,7 +265,7 @@ def _addexcinfo(self, rawexcinfo: _SysExcInfoType) -> None: self.__dict__.setdefault("_excinfo", []).append(excinfo) def addError( - self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: try: if isinstance(rawexcinfo[1], exit.Exception): @@ -275,11 +275,11 @@ def addError( self._addexcinfo(rawexcinfo) def addFailure( - self, testcase: "unittest.TestCase", rawexcinfo: _SysExcInfoType + self, testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType ) -> None: self._addexcinfo(rawexcinfo) - def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: + def addSkip(self, testcase: unittest.TestCase, reason: str) -> None: try: raise pytest.skip.Exception(reason, _use_item_location=True) except skip.Exception: @@ -287,7 +287,7 @@ def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None: def addExpectedFailure( self, - testcase: "unittest.TestCase", + testcase: unittest.TestCase, rawexcinfo: _SysExcInfoType, reason: str = "", ) -> None: @@ -298,8 +298,8 @@ def addExpectedFailure( def addUnexpectedSuccess( self, - testcase: "unittest.TestCase", - reason: Optional["twisted.trial.unittest.Todo"] = None, + testcase: unittest.TestCase, + reason: twisted.trial.unittest.Todo | None = None, ) -> None: msg = "Unexpected success" if reason: @@ -310,13 +310,13 @@ def addUnexpectedSuccess( except fail.Exception: self._addexcinfo(sys.exc_info()) - def addSuccess(self, testcase: "unittest.TestCase") -> None: + def addSuccess(self, testcase: unittest.TestCase) -> None: pass - def stopTest(self, testcase: "unittest.TestCase") -> None: + def stopTest(self, testcase: unittest.TestCase) -> None: pass - def addDuration(self, testcase: "unittest.TestCase", elapsed: float) -> None: + def addDuration(self, testcase: unittest.TestCase, elapsed: float) -> None: pass def runtest(self) -> None: diff --git a/src/_pytest/unraisableexception.py b/src/_pytest/unraisableexception.py index 50b121e8811..c191703a3de 100644 --- a/src/_pytest/unraisableexception.py +++ b/src/_pytest/unraisableexception.py @@ -1,16 +1,21 @@ +from __future__ import annotations + import sys import traceback from types import TracebackType from typing import Any from typing import Callable from typing import Generator -from typing import Optional -from typing import Type +from typing import TYPE_CHECKING import warnings import pytest +if TYPE_CHECKING: + from typing_extensions import Self + + # Copied from cpython/Lib/test/support/__init__.py, with modifications. class catch_unraisable_exception: """Context manager catching unraisable exception using sys.unraisablehook. @@ -34,24 +39,24 @@ class catch_unraisable_exception: """ def __init__(self) -> None: - self.unraisable: Optional[sys.UnraisableHookArgs] = None - self._old_hook: Optional[Callable[[sys.UnraisableHookArgs], Any]] = None + self.unraisable: sys.UnraisableHookArgs | None = None + self._old_hook: Callable[[sys.UnraisableHookArgs], Any] | None = 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 # finalized. Storing unraisable.exc_value creates a reference cycle. self.unraisable = unraisable - def __enter__(self) -> "catch_unraisable_exception": + def __enter__(self) -> Self: self._old_hook = sys.unraisablehook sys.unraisablehook = self._hook return self def __exit__( self, - exc_type: Optional[Type[BaseException]], - exc_val: Optional[BaseException], - exc_tb: Optional[TracebackType], + exc_type: type[BaseException] | None, + exc_val: BaseException | None, + exc_tb: TracebackType | None, ) -> None: assert self._old_hook is not None sys.unraisablehook = self._old_hook diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index a5884f29582..4ab14e48c92 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -1,10 +1,11 @@ +from __future__ import annotations + import dataclasses import inspect from types import FunctionType from typing import Any from typing import final from typing import Generic -from typing import Type from typing import TypeVar import warnings @@ -72,7 +73,7 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): __module__ = "pytest" @classmethod - def simple(cls, apiname: str) -> "PytestExperimentalApiWarning": + def simple(cls, apiname: str) -> PytestExperimentalApiWarning: return cls(f"{apiname} is an experimental api that may change over time") @@ -132,7 +133,7 @@ class UnformattedWarning(Generic[_W]): as opposed to a direct message. """ - category: Type["_W"] + category: type[_W] template: str def format(self, **kwargs: Any) -> _W: diff --git a/src/_pytest/warnings.py b/src/_pytest/warnings.py index 22590892f8d..5c59e55c5db 100644 --- a/src/_pytest/warnings.py +++ b/src/_pytest/warnings.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from contextlib import contextmanager import sys from typing import Generator from typing import Literal -from typing import Optional import warnings from _pytest.config import apply_warning_filters @@ -28,7 +29,7 @@ def catch_warnings_for_item( config: Config, ihook, when: Literal["config", "collect", "runtest"], - item: Optional[Item], + item: Item | None, ) -> Generator[None, None, None]: """Context manager that catches warnings generated in the contained execution block. @@ -142,7 +143,7 @@ def pytest_sessionfinish(session: Session) -> Generator[None, None, None]: @pytest.hookimpl(wrapper=True) def pytest_load_initial_conftests( - early_config: "Config", + early_config: Config, ) -> Generator[None, None, None]: with catch_warnings_for_item( config=early_config, ihook=early_config.hook, when="config", item=None diff --git a/src/py.py b/src/py.py index d1c39d203a8..5c661e66c1f 100644 --- a/src/py.py +++ b/src/py.py @@ -1,6 +1,8 @@ # shim for pylib going away # if pylib is installed this file will get skipped # (`py/__init__.py` has higher precedence) +from __future__ import annotations + import sys import _pytest._py.error as error diff --git a/src/pytest/__init__.py b/src/pytest/__init__.py index c6b6de827e9..90abcdab036 100644 --- a/src/pytest/__init__.py +++ b/src/pytest/__init__.py @@ -1,6 +1,8 @@ # PYTHON_ARGCOMPLETE_OK """pytest: unit and functional testing with Python.""" +from __future__ import annotations + from _pytest import __version__ from _pytest import version_tuple from _pytest._code import ExceptionInfo diff --git a/src/pytest/__main__.py b/src/pytest/__main__.py index e4cb67d5dd5..cccab5d57b8 100644 --- a/src/pytest/__main__.py +++ b/src/pytest/__main__.py @@ -1,5 +1,7 @@ """The pytest entry point.""" +from __future__ import annotations + import pytest diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py index 0215aba9695..4a95e2d0cd9 100644 --- a/testing/_py/test_local.py +++ b/testing/_py/test_local.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import contextlib import multiprocessing import os diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 0f3b35036d7..01d911e8ca4 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import importlib.metadata import os diff --git a/testing/code/test_code.py b/testing/code/test_code.py index 57ab4cdfddb..7ae5ad46100 100644 --- a/testing/code/test_code.py +++ b/testing/code/test_code.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import re import sys from types import FrameType diff --git a/testing/conftest.py b/testing/conftest.py index b7e2d6111af..24e5d183094 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import re import sys from typing import Generator -from typing import List from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -190,22 +191,22 @@ class ColorMapping: NO_COLORS = {k: "" for k in COLORS.keys()} @classmethod - def format(cls, lines: List[str]) -> List[str]: + def format(cls, lines: list[str]) -> list[str]: """Straightforward replacement of color names to their ASCII codes.""" return [line.format(**cls.COLORS) for line in lines] @classmethod - def format_for_fnmatch(cls, lines: List[str]) -> List[str]: + def format_for_fnmatch(cls, lines: list[str]) -> list[str]: """Replace color names for use with LineMatcher.fnmatch_lines""" return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines] @classmethod - def format_for_rematch(cls, lines: List[str]) -> List[str]: + def format_for_rematch(cls, lines: list[str]) -> list[str]: """Replace color names for use with LineMatcher.re_match_lines""" return [line.format(**cls.RE_COLORS) for line in lines] @classmethod - def strip_colors(cls, lines: List[str]) -> List[str]: + def strip_colors(cls, lines: list[str]) -> list[str]: """Entirely remove every color code""" return [line.format(**cls.NO_COLORS) for line in lines] diff --git a/testing/deprecated_test.py b/testing/deprecated_test.py index 9e83a49d554..5d0e69c58c1 100644 --- a/testing/deprecated_test.py +++ b/testing/deprecated_test.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path import re import sys diff --git a/testing/example_scripts/acceptance/fixture_mock_integration.py b/testing/example_scripts/acceptance/fixture_mock_integration.py index d802a7f8728..e612ae01e66 100644 --- a/testing/example_scripts/acceptance/fixture_mock_integration.py +++ b/testing/example_scripts/acceptance/fixture_mock_integration.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Reproduces issue #3774""" +from __future__ import annotations + from unittest import mock import pytest diff --git a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py index 58c41942d1c..5e30bb15883 100644 --- a/testing/example_scripts/collect/collect_init_tests/tests/__init__.py +++ b/testing/example_scripts/collect/collect_init_tests/tests/__init__.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_init(): pass diff --git a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py index d88c001c2cc..3cb8f1be095 100644 --- a/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py +++ b/testing/example_scripts/collect/collect_init_tests/tests/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/collect/package_infinite_recursion/conftest.py b/testing/example_scripts/collect/package_infinite_recursion/conftest.py index bba5db8b2fd..c2d2b918874 100644 --- a/testing/example_scripts/collect/package_infinite_recursion/conftest.py +++ b/testing/example_scripts/collect/package_infinite_recursion/conftest.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def pytest_ignore_collect(collection_path): return False diff --git a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py index 2809d0cc689..38c51e586fc 100644 --- a/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py +++ b/testing/example_scripts/collect/package_infinite_recursion/tests/test_basic.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test(): pass 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 58c41942d1c..5e30bb15883 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 @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + 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 d88c001c2cc..3cb8f1be095 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,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/conftest.py b/testing/example_scripts/config/collect_pytest_prefix/conftest.py index 2da4ffe2fed..5e0ab54411b 100644 --- a/testing/example_scripts/config/collect_pytest_prefix/conftest.py +++ b/testing/example_scripts/config/collect_pytest_prefix/conftest.py @@ -1,2 +1,5 @@ +from __future__ import annotations + + class pytest_something: pass diff --git a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py index d88c001c2cc..3cb8f1be095 100644 --- a/testing/example_scripts/config/collect_pytest_prefix/test_foo.py +++ b/testing/example_scripts/config/collect_pytest_prefix/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_foo(): pass diff --git a/testing/example_scripts/conftest_usageerror/conftest.py b/testing/example_scripts/conftest_usageerror/conftest.py index 64bbeefac1d..a6690bdc303 100644 --- a/testing/example_scripts/conftest_usageerror/conftest.py +++ b/testing/example_scripts/conftest_usageerror/conftest.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def pytest_configure(config): import pytest diff --git a/testing/example_scripts/customdirectory/conftest.py b/testing/example_scripts/customdirectory/conftest.py index fe1c743a686..4718d7d5be3 100644 --- a/testing/example_scripts/customdirectory/conftest.py +++ b/testing/example_scripts/customdirectory/conftest.py @@ -1,5 +1,7 @@ # mypy: allow-untyped-defs # content of conftest.py +from __future__ import annotations + import json import pytest diff --git a/testing/example_scripts/customdirectory/tests/test_first.py b/testing/example_scripts/customdirectory/tests/test_first.py index 890ca3dea38..06f40ca4733 100644 --- a/testing/example_scripts/customdirectory/tests/test_first.py +++ b/testing/example_scripts/customdirectory/tests/test_first.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs # content of test_first.py +from __future__ import annotations + + def test_1(): pass diff --git a/testing/example_scripts/customdirectory/tests/test_second.py b/testing/example_scripts/customdirectory/tests/test_second.py index 42108d5da84..79bcc099e65 100644 --- a/testing/example_scripts/customdirectory/tests/test_second.py +++ b/testing/example_scripts/customdirectory/tests/test_second.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs # content of test_second.py +from __future__ import annotations + + def test_2(): pass diff --git a/testing/example_scripts/customdirectory/tests/test_third.py b/testing/example_scripts/customdirectory/tests/test_third.py index ede0f3e6025..5af476ad44d 100644 --- a/testing/example_scripts/customdirectory/tests/test_third.py +++ b/testing/example_scripts/customdirectory/tests/test_third.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs # content of test_third.py +from __future__ import annotations + + def test_3(): pass diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_dataclasses.py index d96c90a91bd..18180b99f2d 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py index 7479c66c1be..0dcc7ab2802 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_field_comparison_off.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py index 4737ef904e0..4985c69ff30 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_verbose.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py index e026fe3d192..b787cb39ee2 100644 --- a/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py +++ b/testing/example_scripts/dataclasses/test_compare_dataclasses_with_custom_eq.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/dataclasses/test_compare_initvar.py b/testing/example_scripts/dataclasses/test_compare_initvar.py index d687fc22530..fc589e1fde4 100644 --- a/testing/example_scripts/dataclasses/test_compare_initvar.py +++ b/testing/example_scripts/dataclasses/test_compare_initvar.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from dataclasses import dataclass from dataclasses import InitVar diff --git a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py index 801aa0a732e..885edd7d9d7 100644 --- a/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_recursive_dataclasses.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from dataclasses import dataclass diff --git a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py index 0a4820c69ba..b45a6772c59 100644 --- a/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py +++ b/testing/example_scripts/dataclasses/test_compare_two_different_dataclasses.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from dataclasses import dataclass from dataclasses import field diff --git a/testing/example_scripts/doctest/main_py/__main__.py b/testing/example_scripts/doctest/main_py/__main__.py index c8a124f5416..3a0f6bed1d6 100644 --- a/testing/example_scripts/doctest/main_py/__main__.py +++ b/testing/example_scripts/doctest/main_py/__main__.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_this_is_ignored(): assert True diff --git a/testing/example_scripts/doctest/main_py/test_normal_module.py b/testing/example_scripts/doctest/main_py/test_normal_module.py index 26a4d90bc89..8c150da5c02 100644 --- a/testing/example_scripts/doctest/main_py/test_normal_module.py +++ b/testing/example_scripts/doctest/main_py/test_normal_module.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_doc(): """ >>> 10 > 5 diff --git a/testing/example_scripts/fixtures/custom_item/conftest.py b/testing/example_scripts/fixtures/custom_item/conftest.py index fe1ae620aa6..274ab97d01b 100644 --- a/testing/example_scripts/fixtures/custom_item/conftest.py +++ b/testing/example_scripts/fixtures/custom_item/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py index 2809d0cc689..38c51e586fc 100644 --- a/testing/example_scripts/fixtures/custom_item/foo/test_foo.py +++ b/testing/example_scripts/fixtures/custom_item/foo/test_foo.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test(): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py index 3a5d3ac33fe..94eaa3e0796 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py index d0c4bdbdfd9..cb3f9fbf469 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub1/test_in_sub1.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_1(arg1): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py index a1f3b2d58b9..112d1e05f27 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py index 45e9744786a..3dea97f544c 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_conftest_funcargs_only_available_in_subdir/sub2/test_in_sub2.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_2(arg2): pass diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py index 84e5256f070..d90961ae3c4 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_detect_recursive_dependency_error.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py index 7f1769beb9b..b4fcc17bfc7 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py index ad26fdd8cdc..b933b70edf3 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py index 9ee74a47186..d31ab971f2b 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_conftest/pkg/test_spam.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_spam(spam): assert spam == "spamspam" diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py index 7f1769beb9b..b4fcc17bfc7 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py index fa688f0a844..2d6d7faef61 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_conftest_module/test_extend_fixture_conftest_module.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py index f78a57c322b..45e5deaafea 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_extend_fixture_module_class.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py index 12e0e3e91d4..1c7a710cd0c 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_basic.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py index 8b6e8697e06..96f0cacfafd 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_classlevel.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py index 40587cf2bd1..b78ca04b3ab 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookup_modulelevel.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py index 0cc8446d8ee..0dd782e4285 100644 --- a/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py +++ b/testing/example_scripts/fixtures/fill_fixtures/test_funcarg_lookupfails.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/test_fixture_named_request.py b/testing/example_scripts/fixtures/test_fixture_named_request.py index a2ab7ee330d..db88bcdabb9 100644 --- a/testing/example_scripts/fixtures/test_fixture_named_request.py +++ b/testing/example_scripts/fixtures/test_fixture_named_request.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py index 0f316f0e449..0559905cea4 100644 --- a/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py +++ b/testing/example_scripts/fixtures/test_getfixturevalue_dynamic.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py index bde5c0711ac..2e88c5ad5a9 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/conftest.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/conftest.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py index dd18e1741f0..b10f874e78d 100644 --- a/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py +++ b/testing/example_scripts/issue88_initial_file_multinodes/test_hello.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_hello(): pass diff --git a/testing/example_scripts/issue_519.py b/testing/example_scripts/issue_519.py index 39766164490..138c07e95be 100644 --- a/testing/example_scripts/issue_519.py +++ b/testing/example_scripts/issue_519.py @@ -1,7 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pprint -from typing import List -from typing import Tuple import pytest @@ -16,7 +16,7 @@ def pytest_generate_tests(metafunc): @pytest.fixture(scope="session") def checked_order(): - order: List[Tuple[str, str, str]] = [] + order: list[tuple[str, str, str]] = [] yield order pprint.pprint(order) diff --git a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py index d95ad0a83d9..c98e58316eb 100644 --- a/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py +++ b/testing/example_scripts/marks/marks_considered_keywords/test_marks_as_keywords.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py index 17085e50b54..3b580aa341a 100644 --- a/testing/example_scripts/perf_examples/collect_stats/generate_folders.py +++ b/testing/example_scripts/perf_examples/collect_stats/generate_folders.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse import pathlib diff --git a/testing/example_scripts/perf_examples/collect_stats/template_test.py b/testing/example_scripts/perf_examples/collect_stats/template_test.py index f50eb65525c..d9449485db6 100644 --- a/testing/example_scripts/perf_examples/collect_stats/template_test.py +++ b/testing/example_scripts/perf_examples/collect_stats/template_test.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_x(): pass diff --git a/testing/example_scripts/tmpdir/tmp_path_fixture.py b/testing/example_scripts/tmpdir/tmp_path_fixture.py index 4aa35faa0b6..503ead473e7 100644 --- a/testing/example_scripts/tmpdir/tmp_path_fixture.py +++ b/testing/example_scripts/tmpdir/tmp_path_fixture.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py index d66b66df5b7..733202915e4 100644 --- a/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py +++ b/testing/example_scripts/unittest/test_parametrized_fixture_error_message.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import unittest import pytest diff --git a/testing/example_scripts/unittest/test_setup_skip.py b/testing/example_scripts/unittest/test_setup_skip.py index 7550a097576..52ff96ea8be 100644 --- a/testing/example_scripts/unittest/test_setup_skip.py +++ b/testing/example_scripts/unittest/test_setup_skip.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Skipping an entire subclass with unittest.skip() should *not* call setUp from a base class.""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_setup_skip_class.py b/testing/example_scripts/unittest/test_setup_skip_class.py index 48f7e476f40..fe431d8e794 100644 --- a/testing/example_scripts/unittest/test_setup_skip_class.py +++ b/testing/example_scripts/unittest/test_setup_skip_class.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Skipping an entire subclass with unittest.skip() should *not* call setUpClass from a base class.""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_setup_skip_module.py b/testing/example_scripts/unittest/test_setup_skip_module.py index eee4263d22b..07fd96c9cef 100644 --- a/testing/example_scripts/unittest/test_setup_skip_module.py +++ b/testing/example_scripts/unittest/test_setup_skip_module.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """setUpModule is always called, even if all tests in the module are skipped""" +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/unittest/test_unittest_asyncio.py b/testing/example_scripts/unittest/test_unittest_asyncio.py index a82ddaebcc3..8792492b38d 100644 --- a/testing/example_scripts/unittest/test_unittest_asyncio.py +++ b/testing/example_scripts/unittest/test_unittest_asyncio.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs -from typing import List +from __future__ import annotations + from unittest import IsolatedAsyncioTestCase -teardowns: List[None] = [] +teardowns: list[None] = [] class AsyncArguments(IsolatedAsyncioTestCase): diff --git a/testing/example_scripts/unittest/test_unittest_asynctest.py b/testing/example_scripts/unittest/test_unittest_asynctest.py index e9b10171e8d..8a93366b9a3 100644 --- a/testing/example_scripts/unittest/test_unittest_asynctest.py +++ b/testing/example_scripts/unittest/test_unittest_asynctest.py @@ -1,13 +1,14 @@ # mypy: allow-untyped-defs """Issue #7110""" +from __future__ import annotations + import asyncio -from typing import List import asynctest -teardowns: List[None] = [] +teardowns: list[None] = [] class Test(asynctest.TestCase): diff --git a/testing/example_scripts/unittest/test_unittest_plain_async.py b/testing/example_scripts/unittest/test_unittest_plain_async.py index 2a4a66509a4..ea1ae371551 100644 --- a/testing/example_scripts/unittest/test_unittest_plain_async.py +++ b/testing/example_scripts/unittest/test_unittest_plain_async.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import unittest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message.py b/testing/example_scripts/warnings/test_group_warnings_by_message.py index be64a1ff2c8..ee3bc2bbee4 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import warnings import pytest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py index 95fa795efe0..cc514bafbe9 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_1.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import warnings import pytest diff --git a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py index 5204fde8a85..33d5ce8ce34 100644 --- a/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py +++ b/testing/example_scripts/warnings/test_group_warnings_by_message_summary/test_2.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from test_1 import func diff --git a/testing/examples/test_issue519.py b/testing/examples/test_issue519.py index 7b9c109889e..80f78d843a2 100644 --- a/testing/examples/test_issue519.py +++ b/testing/examples/test_issue519.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/freeze/create_executable.py b/testing/freeze/create_executable.py index fbfda2e5d94..2015d22c7c0 100644 --- a/testing/freeze/create_executable.py +++ b/testing/freeze/create_executable.py @@ -1,5 +1,8 @@ """Generate an executable with pytest runner embedded using PyInstaller.""" +from __future__ import annotations + + if __name__ == "__main__": import subprocess diff --git a/testing/freeze/runtests_script.py b/testing/freeze/runtests_script.py index ef63a2d15b9..286c98ac539 100644 --- a/testing/freeze/runtests_script.py +++ b/testing/freeze/runtests_script.py @@ -3,6 +3,9 @@ pytest main(). """ +from __future__ import annotations + + if __name__ == "__main__": import sys diff --git a/testing/freeze/tests/test_trivial.py b/testing/freeze/tests/test_trivial.py index 425f29a649c..000ca97310c 100644 --- a/testing/freeze/tests/test_trivial.py +++ b/testing/freeze/tests/test_trivial.py @@ -1,4 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_upper(): assert "foo".upper() == "FOO" diff --git a/testing/freeze/tox_run.py b/testing/freeze/tox_run.py index 1230fcce140..38c1e75cf10 100644 --- a/testing/freeze/tox_run.py +++ b/testing/freeze/tox_run.py @@ -3,6 +3,9 @@ directory. """ +from __future__ import annotations + + if __name__ == "__main__": import os import sys diff --git a/testing/io/test_pprint.py b/testing/io/test_pprint.py index 15fe6611280..1326ef34b2e 100644 --- a/testing/io/test_pprint.py +++ b/testing/io/test_pprint.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from collections import ChainMap from collections import Counter from collections import defaultdict diff --git a/testing/io/test_saferepr.py b/testing/io/test_saferepr.py index f627434c4e9..075d40cdf44 100644 --- a/testing/io/test_saferepr.py +++ b/testing/io/test_saferepr.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest._io.saferepr import DEFAULT_REPR_MAX_SIZE from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr_unlimited diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index afa8d5cae87..043c2d1d904 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import io import os from pathlib import Path @@ -6,7 +8,6 @@ import shutil import sys from typing import Generator -from typing import Optional from unittest import mock from _pytest._io import terminalwriter @@ -166,7 +167,7 @@ def test_attr_hasmarkup() -> None: assert "\x1b[0m" in s -def assert_color(expected: bool, default: Optional[bool] = None) -> None: +def assert_color(expected: bool, default: bool | None = None) -> None: file = io.StringIO() if default is None: default = not expected diff --git a/testing/io/test_wcwidth.py b/testing/io/test_wcwidth.py index 82503b8300c..9ff1ad06e60 100644 --- a/testing/io/test_wcwidth.py +++ b/testing/io/test_wcwidth.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest._io.wcwidth import wcswidth from _pytest._io.wcwidth import wcwidth import pytest diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py index c1cfff632af..0603eaba218 100644 --- a/testing/logging/test_fixture.py +++ b/testing/logging/test_fixture.py @@ -1,5 +1,7 @@ # mypy: disable-error-code="attr-defined" # mypy: disallow-untyped-defs +from __future__ import annotations + import logging from typing import Iterator diff --git a/testing/logging/test_formatter.py b/testing/logging/test_formatter.py index 37971293726..cfe3bee68c4 100644 --- a/testing/logging/test_formatter.py +++ b/testing/logging/test_formatter.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import logging from typing import Any diff --git a/testing/logging/test_reporting.py b/testing/logging/test_reporting.py index 7e592febf56..cf54788e246 100644 --- a/testing/logging/test_reporting.py +++ b/testing/logging/test_reporting.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import io import os import re diff --git a/testing/plugins_integration/bdd_wallet.py b/testing/plugins_integration/bdd_wallet.py index 2bdb1545424..d748028842a 100644 --- a/testing/plugins_integration/bdd_wallet.py +++ b/testing/plugins_integration/bdd_wallet.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pytest_bdd import given from pytest_bdd import scenario from pytest_bdd import then diff --git a/testing/plugins_integration/django_settings.py b/testing/plugins_integration/django_settings.py index 0715f476531..e36e554db9a 100644 --- a/testing/plugins_integration/django_settings.py +++ b/testing/plugins_integration/django_settings.py @@ -1 +1,4 @@ +from __future__ import annotations + + SECRET_KEY = "mysecret" diff --git a/testing/plugins_integration/pytest_anyio_integration.py b/testing/plugins_integration/pytest_anyio_integration.py index 383d7a0b5db..41ffad18a6e 100644 --- a/testing/plugins_integration/pytest_anyio_integration.py +++ b/testing/plugins_integration/pytest_anyio_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import anyio import pytest diff --git a/testing/plugins_integration/pytest_asyncio_integration.py b/testing/plugins_integration/pytest_asyncio_integration.py index b216c4beecd..cef67f83ea6 100644 --- a/testing/plugins_integration/pytest_asyncio_integration.py +++ b/testing/plugins_integration/pytest_asyncio_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import asyncio import pytest diff --git a/testing/plugins_integration/pytest_mock_integration.py b/testing/plugins_integration/pytest_mock_integration.py index 5494c44270a..a49129cf0c9 100644 --- a/testing/plugins_integration/pytest_mock_integration.py +++ b/testing/plugins_integration/pytest_mock_integration.py @@ -1,3 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + + def test_mocker(mocker): mocker.MagicMock() diff --git a/testing/plugins_integration/pytest_rerunfailures_integration.py b/testing/plugins_integration/pytest_rerunfailures_integration.py index 9a13a3279a9..449661f7294 100644 --- a/testing/plugins_integration/pytest_rerunfailures_integration.py +++ b/testing/plugins_integration/pytest_rerunfailures_integration.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import unittest diff --git a/testing/plugins_integration/pytest_trio_integration.py b/testing/plugins_integration/pytest_trio_integration.py index 60f48ec609b..eceac5076a9 100644 --- a/testing/plugins_integration/pytest_trio_integration.py +++ b/testing/plugins_integration/pytest_trio_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import trio import pytest diff --git a/testing/plugins_integration/pytest_twisted_integration.py b/testing/plugins_integration/pytest_twisted_integration.py index 0dbf5faeb8a..4f386bf1b9f 100644 --- a/testing/plugins_integration/pytest_twisted_integration.py +++ b/testing/plugins_integration/pytest_twisted_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest_twisted from twisted.internet.task import deferLater diff --git a/testing/plugins_integration/simple_integration.py b/testing/plugins_integration/simple_integration.py index 48089afcc7e..ed504ae4bf1 100644 --- a/testing/plugins_integration/simple_integration.py +++ b/testing/plugins_integration/simple_integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import pytest diff --git a/testing/python/approx.py b/testing/python/approx.py index 31e64c4df3d..69743cdbe17 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from contextlib import contextmanager from decimal import Decimal from fractions import Fraction @@ -6,7 +8,6 @@ import operator from operator import eq from operator import ne -from typing import Optional from _pytest.pytester import Pytester from _pytest.python_api import _recursive_sequence_map @@ -415,9 +416,7 @@ def test_zero_tolerance(self): (-1e100, -1e100), ], ) - def test_negative_tolerance( - self, rel: Optional[float], abs: Optional[float] - ) -> None: + def test_negative_tolerance(self, rel: float | None, abs: float | None) -> None: # Negative tolerances are not allowed. with pytest.raises(ValueError): 1.1 == approx(1, rel, abs) diff --git a/testing/python/collect.py b/testing/python/collect.py index 843fa3c0e6b..06386611279 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import sys import textwrap from typing import Any -from typing import Dict import _pytest._code from _pytest.config import ExitCode @@ -1129,7 +1130,7 @@ def test_filter_traceback_generated_code(self) -> None: tb = None try: - ns: Dict[str, Any] = {} + ns: dict[str, Any] = {} exec("def foo(): raise ValueError", ns) ns["foo"]() except ValueError: diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index d3cff38f977..bc091bb1f27 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import sys @@ -4514,7 +4516,7 @@ def test_fixture_named_request(pytester: Pytester) -> None: result.stdout.fnmatch_lines( [ "*'request' is a reserved word for fixtures, use another name:", - " *test_fixture_named_request.py:6", + " *test_fixture_named_request.py:8", ] ) diff --git a/testing/python/integration.py b/testing/python/integration.py index c20aaeed839..c52a683a322 100644 --- a/testing/python/integration.py +++ b/testing/python/integration.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest._code import getfslineno from _pytest.fixtures import getfixturemarker from _pytest.pytester import Pytester diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py index 3d0058fa0a7..2dd85607e71 100644 --- a/testing/python/metafunc.py +++ b/testing/python/metafunc.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import itertools import re @@ -8,11 +10,7 @@ from typing import cast from typing import Dict from typing import Iterator -from typing import List -from typing import Optional from typing import Sequence -from typing import Tuple -from typing import Union import hypothesis from hypothesis import strategies @@ -35,7 +33,7 @@ def Metafunc(self, func, config=None) -> python.Metafunc: # on the funcarg level, so we don't need a full blown # initialization. class FuncFixtureInfoMock: - name2fixturedefs: Dict[str, List[fixtures.FixtureDef[object]]] = {} + name2fixturedefs: dict[str, list[fixtures.FixtureDef[object]]] = {} def __init__(self, names): self.names_closure = names @@ -101,7 +99,7 @@ class Exc(Exception): def __repr__(self): return "Exc(from_gen)" - def gen() -> Iterator[Union[int, None, Exc]]: + def gen() -> Iterator[int | None | Exc]: yield 0 yield None yield Exc() @@ -346,7 +344,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[str, Any, str]] = [ + values: list[tuple[str, Any, str]] = [ ("ação", MockConfig({option: True}), "ação"), ("ação", MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -516,7 +514,7 @@ def test_idmaker_enum(self) -> None: def test_idmaker_idfn(self) -> None: """#351""" - def ids(val: object) -> Optional[str]: + def ids(val: object) -> str | None: if isinstance(val, Exception): return repr(val) return None @@ -579,7 +577,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[Any, str]] = [ + values: list[tuple[Any, str]] = [ (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -617,7 +615,7 @@ def getini(self, name): option = "disable_test_id_escaping_and_forfeit_all_rights_to_community_support" - values: List[Tuple[Any, str]] = [ + values: list[tuple[Any, str]] = [ (MockConfig({option: True}), "ação"), (MockConfig({option: False}), "a\\xe7\\xe3o"), ] @@ -1748,9 +1746,9 @@ def test_parametrize_some_arguments_auto_scope( self, pytester: Pytester, monkeypatch ) -> None: """Integration test for (#3941)""" - class_fix_setup: List[object] = [] + class_fix_setup: list[object] = [] monkeypatch.setattr(sys, "class_fix_setup", class_fix_setup, raising=False) - func_fix_setup: List[object] = [] + func_fix_setup: list[object] = [] monkeypatch.setattr(sys, "func_fix_setup", func_fix_setup, raising=False) pytester.makepyfile( diff --git a/testing/python/raises.py b/testing/python/raises.py index 929865e31a0..271dd3e5a8c 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import re import sys diff --git a/testing/python/show_fixtures_per_test.py b/testing/python/show_fixtures_per_test.py index f756dca41c7..c860b61e21b 100644 --- a/testing/python/show_fixtures_per_test.py +++ b/testing/python/show_fixtures_per_test.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/test_argcomplete.py b/testing/test_argcomplete.py index 0c41c0286a4..5d1513b6206 100644 --- a/testing/test_argcomplete.py +++ b/testing/test_argcomplete.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path import subprocess import sys diff --git a/testing/test_assertion.py b/testing/test_assertion.py index 726235999b4..d4325ab4b62 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1,11 +1,11 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys import textwrap from typing import Any -from typing import List from typing import MutableSequence from typing import NamedTuple -from typing import Optional import attr @@ -19,7 +19,7 @@ import pytest -def mock_config(verbose: int = 0, assertion_override: Optional[int] = None): +def mock_config(verbose: int = 0, assertion_override: int | None = None): class TerminalWriter: def _highlight(self, source, lexer="python"): return source @@ -28,7 +28,7 @@ class Config: def get_terminal_writer(self): return TerminalWriter() - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: if verbosity_type is None: return verbose if verbosity_type == _Config.VERBOSITY_ASSERTIONS: @@ -369,12 +369,12 @@ def test_check(list): result.stdout.fnmatch_lines(["*test_hello*FAIL*", "*test_check*PASS*"]) -def callop(op: str, left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: +def callop(op: str, left: Any, right: Any, verbose: int = 0) -> list[str] | None: config = mock_config(verbose=verbose) return plugin.pytest_assertrepr_compare(config, op, left, right) -def callequal(left: Any, right: Any, verbose: int = 0) -> Optional[List[str]]: +def callequal(left: Any, right: Any, verbose: int = 0) -> list[str] | None: return callop("==", left, right, verbose) @@ -1316,7 +1316,7 @@ class TestTruncateExplanation: LINES_IN_TRUNCATION_MSG = 2 def test_doesnt_truncate_when_input_is_empty_list(self) -> None: - expl: List[str] = [] + expl: list[str] = [] result = truncate._truncate_explanation(expl, max_lines=8, max_chars=100) assert result == expl diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 8db9dbbe5ff..185cd5ef2eb 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import ast import errno from functools import partial @@ -12,12 +14,8 @@ import sys import textwrap from typing import cast -from typing import Dict from typing import Generator -from typing import List from typing import Mapping -from typing import Optional -from typing import Set from unittest import mock import zipfile @@ -45,13 +43,13 @@ def rewrite(src: str) -> ast.Module: def getmsg( - f, extra_ns: Optional[Mapping[str, object]] = None, *, must_pass: bool = False -) -> Optional[str]: + f, extra_ns: Mapping[str, object] | None = None, *, must_pass: bool = False +) -> str | None: """Rewrite the assertions in f, run it, and get the failure message.""" src = "\n".join(_pytest._code.Code.from_function(f).source().lines) mod = rewrite(src) code = compile(mod, "", "exec") - ns: Dict[str, object] = {} + ns: dict[str, object] = {} if extra_ns is not None: ns.update(extra_ns) exec(code, ns) @@ -1638,8 +1636,8 @@ def hook( """ import importlib.machinery - self.find_spec_calls: List[str] = [] - self.initial_paths: Set[Path] = set() + self.find_spec_calls: list[str] = [] + self.initial_paths: set[Path] = set() class StubSession: _initialpaths = self.initial_paths @@ -2059,7 +2057,7 @@ class TestReprSizeVerbosity: ) def test_get_maxsize_for_saferepr(self, verbose: int, expected_size) -> None: class FakeConfig: - def get_verbosity(self, verbosity_type: Optional[str] = None) -> int: + def get_verbosity(self, verbosity_type: str | None = None) -> int: return verbose config = FakeConfig() diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 08158f6191a..72b4265cf75 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from enum import auto from enum import Enum import os @@ -5,9 +7,7 @@ import shutil from typing import Any from typing import Generator -from typing import List from typing import Sequence -from typing import Tuple from _pytest.compat import assert_never from _pytest.config import ExitCode @@ -579,7 +579,7 @@ def test_pass(): def rlf( fail_import: int, fail_run: int, args: Sequence[str] = () - ) -> Tuple[Any, Any]: + ) -> tuple[Any, Any]: monkeypatch.setenv("FAILIMPORT", str(fail_import)) monkeypatch.setenv("FAILTEST", str(fail_run)) @@ -693,7 +693,7 @@ def test_lf_and_ff_prints_no_needless_message( else: assert "rerun previous" in result.stdout.str() - def get_cached_last_failed(self, pytester: Pytester) -> List[str]: + def get_cached_last_failed(self, pytester: Pytester) -> list[str]: config = pytester.parseconfigure() assert config.cache is not None return sorted(config.cache.get("cache/lastfailed", {})) diff --git a/testing/test_capture.py b/testing/test_capture.py index b6c206ec47c..fe6bd7d14fa 100644 --- a/testing/test_capture.py +++ b/testing/test_capture.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import contextlib import io from io import UnsupportedOperation diff --git a/testing/test_collection.py b/testing/test_collection.py index 8ff38a334f4..821c424196f 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import pprint @@ -6,8 +8,6 @@ import sys import tempfile import textwrap -from typing import List -from typing import Type from _pytest.assertion.util import running_on_ci from _pytest.config import ExitCode @@ -536,7 +536,7 @@ def test_collect_topdir(self, pytester: Pytester) -> None: assert len(colitems) == 1 assert colitems[0].path == topdir - def get_reported_items(self, hookrec: HookRecorder) -> List[Item]: + def get_reported_items(self, hookrec: HookRecorder) -> list[Item]: """Return pytest.Item instances reported by the pytest_collectreport hook""" calls = hookrec.getcalls("pytest_collectreport") return [ @@ -1885,7 +1885,7 @@ def test_do_not_collect_symlink_siblings( ) def test_respect_system_exceptions( pytester: Pytester, - exception_class: Type[BaseException], + exception_class: type[BaseException], msg: str, ): head = "Before exception" diff --git a/testing/test_compat.py b/testing/test_compat.py index 73ac1bad858..2c6b0269c27 100644 --- a/testing/test_compat.py +++ b/testing/test_compat.py @@ -1,11 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import enum from functools import cached_property from functools import partial from functools import wraps import sys from typing import TYPE_CHECKING -from typing import Union from _pytest.compat import _PytestWrapper from _pytest.compat import assert_never @@ -216,7 +217,7 @@ def prop(self) -> int: def test_assert_never_union() -> None: - x: Union[int, str] = 10 + x: int | str = 10 if isinstance(x, int): pass diff --git a/testing/test_config.py b/testing/test_config.py index 1cb31fed06d..232839399e2 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import importlib.metadata import os @@ -7,12 +9,7 @@ import sys import textwrap from typing import Any -from typing import Dict -from typing import List from typing import Sequence -from typing import Tuple -from typing import Type -from typing import Union import _pytest._code from _pytest.config import _get_plugin_specs_as_list @@ -633,7 +630,7 @@ def test_absolute_win32_path(self, pytester: Pytester) -> None: class TestConfigAPI: def test_config_trace(self, pytester: Pytester) -> None: config = pytester.parseconfig() - values: List[str] = [] + values: list[str] = [] config.trace.root.setwriter(values.append) config.trace("hello") assert len(values) == 1 @@ -996,7 +993,7 @@ def test_basic_behavior(self, _sys_snapshot) -> None: def test_invocation_params_args(self, _sys_snapshot) -> None: """Show that fromdictargs can handle args in their "orig" format""" - option_dict: Dict[str, object] = {} + option_dict: dict[str, object] = {} args = ["-vvvv", "-s", "a", "b"] config = Config.fromdictargs(option_dict, args) @@ -1210,7 +1207,7 @@ def distributions(): def test_disable_plugin_autoload( pytester: Pytester, monkeypatch: MonkeyPatch, - parse_args: Union[Tuple[str, str], Tuple[()]], + parse_args: tuple[str, str] | tuple[()], should_load: bool, ) -> None: class DummyEntryPoint: @@ -1304,7 +1301,7 @@ def test_invalid_options_show_extra_information(pytester: Pytester) -> None: ], ) def test_consider_args_after_options_for_rootdir( - pytester: Pytester, args: List[str] + pytester: Pytester, args: list[str] ) -> None: """ Consider all arguments in the command-line for rootdir @@ -2241,7 +2238,7 @@ def test_strtobool() -> None: ], ) def test_parse_warning_filter( - arg: str, escape: bool, expected: Tuple[str, str, Type[Warning], str, int] + arg: str, escape: bool, expected: tuple[str, str, type[Warning], str, int] ) -> None: assert parse_warning_filter(arg, escape=escape) == expected diff --git a/testing/test_conftest.py b/testing/test_conftest.py index eb3ebecdc98..d51846f2f5f 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -1,14 +1,13 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import textwrap from typing import cast -from typing import Dict from typing import Generator from typing import List -from typing import Optional from typing import Sequence -from typing import Union from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -27,8 +26,8 @@ def ConftestWithSetinitial(path) -> PytestPluginManager: def conftest_setinitial( conftest: PytestPluginManager, - args: Sequence[Union[str, Path]], - confcutdir: Optional[Path] = None, + args: Sequence[str | Path], + confcutdir: Path | None = None, ) -> None: conftest._set_initial_conftests( args=args, @@ -536,7 +535,7 @@ def pytest_addoption(parser): class TestConftestVisibility: - def _setup_tree(self, pytester: Pytester) -> Dict[str, Path]: # for issue616 + def _setup_tree(self, pytester: Pytester) -> dict[str, Path]: # for issue616 # example mostly taken from: # https://mail.python.org/pipermail/pytest-dev/2014-September/002617.html runner = pytester.mkdir("empty") diff --git a/testing/test_debugging.py b/testing/test_debugging.py index 1f3422947de..37032f92354 100644 --- a/testing/test_debugging.py +++ b/testing/test_debugging.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys -from typing import List import _pytest._code from _pytest.debugging import _validate_usepdb_cls @@ -35,7 +36,7 @@ def runpdb_and_get_report(pytester: Pytester, source: str): @pytest.fixture -def custom_pdb_calls() -> List[str]: +def custom_pdb_calls() -> list[str]: called = [] # install dummy debugger class and track which methods were called on it @@ -854,7 +855,7 @@ def test_post_mortem(): self.flush(child) def test_pdb_custom_cls( - self, pytester: Pytester, custom_pdb_calls: List[str] + self, pytester: Pytester, custom_pdb_calls: list[str] ) -> None: p1 = pytester.makepyfile("""xxx """) result = pytester.runpytest_inprocess( @@ -880,7 +881,7 @@ def test_pdb_validate_usepdb_cls(self): assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist") def test_pdb_custom_cls_without_pdb( - self, pytester: Pytester, custom_pdb_calls: List[str] + self, pytester: Pytester, custom_pdb_calls: list[str] ) -> None: p1 = pytester.makepyfile("""xxx """) result = pytester.runpytest_inprocess("--pdbcls=_pytest:_CustomPdb", p1) diff --git a/testing/test_doctest.py b/testing/test_doctest.py index 9b33d641a14..4aa4876c711 100644 --- a/testing/test_doctest.py +++ b/testing/test_doctest.py @@ -1,10 +1,11 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import inspect from pathlib import Path import sys import textwrap from typing import Callable -from typing import Optional from _pytest.doctest import _get_checker from _pytest.doctest import _is_main_py @@ -1595,7 +1596,7 @@ def __getattr__(self, _): "stop", [None, _is_mocked, lambda f: None, lambda f: False, lambda f: True] ) def test_warning_on_unwrap_of_broken_object( - stop: Optional[Callable[[object], object]], + stop: Callable[[object], object] | None, ) -> None: bad_instance = Broken() assert inspect.unwrap.__module__ == "inspect" diff --git a/testing/test_entry_points.py b/testing/test_entry_points.py index 68e3a8a92e4..543f3252b22 100644 --- a/testing/test_entry_points.py +++ b/testing/test_entry_points.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import importlib.metadata diff --git a/testing/test_error_diffs.py b/testing/test_error_diffs.py index f290eb1679f..741a6ca82d0 100644 --- a/testing/test_error_diffs.py +++ b/testing/test_error_diffs.py @@ -5,6 +5,8 @@ """ +from __future__ import annotations + from _pytest.pytester import Pytester import pytest diff --git a/testing/test_faulthandler.py b/testing/test_faulthandler.py index e5016976130..c416e81d2d9 100644 --- a/testing/test_faulthandler.py +++ b/testing/test_faulthandler.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import io import sys diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 260b9d07c9c..9532f1eef75 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path from textwrap import dedent diff --git a/testing/test_helpconfig.py b/testing/test_helpconfig.py index 4906ef5c8f0..daf2bb12714 100644 --- a/testing/test_helpconfig.py +++ b/testing/test_helpconfig.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest.config import ExitCode from _pytest.pytester import Pytester import pytest diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 1ebc3ed3a51..67234302a89 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -1,14 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from datetime import datetime import os from pathlib import Path import platform from typing import cast -from typing import List -from typing import Optional -from typing import Tuple from typing import TYPE_CHECKING -from typing import Union from xml.dom import minidom import xmlschema @@ -39,8 +37,8 @@ def __init__(self, pytester: Pytester, schema: xmlschema.XMLSchema) -> None: self.schema = schema def __call__( - self, *args: Union[str, "os.PathLike[str]"], family: Optional[str] = "xunit1" - ) -> Tuple[RunResult, "DomNode"]: + self, *args: str | os.PathLike[str], family: str | None = "xunit1" + ) -> tuple[RunResult, DomNode]: if family: args = ("-o", "junit_family=" + family, *args) xml_path = self.pytester.path.joinpath("junit.xml") @@ -940,7 +938,7 @@ def test_mangle_test_address() -> None: def test_dont_configure_on_workers(tmp_path: Path) -> None: - gotten: List[object] = [] + gotten: list[object] = [] class FakeConfig: if TYPE_CHECKING: @@ -1183,7 +1181,7 @@ def test_unicode_issue368(pytester: Pytester) -> None: class Report(BaseReport): longrepr = ustr - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" when = "teardown" @@ -1495,7 +1493,7 @@ def test_global_properties(pytester: Pytester, xunit_family: str) -> None: log = LogXML(str(path), None, family=xunit_family) class Report(BaseReport): - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "test_node_id" log.pytest_sessionstart() @@ -1531,7 +1529,7 @@ def test_url_property(pytester: Pytester) -> None: class Report(BaseReport): longrepr = "FooBarBaz" - sections: List[Tuple[str, str]] = [] + sections: list[tuple[str, str]] = [] nodeid = "something" location = "tests/filename.py", 42, "TestClass.method" url = test_url diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index d2b33b4fb8b..72854e4e5c0 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path from _pytest.compat import LEGACY_PATH diff --git a/testing/test_link_resolve.py b/testing/test_link_resolve.py index 0461cd75554..0557dae669d 100644 --- a/testing/test_link_resolve.py +++ b/testing/test_link_resolve.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from contextlib import contextmanager import os.path from pathlib import Path diff --git a/testing/test_main.py b/testing/test_main.py index 6294f66b360..94eac02ce63 100644 --- a/testing/test_main.py +++ b/testing/test_main.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse import os from pathlib import Path import re -from typing import Optional from _pytest.config import ExitCode from _pytest.config import UsageError @@ -66,7 +67,7 @@ def pytest_internalerror(excrepr, excinfo): @pytest.mark.parametrize("returncode", (None, 42)) def test_wrap_session_exit_sessionfinish( - returncode: Optional[int], pytester: Pytester + returncode: int | None, pytester: Pytester ) -> None: pytester.makeconftest( f""" diff --git a/testing/test_mark.py b/testing/test_mark.py index 2896afa4532..090e10ee9c4 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -1,8 +1,8 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import sys -from typing import List -from typing import Optional from unittest import mock from _pytest.config import ExitCode @@ -214,7 +214,7 @@ def test_hello(): ], ) def test_mark_option( - expr: str, expected_passed: List[Optional[str]], pytester: Pytester + expr: str, expected_passed: list[str | None], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -238,7 +238,7 @@ def test_two(): [("interface", ["test_interface"]), ("not interface", ["test_nointer"])], ) def test_mark_option_custom( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makeconftest( """ @@ -276,7 +276,7 @@ def test_nointer(): ], ) def test_keyword_option_custom( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -314,7 +314,7 @@ def test_keyword_option_considers_mark(pytester: Pytester) -> None: ], ) def test_keyword_option_parametrize( - expr: str, expected_passed: List[str], pytester: Pytester + expr: str, expected_passed: list[str], pytester: Pytester ) -> None: pytester.makepyfile( """ @@ -895,7 +895,7 @@ def test_ddd(): pass ) monkeypatch.chdir(pytester.path / "suite") - def get_collected_names(*args: str) -> List[str]: + def get_collected_names(*args: str) -> list[str]: _, rec = pytester.inline_genitems(*args) calls = rec.getcalls("pytest_collection_finish") assert len(calls) == 1 @@ -930,7 +930,7 @@ def test_aliases(self) -> None: @pytest.mark.parametrize("mark", [None, "", "skip", "xfail"]) def test_parameterset_for_parametrize_marks( - pytester: Pytester, mark: Optional[str] + pytester: Pytester, mark: str | None ) -> None: if mark is not None: pytester.makeini( diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 07c89f90838..5bce004cb1c 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Callable from _pytest.mark.expression import Expression diff --git a/testing/test_meta.py b/testing/test_meta.py index 40ed95d6b47..e7d836f7ace 100644 --- a/testing/test_meta.py +++ b/testing/test_meta.py @@ -4,16 +4,17 @@ namespace being set, which is critical for the initialization of xdist. """ +from __future__ import annotations + import pkgutil import subprocess import sys -from typing import List import _pytest import pytest -def _modules() -> List[str]: +def _modules() -> list[str]: pytest_pkg: str = _pytest.__path__ # type: ignore return sorted( n diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 2ad3ccc4ddc..079d8ff60ad 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -1,12 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os from pathlib import Path import re import sys import textwrap -from typing import Dict from typing import Generator -from typing import Type from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -135,7 +135,7 @@ def test_setitem() -> None: def test_setitem_deleted_meanwhile() -> None: - d: Dict[str, object] = {} + d: dict[str, object] = {} monkeypatch = MonkeyPatch() monkeypatch.setitem(d, "x", 2) del d["x"] @@ -160,7 +160,7 @@ def test_setenv_deleted_meanwhile(before: bool) -> None: def test_delitem() -> None: - d: Dict[str, object] = {"x": 1} + d: dict[str, object] = {"x": 1} monkeypatch = MonkeyPatch() monkeypatch.delitem(d, "x") assert "x" not in d @@ -360,7 +360,7 @@ class SampleInherit(Sample): [Sample, SampleInherit], ids=["new", "new-inherit"], ) -def test_issue156_undo_staticmethod(Sample: Type[Sample]) -> None: +def test_issue156_undo_staticmethod(Sample: type[Sample]) -> None: monkeypatch = MonkeyPatch() monkeypatch.setattr(Sample, "hello", None) diff --git a/testing/test_nodes.py b/testing/test_nodes.py index a3caf471f70..f039acf243b 100644 --- a/testing/test_nodes.py +++ b/testing/test_nodes.py @@ -1,8 +1,9 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path import re from typing import cast -from typing import Type import warnings from _pytest import nodes @@ -73,7 +74,7 @@ def runtest(self): "warn_type, msg", [(DeprecationWarning, "deprecated"), (PytestWarning, "pytest")] ) def test_node_warn_is_no_longer_only_pytest_warnings( - pytester: Pytester, warn_type: Type[Warning], msg: str + pytester: Pytester, warn_type: type[Warning], msg: str ) -> None: items = pytester.getitems( """ diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py index e959dfd631b..14e2b5f69fb 100644 --- a/testing/test_parseopt.py +++ b/testing/test_parseopt.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import argparse import locale import os diff --git a/testing/test_pastebin.py b/testing/test_pastebin.py index 9ca0da8f69d..8fdd60bac75 100644 --- a/testing/test_pastebin.py +++ b/testing/test_pastebin.py @@ -1,8 +1,8 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import email.message import io -from typing import List -from typing import Union from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester @@ -11,8 +11,8 @@ class TestPasteCapture: @pytest.fixture - def pastebinlist(self, monkeypatch, request) -> List[Union[str, bytes]]: - pastebinlist: List[Union[str, bytes]] = [] + def pastebinlist(self, monkeypatch, request) -> list[str | bytes]: + pastebinlist: list[str | bytes] = [] plugin = request.config.pluginmanager.getplugin("pastebin") monkeypatch.setattr(plugin, "create_new_paste", pastebinlist.append) return pastebinlist diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py index 688d13f2f05..81aba25f78f 100644 --- a/testing/test_pathlib.py +++ b/testing/test_pathlib.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import errno import importlib.abc import importlib.machinery @@ -12,9 +14,7 @@ from typing import Any from typing import Generator from typing import Iterator -from typing import Optional from typing import Sequence -from typing import Tuple import unittest.mock from _pytest.monkeypatch import MonkeyPatch @@ -865,7 +865,7 @@ def test_my_test(): def create_installed_doctests_and_tests_dir( self, path: Path, monkeypatch: MonkeyPatch - ) -> Tuple[Path, Path, Path]: + ) -> tuple[Path, Path, Path]: """ Create a directory structure where the application code is installed in a virtual environment, and the tests are in an outside ".tests" directory. @@ -1267,8 +1267,8 @@ def setup_imports_tracking(self, monkeypatch: MonkeyPatch) -> None: monkeypatch.setattr(sys, "pytest_namespace_packages_test", [], raising=False) def setup_directories( - self, tmp_path: Path, monkeypatch: Optional[MonkeyPatch], pytester: Pytester - ) -> Tuple[Path, Path]: + self, tmp_path: Path, monkeypatch: MonkeyPatch | None, pytester: Pytester + ) -> tuple[Path, Path]: # Use a code to guard against modules being imported more than once. # This is a safeguard in case future changes break this invariant. code = dedent( @@ -1438,7 +1438,7 @@ class CustomImporter(importlib.abc.MetaPathFinder): def find_spec( self, name: str, path: Any = None, target: Any = None - ) -> Optional[importlib.machinery.ModuleSpec]: + ) -> importlib.machinery.ModuleSpec | None: if name == "com": spec = importlib.machinery.ModuleSpec("com", loader=None) spec.submodule_search_locations = [str(com_root_2), str(com_root_1)] diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 99b003b66ed..db85124bf0d 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -1,9 +1,10 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import shutil import sys import types -from typing import List from _pytest.config import Config from _pytest.config import ExitCode @@ -152,7 +153,7 @@ def pytest_plugin_registered(self): saveindent.append(pytestpm.trace.root.indent) raise ValueError() - values: List[str] = [] + values: list[str] = [] pytestpm.trace.root.setwriter(values.append) undo = pytestpm.enable_tracing() try: diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 9c6081a56db..87714b4708f 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -1,10 +1,11 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import subprocess import sys import time from types import ModuleType -from typing import List from _pytest.config import ExitCode from _pytest.config import PytestPluginManager @@ -227,7 +228,7 @@ def test_inline_run_test_module_not_cleaned_up(self, pytester: Pytester) -> None def spy_factory(self): class SysModulesSnapshotSpy: - instances: List["SysModulesSnapshotSpy"] = [] + instances: list[SysModulesSnapshotSpy] = [] def __init__(self, preserve=None) -> None: SysModulesSnapshotSpy.instances.append(self) @@ -399,7 +400,7 @@ def test_preserve_container(self, monkeypatch: MonkeyPatch, path_type) -> None: original_data = list(getattr(sys, path_type)) original_other = getattr(sys, other_path_type) original_other_data = list(original_other) - new: List[object] = [] + new: list[object] = [] snapshot = SysPathsSnapshot() monkeypatch.setattr(sys, path_type, new) snapshot.restore() diff --git a/testing/test_python_path.py b/testing/test_python_path.py index 73a8725680f..1db02252d22 100644 --- a/testing/test_python_path.py +++ b/testing/test_python_path.py @@ -1,9 +1,9 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys from textwrap import dedent from typing import Generator -from typing import List -from typing import Optional from _pytest.pytester import Pytester import pytest @@ -91,8 +91,8 @@ def test_clean_up(pytester: Pytester) -> None: pytester.makefile(".ini", pytest="[pytest]\npythonpath=I_SHALL_BE_REMOVED\n") pytester.makepyfile(test_foo="""def test_foo(): pass""") - before: Optional[List[str]] = None - after: Optional[List[str]] = None + before: list[str] | None = None + after: list[str] | None = None class Plugin: @pytest.hookimpl(wrapper=True, tryfirst=True) diff --git a/testing/test_recwarn.py b/testing/test_recwarn.py index 27ee9aa72f0..384f2b66a15 100644 --- a/testing/test_recwarn.py +++ b/testing/test_recwarn.py @@ -1,9 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys -from typing import List -from typing import Optional -from typing import Type -from typing import Union import warnings import pytest @@ -54,7 +52,7 @@ class ChildOfChildWarning(ChildWarning): pass @staticmethod - def raise_warnings_from_list(_warnings: List[Type[Warning]]): + def raise_warnings_from_list(_warnings: list[type[Warning]]): for warn in _warnings: warnings.warn(f"Warning {warn().__repr__()}", warn) @@ -134,7 +132,7 @@ def test_invalid_enter_exit(self) -> None: class TestDeprecatedCall: """test pytest.deprecated_call()""" - def dep(self, i: int, j: Optional[int] = None) -> int: + def dep(self, i: int, j: int | None = None) -> int: if i == 0: warnings.warn("is deprecated", DeprecationWarning, stacklevel=1) return 42 @@ -563,7 +561,7 @@ def test_raise_type_error_on_invalid_warning() -> None: pytest.param(Warning(), id="Warning"), ], ) -def test_no_raise_type_error_on_valid_warning(message: Union[str, Warning]) -> None: +def test_no_raise_type_error_on_valid_warning(message: str | Warning) -> None: """Check pytest.warns validates warning messages are strings (#10865) or Warning instances (#11959).""" with pytest.warns(Warning): diff --git a/testing/test_reports.py b/testing/test_reports.py index 7987b401771..3e314d2aade 100644 --- a/testing/test_reports.py +++ b/testing/test_reports.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from typing import Sequence -from typing import Union from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr @@ -294,8 +295,8 @@ def test_a(): reprec = pytester.inline_run() if report_class is TestReport: - reports: Union[Sequence[TestReport], Sequence[CollectReport]] = ( - reprec.getreports("pytest_runtest_logreport") + reports: Sequence[TestReport] | Sequence[CollectReport] = reprec.getreports( + "pytest_runtest_logreport" ) # we have 3 reports: setup/call/teardown assert len(reports) == 3 diff --git a/testing/test_runner.py b/testing/test_runner.py index 3ec5678274e..79f7a3fd4d3 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -1,14 +1,12 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from functools import partial import inspect import os from pathlib import Path import sys import types -from typing import Dict -from typing import List -from typing import Tuple -from typing import Type import warnings from _pytest import outcomes @@ -523,7 +521,7 @@ class TestClass(object): assert res[1].name == "TestClass" -reporttypes: List[Type[reports.BaseReport]] = [ +reporttypes: list[type[reports.BaseReport]] = [ reports.BaseReport, reports.TestReport, reports.CollectReport, @@ -533,9 +531,9 @@ class TestClass(object): @pytest.mark.parametrize( "reporttype", reporttypes, ids=[x.__name__ for x in reporttypes] ) -def test_report_extra_parameters(reporttype: Type[reports.BaseReport]) -> None: +def test_report_extra_parameters(reporttype: type[reports.BaseReport]) -> None: args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] - basekw: Dict[str, List[object]] = dict.fromkeys(args, []) + basekw: dict[str, list[object]] = dict.fromkeys(args, []) report = reporttype(newthing=1, **basekw) assert report.newthing == 1 @@ -1048,7 +1046,7 @@ def runtest(self): def test_current_test_env_var(pytester: Pytester, monkeypatch: MonkeyPatch) -> None: - pytest_current_test_vars: List[Tuple[str, str]] = [] + pytest_current_test_vars: list[tuple[str, str]] = [] monkeypatch.setattr( sys, "pytest_current_test_vars", pytest_current_test_vars, raising=False ) diff --git a/testing/test_runner_xunit.py b/testing/test_runner_xunit.py index 587c9eb9fef..75e838a49e8 100644 --- a/testing/test_runner_xunit.py +++ b/testing/test_runner_xunit.py @@ -1,7 +1,7 @@ # mypy: allow-untyped-defs """Test correct setup/teardowns at module, class, and instance level.""" -from typing import List +from __future__ import annotations from _pytest.pytester import Pytester import pytest @@ -251,7 +251,7 @@ def test_setup_teardown_function_level_with_optional_argument( """Parameter to setup/teardown xunit-style functions parameter is now optional (#1728).""" import sys - trace_setups_teardowns: List[str] = [] + trace_setups_teardowns: list[str] = [] monkeypatch.setattr( sys, "trace_setups_teardowns", trace_setups_teardowns, raising=False ) diff --git a/testing/test_scope.py b/testing/test_scope.py index 1727c2ee1bb..3cb811469a9 100644 --- a/testing/test_scope.py +++ b/testing/test_scope.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import re from _pytest.scope import Scope diff --git a/testing/test_session.py b/testing/test_session.py index 8624af478b1..ba904916033 100644 --- a/testing/test_session.py +++ b/testing/test_session.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester diff --git a/testing/test_setuponly.py b/testing/test_setuponly.py index 8638f5a6140..87123bd9a16 100644 --- a/testing/test_setuponly.py +++ b/testing/test_setuponly.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys from _pytest.config import ExitCode diff --git a/testing/test_setupplan.py b/testing/test_setupplan.py index d51a1873959..5a9211d7806 100644 --- a/testing/test_setupplan.py +++ b/testing/test_setupplan.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 459216a6d6b..558e3d35c6a 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys import textwrap diff --git a/testing/test_stash.py b/testing/test_stash.py index e523c4e6f2b..c7f6f4f95fe 100644 --- a/testing/test_stash.py +++ b/testing/test_stash.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.stash import Stash from _pytest.stash import StashKey import pytest diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 472afea6620..affdb73375e 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + from pathlib import Path from _pytest.cacheprovider import Cache diff --git a/testing/test_terminal.py b/testing/test_terminal.py index ce9fdc50c8a..01a84fd8d2c 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1,6 +1,8 @@ # mypy: allow-untyped-defs """Terminal reporting of the full testing process.""" +from __future__ import annotations + from io import StringIO import os from pathlib import Path @@ -8,10 +10,7 @@ import textwrap from types import SimpleNamespace from typing import cast -from typing import Dict -from typing import List from typing import NamedTuple -from typing import Tuple import pluggy @@ -1929,9 +1928,9 @@ def tr() -> TerminalReporter: ) def test_summary_stats( tr: TerminalReporter, - exp_line: List[Tuple[str, Dict[str, bool]]], + exp_line: list[tuple[str, dict[str, bool]]], exp_color: str, - stats_arg: Dict[str, List[object]], + stats_arg: dict[str, list[object]], ) -> None: tr.stats = stats_arg diff --git a/testing/test_threadexception.py b/testing/test_threadexception.py index 99837b94e8a..abd30144914 100644 --- a/testing/test_threadexception.py +++ b/testing/test_threadexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from _pytest.pytester import Pytester import pytest diff --git a/testing/test_tmpdir.py b/testing/test_tmpdir.py index f424998e50f..865d8e0b05c 100644 --- a/testing/test_tmpdir.py +++ b/testing/test_tmpdir.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import dataclasses import os from pathlib import Path @@ -6,8 +8,6 @@ import sys from typing import Callable from typing import cast -from typing import List -from typing import Union import warnings from _pytest import pathlib @@ -34,7 +34,7 @@ def test_tmp_path_fixture(pytester: Pytester) -> None: @dataclasses.dataclass class FakeConfig: - basetemp: Union[str, Path] + basetemp: str | Path @property def trace(self): @@ -394,7 +394,7 @@ def test_cleanup_lock_create(self, tmp_path): def test_lock_register_cleanup_removal(self, tmp_path: Path) -> None: lock = create_cleanup_lock(tmp_path) - registry: List[Callable[..., None]] = [] + registry: list[Callable[..., None]] = [] register_cleanup_lock_removal(lock, register=registry.append) (cleanup_func,) = registry diff --git a/testing/test_unittest.py b/testing/test_unittest.py index 9561cad5eb5..56224c08228 100644 --- a/testing/test_unittest.py +++ b/testing/test_unittest.py @@ -1,6 +1,7 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import sys -from typing import List from _pytest.config import ExitCode from _pytest.monkeypatch import MonkeyPatch @@ -1214,7 +1215,7 @@ def test_pdb_teardown_called(pytester: Pytester, monkeypatch: MonkeyPatch) -> No We delay the normal tearDown() calls when --pdb is given, so this ensures we are calling tearDown() eventually to avoid memory leaks when using --pdb. """ - teardowns: List[str] = [] + teardowns: list[str] = [] monkeypatch.setattr( pytest, "test_pdb_teardown_called_teardowns", teardowns, raising=False ) @@ -1251,7 +1252,7 @@ def test_pdb_teardown_skipped_for_functions( With --pdb, setUp and tearDown should not be called for tests skipped via a decorator (#7215). """ - tracked: List[str] = [] + tracked: list[str] = [] monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( @@ -1286,7 +1287,7 @@ def test_pdb_teardown_skipped_for_classes( With --pdb, setUp and tearDown should not be called for tests skipped via a decorator on the class (#10060). """ - tracked: List[str] = [] + tracked: list[str] = [] monkeypatch.setattr(pytest, "track_pdb_teardown_skipped", tracked, raising=False) pytester.makepyfile( diff --git a/testing/test_unraisableexception.py b/testing/test_unraisableexception.py index 1657cfe4a84..a15c754d067 100644 --- a/testing/test_unraisableexception.py +++ b/testing/test_unraisableexception.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import sys from _pytest.pytester import Pytester diff --git a/testing/test_warning_types.py b/testing/test_warning_types.py index a50d278bde2..19fe0f8a272 100644 --- a/testing/test_warning_types.py +++ b/testing/test_warning_types.py @@ -1,4 +1,6 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import inspect from _pytest import warning_types diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 73c8c1b3231..d4d0e0b7f93 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -1,9 +1,8 @@ # mypy: allow-untyped-defs +from __future__ import annotations + import os import sys -from typing import List -from typing import Optional -from typing import Tuple import warnings from _pytest.fixtures import FixtureRequest @@ -618,11 +617,11 @@ def test_group_warnings_by_message_summary(pytester: Pytester) -> None: f"*== {WARNINGS_SUMMARY_HEADER} ==*", "test_1.py: 21 warnings", "test_2.py: 1 warning", - " */test_1.py:8: UserWarning: foo", + " */test_1.py:10: UserWarning: foo", " warnings.warn(UserWarning(msg))", "", "test_1.py: 20 warnings", - " */test_1.py:8: UserWarning: bar", + " */test_1.py:10: UserWarning: bar", " warnings.warn(UserWarning(msg))", "", "-- Docs: *", @@ -654,8 +653,8 @@ class TestStackLevel: @pytest.fixture def capwarn(self, pytester: Pytester): class CapturedWarnings: - captured: List[ - Tuple[warnings.WarningMessage, Optional[Tuple[str, int, str]]] + captured: list[ + tuple[warnings.WarningMessage, tuple[str, int, str] | None] ] = [] @classmethod diff --git a/testing/typing_checks.py b/testing/typing_checks.py index 4b146a25110..d4d6a97aea6 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -5,6 +5,8 @@ none of the code triggers any mypy errors. """ +from __future__ import annotations + import contextlib from typing import Optional From b7c0295e1a5186214fa6919f15dfaf379681e8e5 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 17 Jun 2024 17:12:34 +0200 Subject: [PATCH 099/199] use runtime union for EXCEPTION_OR_MORE --- src/_pytest/_code/code.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 750195b94e9..229c7deee90 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -30,7 +30,10 @@ from typing import Pattern from typing import Sequence from typing import SupportsIndex +from typing import Tuple +from typing import Type from typing import TypeVar +from typing import Union import pluggy @@ -53,9 +56,7 @@ TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"] -EXCEPTION_OR_MORE = type[Exception] | tuple[type[Exception], ...] - -type_alias = type # to sidestep shadowing +EXCEPTION_OR_MORE = Union[Type[Exception], Tuple[Type[Exception], ...]] class Code: From 5e1649f59ace652a6feb84dafa591f3f06cdd6c2 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Mon, 17 Jun 2024 18:03:10 +0200 Subject: [PATCH 100/199] resolve most sphinx lookup errors add the extra sphinx annotations to refer to Path instances add Path to nitpicky ignore --- doc/en/conf.py | 8 +++- src/_pytest/_code/code.py | 3 +- src/_pytest/config/__init__.py | 68 +++++++++++++++++----------------- src/_pytest/hookspec.py | 6 +++ src/_pytest/main.py | 1 + src/_pytest/pytester.py | 4 ++ src/_pytest/python.py | 2 +- src/_pytest/runner.py | 1 + tox.ini | 8 +++- 9 files changed, 62 insertions(+), 39 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index 670d6adf74b..53056024b37 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -23,9 +23,11 @@ from textwrap import dedent from typing import TYPE_CHECKING -from _pytest import __version__ as version +from _pytest import __version__ as full_version +version = full_version.split("+")[0] + if TYPE_CHECKING: import sphinx.application @@ -191,6 +193,7 @@ ("py:class", "SubRequest"), ("py:class", "TerminalReporter"), ("py:class", "_pytest._code.code.TerminalRepr"), + ("py:class", "TerminalRepr"), ("py:class", "_pytest.fixtures.FixtureFunctionMarker"), ("py:class", "_pytest.logging.LogCaptureHandler"), ("py:class", "_pytest.mark.structures.ParameterSet"), @@ -212,13 +215,16 @@ ("py:class", "_PluggyPlugin"), # TypeVars ("py:class", "_pytest._code.code.E"), + ("py:class", "E"), # due to delayed annotation ("py:class", "_pytest.fixtures.FixtureFunction"), ("py:class", "_pytest.nodes._NodeType"), + ("py:class", "_NodeType"), # due to delayed annotation ("py:class", "_pytest.python_api.E"), ("py:class", "_pytest.recwarn.T"), ("py:class", "_pytest.runner.TResult"), ("py:obj", "_pytest.fixtures.FixtureValue"), ("py:obj", "_pytest.stash.T"), + ("py:class", "_ScopeName"), ] diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 229c7deee90..e7452825756 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -620,7 +620,8 @@ def getrepr( showlocals: bool = False, style: TracebackStyle = "long", abspath: bool = False, - tbfilter: bool | Callable[[ExceptionInfo[BaseException]], Traceback] = True, + tbfilter: bool + | Callable[[ExceptionInfo[BaseException]], _pytest._code.code.Traceback] = True, funcargs: bool = False, truncate_locals: bool = True, truncate_args: bool = True, diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 47e3f5b5a50..23a2c47970a 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -13,7 +13,7 @@ import importlib.metadata import inspect import os -from pathlib import Path +import pathlib import re import shlex import sys @@ -114,7 +114,7 @@ class ExitCode(enum.IntEnum): class ConftestImportFailure(Exception): def __init__( self, - path: Path, + path: pathlib.Path, *, cause: Exception, ) -> None: @@ -290,7 +290,7 @@ def get_config( invocation_params=Config.InvocationParams( args=args or (), plugins=plugins, - dir=Path.cwd(), + dir=pathlib.Path.cwd(), ), ) @@ -347,7 +347,7 @@ def _prepareconfig( raise -def _get_directory(path: Path) -> Path: +def _get_directory(path: pathlib.Path) -> pathlib.Path: """Get the directory of a path - itself if already a directory.""" if path.is_file(): return path.parent @@ -408,9 +408,9 @@ def __init__(self) -> None: # All conftest modules applicable for a directory. # This includes the directory's own conftest modules as well # as those of its parent directories. - self._dirpath2confmods: dict[Path, list[types.ModuleType]] = {} + self._dirpath2confmods: dict[pathlib.Path, list[types.ModuleType]] = {} # Cutoff directory above which conftests are no longer discovered. - self._confcutdir: Path | None = None + self._confcutdir: pathlib.Path | None = None # If set, conftest loading is skipped. self._noconftest = False @@ -544,12 +544,12 @@ def pytest_configure(self, config: Config) -> None: # def _set_initial_conftests( self, - args: Sequence[str | Path], + args: Sequence[str | pathlib.Path], pyargs: bool, noconftest: bool, - rootpath: Path, - confcutdir: Path | None, - invocation_dir: Path, + rootpath: pathlib.Path, + confcutdir: pathlib.Path | None, + invocation_dir: pathlib.Path, importmode: ImportMode | str, *, consider_namespace_packages: bool, @@ -593,7 +593,7 @@ def _set_initial_conftests( consider_namespace_packages=consider_namespace_packages, ) - def _is_in_confcutdir(self, path: Path) -> bool: + def _is_in_confcutdir(self, path: pathlib.Path) -> bool: """Whether to consider the given path to load conftests from.""" if self._confcutdir is None: return True @@ -610,9 +610,9 @@ def _is_in_confcutdir(self, path: Path) -> bool: def _try_load_conftest( self, - anchor: Path, + anchor: pathlib.Path, importmode: str | ImportMode, - rootpath: Path, + rootpath: pathlib.Path, *, consider_namespace_packages: bool, ) -> None: @@ -635,9 +635,9 @@ def _try_load_conftest( def _loadconftestmodules( self, - path: Path, + path: pathlib.Path, importmode: str | ImportMode, - rootpath: Path, + rootpath: pathlib.Path, *, consider_namespace_packages: bool, ) -> None: @@ -665,14 +665,14 @@ def _loadconftestmodules( clist.append(mod) self._dirpath2confmods[directory] = clist - def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]: + def _getconftestmodules(self, path: pathlib.Path) -> Sequence[types.ModuleType]: directory = self._get_directory(path) return self._dirpath2confmods.get(directory, ()) def _rget_with_confmod( self, name: str, - path: Path, + path: pathlib.Path, ) -> tuple[types.ModuleType, Any]: modules = self._getconftestmodules(path) for mod in reversed(modules): @@ -684,9 +684,9 @@ def _rget_with_confmod( def _importconftest( self, - conftestpath: Path, + conftestpath: pathlib.Path, importmode: str | ImportMode, - rootpath: Path, + rootpath: pathlib.Path, *, consider_namespace_packages: bool, ) -> types.ModuleType: @@ -738,7 +738,7 @@ def _importconftest( def _check_non_top_pytest_plugins( self, mod: types.ModuleType, - conftestpath: Path, + conftestpath: pathlib.Path, ) -> None: if ( hasattr(mod, "pytest_plugins") @@ -995,15 +995,15 @@ class InvocationParams: """The command-line arguments as passed to :func:`pytest.main`.""" plugins: Sequence[str | _PluggyPlugin] | None """Extra plugins, might be `None`.""" - dir: Path - """The directory from which :func:`pytest.main` was invoked.""" + dir: pathlib.Path + """The directory from which :func:`pytest.main` was invoked. :type: pathlib.Path""" def __init__( self, *, args: Iterable[str], plugins: Sequence[str | _PluggyPlugin] | None, - dir: Path, + dir: pathlib.Path, ) -> None: object.__setattr__(self, "args", tuple(args)) object.__setattr__(self, "plugins", plugins) @@ -1037,7 +1037,7 @@ def __init__( if invocation_params is None: invocation_params = self.InvocationParams( - args=(), plugins=None, dir=Path.cwd() + args=(), plugins=None, dir=pathlib.Path.cwd() ) self.option = argparse.Namespace() @@ -1088,7 +1088,7 @@ def __init__( self.args: list[str] = [] @property - def rootpath(self) -> Path: + def rootpath(self) -> pathlib.Path: """The path to the :ref:`rootdir `. :type: pathlib.Path @@ -1098,11 +1098,9 @@ def rootpath(self) -> Path: return self._rootpath @property - def inipath(self) -> Path | None: + def inipath(self) -> pathlib.Path | None: """The path to the :ref:`configfile `. - :type: Optional[pathlib.Path] - .. versionadded:: 6.1 """ return self._inipath @@ -1313,8 +1311,8 @@ def _decide_args( args: list[str], pyargs: bool, testpaths: list[str], - invocation_dir: Path, - rootpath: Path, + invocation_dir: pathlib.Path, + rootpath: pathlib.Path, warn: bool, ) -> tuple[list[str], ArgsSource]: """Decide the args (initial paths/nodeids) to use given the relevant inputs. @@ -1640,17 +1638,19 @@ def _getini(self, name: str): else: return self._getini_unknown_type(name, type, value) - def _getconftest_pathlist(self, name: str, path: Path) -> list[Path] | None: + def _getconftest_pathlist( + self, name: str, path: pathlib.Path + ) -> list[pathlib.Path] | None: try: mod, relroots = self.pluginmanager._rget_with_confmod(name, path) except KeyError: return None assert mod.__file__ is not None - modpath = Path(mod.__file__).parent - values: list[Path] = [] + modpath = pathlib.Path(mod.__file__).parent + values: list[pathlib.Path] = [] for relroot in relroots: if isinstance(relroot, os.PathLike): - relroot = Path(relroot) + relroot = pathlib.Path(relroot) else: relroot = relroot.replace("/", os.sep) relroot = absolutepath(modpath / relroot) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 13f4fddbddb..99614899994 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -321,6 +321,7 @@ def pytest_ignore_collect( Stops at first non-None result, see :ref:`firstresult`. :param collection_path: The path to analyze. + :type collection_path: pathlib.Path :param path: The path to analyze (deprecated). :param config: The pytest config object. @@ -354,6 +355,7 @@ def pytest_collect_directory(path: Path, parent: Collector) -> Collector | None: Stops at first non-None result, see :ref:`firstresult`. :param path: The path to analyze. + :type path: pathlib.Path See :ref:`custom directory collectors` for a simple example of use of this hook. @@ -386,6 +388,7 @@ def pytest_collect_file( The new node needs to have the specified ``parent`` as a parent. :param file_path: The path to analyze. + :type file_path: pathlib.Path :param path: The path to collect (deprecated). .. versionchanged:: 7.0.0 @@ -507,6 +510,7 @@ def pytest_pycollect_makemodule( Stops at first non-None result, see :ref:`firstresult`. :param module_path: The path of the module to collect. + :type module_path: pathlib.Path :param path: The path of the module to collect (deprecated). .. versionchanged:: 7.0.0 @@ -1026,6 +1030,7 @@ def pytest_report_header( # type:ignore[empty-body] :param config: The pytest config object. :param start_path: The starting dir. + :type start_path: pathlib.Path :param startdir: The starting dir (deprecated). .. note:: @@ -1069,6 +1074,7 @@ def pytest_report_collectionfinish( # type:ignore[empty-body] :param config: The pytest config object. :param start_path: The starting dir. + :type start_path: pathlib.Path :param startdir: The starting dir (deprecated). :param items: List of pytest items that are going to be executed; this list should not be modified. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index a19ddef58fb..47ebad4713d 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -510,6 +510,7 @@ def from_parent( # type: ignore[override] :param parent: The parent collector of this Dir. :param path: The directory's path. + :type path: pathlib.Path """ return super().from_parent(parent=parent, path=path) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index e27648507e9..5c6ce5e889f 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -909,6 +909,7 @@ def mkdir(self, name: str | os.PathLike[str]) -> Path: The name of the directory, relative to the pytester path. :returns: The created directory. + :rtype: pathlib.Path """ p = self.path / name p.mkdir() @@ -932,6 +933,7 @@ def copy_example(self, name: str | None = None) -> Path: The name of the file to copy. :return: Path to the copied directory (inside ``self.path``). + :rtype: pathlib.Path """ example_dir_ = self._request.config.getini("pytester_example_dir") if example_dir_ is None: @@ -1390,8 +1392,10 @@ def run( - Otherwise, it is passed through to :py:class:`subprocess.Popen`. For further information in this case, consult the document of the ``stdin`` parameter in :py:class:`subprocess.Popen`. + :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int :returns: The result. + """ __tracebackhide__ = True diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 2904c3a1e0f..9182ce7dfe9 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -1168,7 +1168,7 @@ def parametrize( If N argnames were specified, argvalues must be a list of N-tuples, where each tuple-element specifies a value for its respective argname. - + :type argvalues: Iterable[_pytest.mark.structures.ParameterSet | Sequence[object] | object] :param indirect: A list of arguments' names (subset of argnames) or a boolean. If True the list contains all names from the argnames. Each diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index bf30a7d2d27..716c4948f4a 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -327,6 +327,7 @@ def from_call( :param func: The function to call. Called without arguments. + :type func: Callable[[], _pytest.runner.TResult] :param when: The phase in which the function is called. :param reraise: diff --git a/tox.ini b/tox.ini index dae89975467..c421a63b4dd 100644 --- a/tox.ini +++ b/tox.ini @@ -81,7 +81,7 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:docs] -basepython = python3 +basepython = python3.9 # sync with rtd to get errors usedevelop = True deps = -r{toxinidir}/doc/en/requirements.txt @@ -92,7 +92,11 @@ commands = -git fetch --unshallow -git fetch --tags - sphinx-build -W --keep-going -b html doc/en doc/en/_build/html {posargs:} + sphinx-build \ + -j auto \ + -W --keep-going \ + -b html doc/en doc/en/_build/html \ + {posargs:} setenv = # Sphinx is not clean of this warning. PYTHONWARNDEFAULTENCODING= From 4e54f19be1d45b2dee778becfe766e90906ef274 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 20 Jun 2024 11:00:21 +0200 Subject: [PATCH 101/199] update tox:docs python to rtd python --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index c421a63b4dd..2cd8c72a62f 100644 --- a/tox.ini +++ b/tox.ini @@ -81,7 +81,7 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:docs] -basepython = python3.9 # sync with rtd to get errors +basepython = python3.12 # sync with rtd to get errors usedevelop = True deps = -r{toxinidir}/doc/en/requirements.txt From 85e451a2cc45315dc39ae905d3f6774bf97d75c0 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Thu, 20 Jun 2024 11:09:48 +0200 Subject: [PATCH 102/199] add changelog entry --- changelog/12467.improvement.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 changelog/12467.improvement.rst diff --git a/changelog/12467.improvement.rst b/changelog/12467.improvement.rst new file mode 100644 index 00000000000..b1e0581ed16 --- /dev/null +++ b/changelog/12467.improvement.rst @@ -0,0 +1,3 @@ +Migrated all internal type-annotations to the python3.10+ style by using the `annotations` future import. + +-- by :user:`RonnyPfannschmidt` From 43815b6fc25368c1f52c450f3d3d995a5384e357 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 22:04:32 +0200 Subject: [PATCH 103/199] =?UTF-8?q?=F0=9F=9A=91=F0=9F=A7=AA=F0=9F=93=9D=20?= =?UTF-8?q?Allow=20invoking=20`git`=20in=20tox?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is called when building the docs. Apparently, `tox -e docs` is not invoked in CI, neither is it called in RTD, resulting in the regression having been caught only in local development environments. This is a follow-up for #12493. --- tox.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tox.ini b/tox.ini index dae89975467..66ce3f24c3e 100644 --- a/tox.ini +++ b/tox.ini @@ -87,6 +87,8 @@ deps = -r{toxinidir}/doc/en/requirements.txt # https://github.com/twisted/towncrier/issues/340 towncrier<21.3.0 +allowlist_externals = + git commands = # Retrieve possibly missing commits: -git fetch --unshallow From dc2568a683202a5f096fcaa06cca83fc11979e17 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 22:09:25 +0200 Subject: [PATCH 104/199] =?UTF-8?q?=F0=9F=9A=91=F0=9F=A7=AA=F0=9F=93=9D=20?= =?UTF-8?q?Stop=20capping=20Towncrier=20@=20local=20docs=20builds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is uncapped in RTD. Apparently, `tox -e docs` is not invoked in CI, neither is it called in RTD, resulting in the regression having been caught only in local development environments. This is a follow-up for #12493. --- tox.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/tox.ini b/tox.ini index 66ce3f24c3e..88708d47bb5 100644 --- a/tox.ini +++ b/tox.ini @@ -85,8 +85,6 @@ basepython = python3 usedevelop = True deps = -r{toxinidir}/doc/en/requirements.txt - # https://github.com/twisted/towncrier/issues/340 - towncrier<21.3.0 allowlist_externals = git commands = From 6a8e9ed43ade6ea60d0cf72819b187613e592354 Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 21 Jun 2024 09:42:24 +0200 Subject: [PATCH 105/199] fixup: Config.cache cannot be None --- src/_pytest/config/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 23a2c47970a..0a96d1a31f5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1024,7 +1024,7 @@ class ArgsSource(enum.Enum): TESTPATHS = enum.auto() # Set by cacheprovider plugin. - cache: Cache | None + cache: Cache def __init__( self, From 0cf92cfa4cd4a0c156dbf3dc0e83b7274996a03b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 18:30:08 +0200 Subject: [PATCH 106/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20comments=20to=20ch?= =?UTF-8?q?ange=20note=20categories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 734c508ca70..895cedb5f0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -372,42 +372,58 @@ directory = "changelog/" title_format = "pytest {version} ({project_date})" template = "changelog/_template.rst" +# NOTE: The types are declared because: +# NOTE: - there is no mechanism to override just the value of +# NOTE: `tool.towncrier.type.misc.showcontent`; +# NOTE: - and, we want to declare extra non-default types for +# NOTE: clarity and flexibility. + [[tool.towncrier.type]] +# When something public gets removed in a breaking way. Could be +# deprecated in an earlier release. directory = "breaking" name = "Breaking Changes" showcontent = true [[tool.towncrier.type]] +# Declarations of future API removals and breaking changes in behavior. directory = "deprecation" name = "Deprecations" showcontent = true [[tool.towncrier.type]] +# New behaviors, public APIs. That sort of stuff. directory = "feature" name = "Features" showcontent = true [[tool.towncrier.type]] +# New behaviors in existing features. directory = "improvement" name = "Improvements" showcontent = true [[tool.towncrier.type]] +# Something we deemed an improper undesired behavior that got corrected +# in the release to match pre-agreed expectations. directory = "bugfix" name = "Bug Fixes" showcontent = true [[tool.towncrier.type]] +# Updates regarding bundling dependencies. directory = "vendor" name = "Vendored Libraries" showcontent = true [[tool.towncrier.type]] +# Notable updates to the documentation structure or build process. directory = "doc" name = "Improved Documentation" showcontent = true [[tool.towncrier.type]] +# Changes that might not even be worth exposing to the end users. directory = "trivial" name = "Trivial/Internal Changes" showcontent = true From 45a89ec61f9a9552236b07cde457ed38a7217e58 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 19:17:40 +0200 Subject: [PATCH 107/199] =?UTF-8?q?=F0=9F=93=9D=20Extend=20change=20log=20?= =?UTF-8?q?title=20meanings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch makes them more verbose so that they are clearer to the readers. --- pyproject.toml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 895cedb5f0e..c850284f4ca 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -382,50 +382,50 @@ template = "changelog/_template.rst" # When something public gets removed in a breaking way. Could be # deprecated in an earlier release. directory = "breaking" -name = "Breaking Changes" +name = "Removals and backward incompatible breaking changes" showcontent = true [[tool.towncrier.type]] # Declarations of future API removals and breaking changes in behavior. directory = "deprecation" -name = "Deprecations" +name = "Deprecations (removal in next major release)" showcontent = true [[tool.towncrier.type]] # New behaviors, public APIs. That sort of stuff. directory = "feature" -name = "Features" +name = "New features" showcontent = true [[tool.towncrier.type]] # New behaviors in existing features. directory = "improvement" -name = "Improvements" +name = "Improvements in existing functionality" showcontent = true [[tool.towncrier.type]] # Something we deemed an improper undesired behavior that got corrected # in the release to match pre-agreed expectations. directory = "bugfix" -name = "Bug Fixes" +name = "Bug fixes" showcontent = true [[tool.towncrier.type]] # Updates regarding bundling dependencies. directory = "vendor" -name = "Vendored Libraries" +name = "Vendored libraries" showcontent = true [[tool.towncrier.type]] # Notable updates to the documentation structure or build process. directory = "doc" -name = "Improved Documentation" +name = "Improved documentation" showcontent = true [[tool.towncrier.type]] # Changes that might not even be worth exposing to the end users. directory = "trivial" -name = "Trivial/Internal Changes" +name = "Trivial/internal changes" showcontent = true [tool.mypy] From 03be1ed8b1794f837dddc7f26aabebe203d90bea Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 19:35:40 +0200 Subject: [PATCH 108/199] =?UTF-8?q?=F0=9F=93=9D=F0=9F=92=85=20Split=20`tri?= =?UTF-8?q?vial`=20change=20log=20category=20into=203?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The new change note types are `packaging`, `contrib` and `misc`. `packaging` is intended for the audience of downstream redistributors. The `contrib` notes are meant to be documenting news affecting the project contributors, their development, and processes. Finally, `misc` is for things that don't fit anywhere but are still desired to be documented for some reason. --- .pre-commit-config.yaml | 37 +++++++++++++++++++++++++++++++++++-- changelog/README.rst | 14 +++++++++++++- pyproject.toml | 20 +++++++++++++++++--- 3 files changed, 65 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 5a3d75ff874..c85175f2928 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,8 +66,41 @@ repos: - id: changelogs-rst name: changelog filenames language: fail - entry: 'changelog files must be named ####.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst' - exclude: changelog/(\d+\.(breaking|bugfix|deprecation|doc|feature|improvement|trivial|vendor).rst|README.rst|_template.rst) + entry: >- + changelog files must be named + ####.( + breaking + | deprecation + | feature + | improvement + | bugfix + | vendor + | doc + | packaging + | contrib + | misc + )(.#)?(.rst)? + exclude: >- + (?x) + ^ + changelog/( + \.gitignore + |\d+\.( + breaking + |deprecation + |feature + |improvement + |bugfix + |vendor + |doc + |packaging + |contrib + |misc + )(\.\d+)?(\.rst)? + |README\.rst + |_template\.rst + ) + $ files: ^changelog/ - id: py-deprecated name: py library is deprecated diff --git a/changelog/README.rst b/changelog/README.rst index 88956ef28d8..fdaa573d427 100644 --- a/changelog/README.rst +++ b/changelog/README.rst @@ -20,10 +20,22 @@ Each file should be named like ``..rst``, where * ``deprecation``: feature deprecation. * ``breaking``: a change which may break existing suites, such as feature removal or behavior change. * ``vendor``: changes in packages vendored in pytest. -* ``trivial``: fixing a small typo or internal change that might be noteworthy. +* ``packaging``: notes for downstreams about unobvious side effects + and tooling. changes in the test invocation considerations and + runtime assumptions. +* ``contrib``: stuff that affects the contributor experience. e.g. + Running tests, building the docs, setting up the development + environment. +* ``misc``: changes that are hard to assign to any of the above + categories. So for example: ``123.feature.rst``, ``456.bugfix.rst``. +.. tip:: + + See :file:`pyproject.toml` for all available categories + (``tool.towncrier.type``). + If your PR fixes an issue, use that number here. If there is no issue, then after you submit the PR and get the PR number you can add a changelog using that instead. diff --git a/pyproject.toml b/pyproject.toml index c850284f4ca..14f69bc3422 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -423,9 +423,23 @@ name = "Improved documentation" showcontent = true [[tool.towncrier.type]] -# Changes that might not even be worth exposing to the end users. -directory = "trivial" -name = "Trivial/internal changes" +# Notes for downstreams about unobvious side effects and tooling. Changes +# in the test invocation considerations and runtime assumptions. +directory = "packaging" +name = "Packaging updates and notes for downstreams" +showcontent = true + +[[tool.towncrier.type]] +# Stuff that affects the contributor experience. e.g. Running tests, +# building the docs, setting up the development environment. +directory = "contrib" +name = "Contributor-facing changes" +showcontent = true + +[[tool.towncrier.type]] +# Changes that are hard to assign to any of the above categories. +directory = "misc" +name = "Miscellaneous internal changes" showcontent = true [tool.mypy] From 3ce1d658bd4e952a28774525ed5235987f1883fc Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 21:59:39 +0200 Subject: [PATCH 109/199] =?UTF-8?q?=F0=9F=8E=A8=20Set=20up=20Git=20to=20on?= =?UTF-8?q?ly=20allow=20certain=20change=20notes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/.gitignore | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 changelog/.gitignore diff --git a/changelog/.gitignore b/changelog/.gitignore new file mode 100644 index 00000000000..3b34da34bc6 --- /dev/null +++ b/changelog/.gitignore @@ -0,0 +1,34 @@ +* +!.gitignore +!_template.rst +!README.rst +!*.bugfix +!*.bugfix.rst +!*.bugfix.*.rst +!*.breaking +!*.breaking.rst +!*.breaking.*.rst +!*.contrib +!*.contrib.rst +!*.contrib.*.rst +!*.deprecation +!*.deprecation.rst +!*.deprecation.*.rst +!*.doc +!*.doc.rst +!*.doc.*.rst +!*.feature +!*.feature.rst +!*.feature.*.rst +!*.improvement +!*.improvement.rst +!*.improvement.*.rst +!*.misc +!*.misc.rst +!*.misc.*.rst +!*.packaging +!*.packaging.rst +!*.packaging.*.rst +!*.vendor +!*.vendor.rst +!*.vendor.*.rst From e73db6864208dba4e6722a208419c7fc730335d5 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 20:06:02 +0200 Subject: [PATCH 110/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PR=20#12501?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12501.contrib.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 changelog/12501.contrib.rst diff --git a/changelog/12501.contrib.rst b/changelog/12501.contrib.rst new file mode 100644 index 00000000000..6f434c287b3 --- /dev/null +++ b/changelog/12501.contrib.rst @@ -0,0 +1,11 @@ +The changelog configuration has been updated to introduce more accurate +audience-tailored categories. Previously, there was a ``trivial`` +change log fragment type with an unclear and broad meaning. It was +removed and we now have ``contrib``, ``misc`` and ``packaging`` in +place of it. + +The new change note types target the readers who are downstream +packagers and project contributors. Additionally, the miscellaneous +section is kept for unspecified updates that do not fit anywhere else. + +-- by :user:`webknjaz` From 323b0bd8535f812da7332afd916aa323f0e6d79d Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 20:14:31 +0200 Subject: [PATCH 111/199] =?UTF-8?q?=F0=9F=A7=AA=20Make=20a=20draft=20based?= =?UTF-8?q?=20plugin=20bump=20PR=20CI=20trigger?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Normally, PRs/commits published using the default GitHub Actions CI/CD API token are not propagated to any integrations. This patch marks the plugin update PR as a draft and leaves a comment asking the maintainers to mark it as ready for review in order to actually trigger a CI run. This idea is found in GitHub's own repos: * https://github.com/github/codeql-action/pull/2263#issuecomment-2078311173 * https://github.com/github/codeql-action/blob/4ebadbc7/.github/workflows/update-dependencies.yml#L38-L41 * https://github.com/github/codeql-action/pull/1868 * https://github.com/github/codeql-action/pull/679 --- .github/workflows/test.yml | 5 +++++ .github/workflows/update-plugin-list.yml | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d16ce0edf93..adc03a8c284 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,6 +14,11 @@ on: branches: - main - "[0-9]+.[0-9]+.x" + types: + - opened # default + - synchronize # default + - reopened # default + - ready_for_review # used in PRs created from the release workflow env: PYTEST_ADDOPTS: "--color=yes" diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 1015d01c9c6..4da55e6d5a2 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -46,6 +46,7 @@ jobs: run: python scripts/update-plugin-list.py - name: Create Pull Request + id: pr uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e with: commit-message: '[automated] Update plugin list' @@ -55,3 +56,13 @@ jobs: branch-suffix: short-commit-hash title: '[automated] Update plugin list' body: '[automated] Update plugin list' + draft: true + + - name: Instruct the maintainers to trigger CI by undrafting the PR + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh pr comment + --body 'Please mark the PR as ready for review to trigger PR checks.' + --repo '${{ github.repository }}' + '${{ steps.pr.outputs.pull-request-number }}' From 072cb5250e23073deb2c0bbe94419810e605e54f Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 20:22:29 +0200 Subject: [PATCH 112/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PR=20#12502?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12502.contrib.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/12502.contrib.rst diff --git a/changelog/12502.contrib.rst b/changelog/12502.contrib.rst new file mode 100644 index 00000000000..a7db422526f --- /dev/null +++ b/changelog/12502.contrib.rst @@ -0,0 +1,7 @@ +The UX of the GitHub automation making pull requests to update the +plugin list has been updated. Previously, the maintainers had to close +the automatically created pull requests and re-open them to trigger the +CI runs. From now on, they only need to click the `Ready for review` +button instead. + +-- by :user:`webknjaz`. From a69230ea5ff6d9f584713b2cf411bd0642debd87 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 16:51:50 +0200 Subject: [PATCH 113/199] =?UTF-8?q?=F0=9F=8E=A8=20Add=20descriptions=20to?= =?UTF-8?q?=20all=20`tox`=20environments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, a part of the environments weren't documented in the config, making it difficult for the newbies to figure out what their purposes are. This patch sets the descriptions for all the envs listed with the `tox -av` command, leveraging the dynamic factor-dependent explanation fragments. ```console default environments: linting -> run pre-commit-defined linters under `python3` py38 -> run the tests under `py38` py39 -> run the tests under `py39` py310 -> run the tests under `py310` py311 -> run the tests under `py311` py312 -> run the tests under `py312` py313 -> run the tests under `py313` pypy3 -> run the tests under `pypy3` py38-pexpect -> run the tests against `pexpect` under `py38` py38-xdist -> run the tests with pytest in parallel mode under `py38` py38-unittestextras -> run the tests against the unit test extras under `py38` py38-numpy -> run the tests against `numpy` under `py38` py38-pluggymain -> run the tests against the bleeding edge `pluggy` from Git under `py38` py38-pylib -> run the tests against `py` lib under `py38` doctesting -> run the tests under `~/.pyenv/versions/3.12.3/envs/pytest-pyenv-py3.12.3/bin/python` including doctests doctesting-coverage -> run the tests collecting coverage under `~/.pyenv/versions/3.12.3/envs/pytest-pyenv-py3.12.3/bin/python` including doctests plugins -> run reverse dependency testing against pytest plugins under `~/.pyenv/versions/3.12.3/envs/pytest-pyenv-py3.12.3/bin/python` py38-freeze -> test pytest frozen with `pyinstaller` under `py38` docs -> build the documentation site under `~/src/github/pytest-dev/pytest/doc/en/_build/html` with `python3` docs-checklinks -> check the links in the documentation with `python3` py311-exceptiongroup -> run the tests against `exceptiongroup` under `py311` additional environments: regen -> regenerate documentation examples under `python3` release -> do a release, required posarg of the version number prepare-release-pr -> prepare a release PR from a manual trigger in GitHub actions generate-gh-release-notes -> generate release notes that can be published as GitHub Release nobyte -> run the tests in no-bytecode mode under `~/.pyenv/versions/3.12.3/envs/pytest-pyenv-py3.12.3/bin/python` lsof -> run the tests with `--lsof` pytest CLI option under `~/.pyenv/versions/3.12.3/envs/pytest-pyenv-py3.12.3/bin/python` ``` --- tox.ini | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tox.ini b/tox.ini index ec74fc94b0d..61563ca2c5f 100644 --- a/tox.ini +++ b/tox.ini @@ -26,6 +26,20 @@ envlist = [testenv] +description = + run the tests + coverage: collecting coverage + exceptiongroup: against `exceptiongroup` + nobyte: in no-bytecode mode + lsof: with `--lsof` pytest CLI option + numpy: against `numpy` + pexpect: against `pexpect` + pluggymain: against the bleeding edge `pluggy` from Git + pylib: against `py` lib + unittestextras: against the unit test extras + xdist: with pytest in parallel mode + under `{basepython}` + doctesting: including doctests commands = {env:_PYTEST_TOX_COVERAGE_RUN:} pytest {posargs:{env:_PYTEST_TOX_DEFAULT_POSARGS:}} doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest @@ -72,6 +86,8 @@ deps = {env:_PYTEST_TOX_EXTRA_DEP:} [testenv:linting] +description = + run pre-commit-defined linters under `{basepython}` skip_install = True basepython = python3 deps = pre-commit>=2.9.3 @@ -81,6 +97,9 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:docs] +description = + build the documentation site under \ + `{toxinidir}{/}doc{/}en{/}_build{/}html` with `{basepython}` basepython = python3.12 # sync with rtd to get errors usedevelop = True deps = @@ -102,6 +121,8 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:docs-checklinks] +description = + check the links in the documentation with `{basepython}` basepython = python3 usedevelop = True changedir = doc/en @@ -113,6 +134,8 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:regen] +description = + regenerate documentation examples under `{basepython}` changedir = doc/en basepython = python3 passenv = @@ -130,6 +153,8 @@ setenv = PYTHONWARNDEFAULTENCODING= [testenv:plugins] +description = + run reverse dependency testing against pytest plugins under `{basepython}` # use latest versions of all plugins, including pre-releases pip_pre=true # use latest pip to get new dependency resolver (#7783) @@ -154,6 +179,8 @@ commands = pytest simple_integration.py --force-sugar --flakes [testenv:py38-freeze] +description = + test pytest frozen with `pyinstaller` under `{basepython}` changedir = testing/freeze deps = pyinstaller From a67327ac8ebf04bde81ac7d9bccc3d70f56c331d Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Thu, 20 Jun 2024 17:12:57 +0200 Subject: [PATCH 114/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20to=20PR=20#12498?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12498.contrib.rst | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelog/12498.contrib.rst diff --git a/changelog/12498.contrib.rst b/changelog/12498.contrib.rst new file mode 100644 index 00000000000..436c6f0e9ed --- /dev/null +++ b/changelog/12498.contrib.rst @@ -0,0 +1,5 @@ +All the undocumented ``tox`` environments now have descriptions. +They can be listed in one's development environment by invoking +``tox -av`` in a terminal. + +-- by :user:`webknjaz` From 5d95f09b2acc8a1605aee64784c466ff409828df Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Fri, 21 Jun 2024 15:03:36 +0200 Subject: [PATCH 115/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PR=20#12493?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12493.contrib.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 changelog/12493.contrib.rst diff --git a/changelog/12493.contrib.rst b/changelog/12493.contrib.rst new file mode 100644 index 00000000000..db3d045697e --- /dev/null +++ b/changelog/12493.contrib.rst @@ -0,0 +1,13 @@ +The change log draft preview integration has been refactored to use a +third party extension ``sphinxcontib-towncrier``. The previous in-repo +script was putting the change log preview file at +:file:`doc/en/_changelog_towncrier_draft.rst`. Said file is no longer +ignored in Git and might show up among untracked files in the +development environments of the contributors. To address that, the +contributors can run the following command that will clean it up: + +.. code-block:: console + + $ git clean -x -i -- doc/en/_changelog_towncrier_draft.rst + +-- by :user:`webknjaz` From 9947ec3ad1a0fb9f85b092a50b0866b3870e37ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Fri, 21 Jun 2024 17:47:23 +0200 Subject: [PATCH 116/199] =?UTF-8?q?=F0=9F=A7=AA=F0=9F=9A=91=20Pass=20a=20C?= =?UTF-8?q?odecov=20config=20to=20the=20action=20@=20GHA=20(#12508)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The #11921 update broke uploading coverage of the `main` branch (or any in-repo pushes for that matter) to Codecov 4 months ago. Version 4 requires an upload token to be provided and since there was no configuration for it, the upload was failing. But the step itself was showing up as successful due to `fail_ci_if_error: true` being set. The error is visible in the console output, though. This patch flips the setting to `fail_ci_if_error: false` and sets the Codecov upload token in the config in clear text. The non-secret part allows the PRs uploads to be more stable. Co-authored-by: Ronny Pfannschmidt --- .github/workflows/test.yml | 3 +-- codecov.yml | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index adc03a8c284..ab4ea3db75c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -247,9 +247,8 @@ jobs: - name: Upload coverage to Codecov if: "matrix.use_coverage" uses: codecov/codecov-action@v4 - continue-on-error: true with: - fail_ci_if_error: true + fail_ci_if_error: false files: ./coverage.xml verbose: true diff --git a/codecov.yml b/codecov.yml index f1cc8697338..0841ab049ff 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,9 @@ # reference: https://docs.codecov.io/docs/codecovyml-reference +--- + +codecov: + token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token + coverage: status: patch: true From 34e28295a7bf0bdd37a5d7620ad8bfe981accb4e Mon Sep 17 00:00:00 2001 From: Farbod Ahmadian Date: Fri, 21 Jun 2024 18:20:44 +0200 Subject: [PATCH 117/199] refactor: simplify bound method representation (#12492) Co-authored-by: Sviatoslav Sydorenko Co-authored-by: Farbod Ahmadian --- AUTHORS | 1 + changelog/389.improvement.rst | 38 ++++++++++++++++++++++++++++++++ src/_pytest/_io/saferepr.py | 1 - src/_pytest/assertion/rewrite.py | 4 ++++ testing/test_assertrewrite.py | 29 +++++++++++++++++++++++- 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 changelog/389.improvement.rst diff --git a/AUTHORS b/AUTHORS index 347efad57b3..4d34d2ba9b4 100644 --- a/AUTHORS +++ b/AUTHORS @@ -149,6 +149,7 @@ Evgeny Seliverstov Fabian Sturm Fabien Zarifian Fabio Zadrozny +Farbod Ahmadian faph Felix Hofstätter Felix Nieuwenhuizen diff --git a/changelog/389.improvement.rst b/changelog/389.improvement.rst new file mode 100644 index 00000000000..f8e2c19fde0 --- /dev/null +++ b/changelog/389.improvement.rst @@ -0,0 +1,38 @@ +The readability of assertion introspection of bound methods has been enhanced +-- by :user:`farbodahm`, :user:`webknjaz`, :user:`obestwalter`, :user:`flub` +and :user:`glyphack`. + +Earlier, it was like: + +.. code-block:: console + + =================================== FAILURES =================================== + _____________________________________ test _____________________________________ + + def test(): + > assert Help().fun() == 2 + E assert 1 == 2 + E + where 1 = >() + E + where > = .fun + E + where = Help() + + example.py:7: AssertionError + =========================== 1 failed in 0.03 seconds =========================== + + +And now it's like: + +.. code-block:: console + + =================================== FAILURES =================================== + _____________________________________ test _____________________________________ + + def test(): + > assert Help().fun() == 2 + E assert 1 == 2 + E + where 1 = fun() + E + where fun = .fun + E + where = Help() + + test_local.py:13: AssertionError + =========================== 1 failed in 0.03 seconds =========================== diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index 5ace418227d..13b793f0a77 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -60,7 +60,6 @@ def repr(self, x: object) -> str: s = ascii(x) else: s = super().repr(x) - except (KeyboardInterrupt, SystemExit): raise except BaseException as exc: diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 442fc5d9f30..bfcbcbd3f8d 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -417,6 +417,10 @@ def _saferepr(obj: object) -> str: sequences, especially '\n{' and '\n}' are likely to be present in JSON reprs. """ + if isinstance(obj, types.MethodType): + # for bound methods, skip redundant information + return obj.__name__ + maxsize = _get_maxsize_for_saferepr(util._config) return saferepr(obj, maxsize=maxsize).replace("\n", "\\n") diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 185cd5ef2eb..5ee40ee6568 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -10,6 +10,7 @@ import os from pathlib import Path import py_compile +import re import stat import sys import textwrap @@ -24,6 +25,7 @@ from _pytest.assertion import util from _pytest.assertion.rewrite import _get_assertion_exprs from _pytest.assertion.rewrite import _get_maxsize_for_saferepr +from _pytest.assertion.rewrite import _saferepr from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.assertion.rewrite import get_cache_dir from _pytest.assertion.rewrite import PYC_TAIL @@ -2036,7 +2038,9 @@ def test_foo(): assert test_foo_pyc.is_file() # normal file: not touched by pytest, normal cache tag - bar_init_pyc = get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc" + bar_init_pyc = ( + get_cache_dir(bar_init) / f"__init__.{sys.implementation.cache_tag}.pyc" + ) assert bar_init_pyc.is_file() @@ -2103,3 +2107,26 @@ def test_foo(): ) result = pytester.runpytest() assert result.ret == 0 + + +class TestSafereprUnbounded: + class Help: + def bound_method(self): # pragma: no cover + pass + + def test_saferepr_bound_method(self): + """saferepr() of a bound method should show only the method name""" + assert _saferepr(self.Help().bound_method) == "bound_method" + + def test_saferepr_unbounded(self): + """saferepr() of an unbound method should still show the full information""" + obj = self.Help() + # using id() to fetch memory address fails on different platforms + pattern = re.compile( + rf"<{Path(__file__).stem}.{self.__class__.__name__}.Help object at 0x[0-9a-fA-F]*>", + ) + assert pattern.match(_saferepr(obj)) + assert ( + _saferepr(self.Help) + == f"" + ) From 27b255028320ac3837893298012d437cec0354ea Mon Sep 17 00:00:00 2001 From: pytest bot Date: Fri, 21 Jun 2024 13:51:03 +0000 Subject: [PATCH 118/199] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 76 ++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 83d074e7f03..48f7cf9722e 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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 @@ -125,7 +126,7 @@ This list contains 1477 plugins. :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-aux` templates/examples and aux for pytest Jun 10, 2024 N/A N/A + :pypi:`pytest-aux` templates/examples and aux for pytest Jun 18, 2024 N/A N/A :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 @@ -150,7 +151,7 @@ This list contains 1477 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 12, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 21, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -236,7 +237,7 @@ This list contains 1477 plugins. :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-cleanslate` Collects and executes pytest tests separately May 30, 2024 N/A pytest + :pypi:`pytest-cleanslate` Collects and executes pytest tests separately Jun 17, 2024 N/A pytest :pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A :pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -304,7 +305,7 @@ This list contains 1477 plugins. :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-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail May 30, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-custom-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. Jun 17, 2024 4 - Beta pytest>=6.2.0 :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 Apr 05, 2024 5 - Production/Stable pytest>=8 @@ -487,7 +488,7 @@ This list contains 1477 plugins. :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7 :pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9) :pypi:`pytest-exasol-saas` Jun 07, 2024 N/A pytest<9,>=7 - :pypi:`pytest-excel` pytest plugin for generating excel reports Sep 14, 2023 5 - Production/Stable N/A + :pypi:`pytest-excel` pytest plugin for generating excel reports Jun 18, 2024 5 - Production/Stable pytest>3.6 :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 Oct 07, 2023 N/A pytest <8,>=5 @@ -636,7 +637,7 @@ This list contains 1477 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 12, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 16, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -724,7 +725,7 @@ This list contains 1477 plugins. :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-ctrf` Pytest plugin to generate json report in CTRF (Common Test Report Format) May 21, 2024 N/A pytest>6.0.0 + :pypi:`pytest-json-ctrf` Pytest plugin to generate json report in CTRF (Common Test Report Format) Jun 15, 2024 N/A pytest>6.0.0 :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) @@ -839,7 +840,7 @@ This list contains 1477 plugins. :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. Apr 11, 2024 N/A pytest>=1.0 + :pypi:`pytest-mock-resources` A pytest plugin for easily instantiating reproducible mock resources. Jun 20, 2024 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 @@ -886,7 +887,7 @@ This list contains 1477 plugins. :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 11, 2024 N/A pytest<9.0.0,>=8.2.0 + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Jun 18, 2024 N/A pytest<9.0.0,>=8.2.0 :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 Apr 11, 2024 N/A N/A @@ -1016,7 +1017,7 @@ This list contains 1477 plugins. :pypi:`pytest-proceed` Apr 10, 2024 N/A pytest :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-progress` pytest plugin for instant test progress status Jun 18, 2024 5 - Production/Stable pytest>=2.7 :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 @@ -1086,7 +1087,7 @@ This list contains 1477 plugins. :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 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. Jun 10, 2024 5 - Production/Stable pytest>=6.2 + :pypi:`pytest-redis` Redis fixtures and fixture factories for Pytest. Jun 19, 2024 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) @@ -1198,7 +1199,7 @@ This list contains 1477 plugins. :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 Dec 19, 2023 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 May 09, 2024 3 - Alpha pytest>=6.2 + :pypi:`pytest-servers` pytest servers Jun 17, 2024 3 - Alpha pytest>=6.2 :pypi:`pytest-service` May 11, 2024 5 - Production/Stable pytest>=6.0.0 :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 @@ -1261,7 +1262,7 @@ This list contains 1477 plugins. :pypi:`pytest-spiratest` Exports unit tests as test runs in Spira (SpiraTest/Team/Plan) Jan 01, 2024 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 Feb 01, 2024 6 - Mature pytest >=8.0.0 - :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Jan 29, 2024 4 - Beta pytest (>=5,<9) + :pypi:`pytest-split` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Jun 19, 2024 4 - Beta pytest<9,>=5 :pypi:`pytest-split-ext` Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. Sep 23, 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) @@ -1280,7 +1281,7 @@ This list contains 1477 plugins. :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 20, 2024 N/A N/A - :pypi:`pytest-static` pytest-static Jan 15, 2024 1 - Planning pytest (>=7.4.3,<8.0.0) + :pypi:`pytest-static` pytest-static Jun 20, 2024 1 - Planning pytest<8.0.0,>=7.4.3 :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 @@ -1978,6 +1979,13 @@ This list contains 1477 plugins. Useful assertion utilities for use with pytest + :pypi:`pytest-assist` + *last release*: Jun 21, 2024, + *status*: N/A, + *requires*: pytest + + load testing library + :pypi:`pytest-assume` *last release*: Jun 24, 2021, *status*: N/A, @@ -2154,7 +2162,7 @@ This list contains 1477 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. :pypi:`pytest-aux` - *last release*: Jun 10, 2024, + *last release*: Jun 18, 2024, *status*: N/A, *requires*: N/A @@ -2329,7 +2337,7 @@ This list contains 1477 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Jun 12, 2024, + *last release*: Jun 21, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2931,7 +2939,7 @@ This list contains 1477 plugins. Easy quality control for CLDF datasets using pytest :pypi:`pytest-cleanslate` - *last release*: May 30, 2024, + *last release*: Jun 17, 2024, *status*: N/A, *requires*: pytest @@ -3407,11 +3415,11 @@ This list contains 1477 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report :pypi:`pytest-custom-outputs` - *last release*: May 30, 2024, + *last release*: Jun 17, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 - A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail + A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. :pypi:`pytest-custom-report` *last release*: Jan 30, 2019, @@ -4688,9 +4696,9 @@ This list contains 1477 plugins. :pypi:`pytest-excel` - *last release*: Sep 14, 2023, + *last release*: Jun 18, 2024, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest>3.6 pytest plugin for generating excel reports @@ -5731,7 +5739,7 @@ This list contains 1477 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Jun 12, 2024, + *last release*: Jun 16, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 @@ -6347,7 +6355,7 @@ This list contains 1477 plugins. Generate JSON test reports :pypi:`pytest-json-ctrf` - *last release*: May 21, 2024, + *last release*: Jun 15, 2024, *status*: N/A, *requires*: pytest>6.0.0 @@ -7152,7 +7160,7 @@ This list contains 1477 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*: Apr 11, 2024, + *last release*: Jun 20, 2024, *status*: N/A, *requires*: pytest>=1.0 @@ -7481,7 +7489,7 @@ This list contains 1477 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: Jun 11, 2024, + *last release*: Jun 18, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.2.0 @@ -8391,9 +8399,9 @@ This list contains 1477 plugins. Profiling plugin for py.test :pypi:`pytest-progress` - *last release*: Jan 31, 2022, + *last release*: Jun 18, 2024, *status*: 5 - Production/Stable, - *requires*: N/A + *requires*: pytest>=2.7 pytest plugin for instant test progress status @@ -8881,7 +8889,7 @@ This list contains 1477 plugins. Provides pytest plugins for reporting request/response traffic, screenshots, and more to ReportPortal :pypi:`pytest-redis` - *last release*: Jun 10, 2024, + *last release*: Jun 19, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=6.2 @@ -9665,7 +9673,7 @@ This list contains 1477 plugins. Automatically mocks resources from serverless.yml in pytest using moto. :pypi:`pytest-servers` - *last release*: May 09, 2024, + *last release*: Jun 17, 2024, *status*: 3 - Alpha, *requires*: pytest>=6.2 @@ -10106,9 +10114,9 @@ This list contains 1477 plugins. Pytest plugin for the splinter automation library :pypi:`pytest-split` - *last release*: Jan 29, 2024, + *last release*: Jun 19, 2024, *status*: 4 - Beta, - *requires*: pytest (>=5,<9) + *requires*: pytest<9,>=5 Pytest plugin which splits the test suite to equally sized sub suites based on test execution time. @@ -10239,9 +10247,9 @@ This list contains 1477 plugins. A package to prevent Dependency Confusion attacks against Yandex. :pypi:`pytest-static` - *last release*: Jan 15, 2024, + *last release*: Jun 20, 2024, *status*: 1 - Planning, - *requires*: pytest (>=7.4.3,<8.0.0) + *requires*: pytest<8.0.0,>=7.4.3 pytest-static From 15c33fbaa3e8569af0efa7532ac59f5f0fb3ba4a Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Thu, 20 Jun 2024 23:09:14 +0200 Subject: [PATCH 119/199] feat: support keyword arguments in marker expressions Fixes #12281 --- src/_pytest/mark/__init__.py | 34 +++++++-- src/_pytest/mark/expression.py | 110 ++++++++++++++++++++++++--- testing/test_mark.py | 48 ++++++++++++ testing/test_mark_expression.py | 129 +++++++++++++++++++++++++++++++- 4 files changed, 304 insertions(+), 17 deletions(-) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index b8a3092151f..ae6940a6256 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -2,6 +2,7 @@ from __future__ import annotations +import collections import dataclasses from typing import AbstractSet from typing import Collection @@ -181,7 +182,9 @@ def from_item(cls, item: Item) -> KeywordMatcher: return cls(mapped_names) - def __call__(self, subname: str) -> bool: + def __call__(self, subname: str, /, **kwargs: object) -> bool: + if kwargs: + raise UsageError("Keyword expressions do not support call parameters.") subname = subname.lower() names = (name.lower() for name in self._names) @@ -211,6 +214,9 @@ def deselect_by_keyword(items: list[Item], config: Config) -> None: items[:] = remaining +NOT_NONE_SENTINEL = object() + + @dataclasses.dataclass class MarkMatcher: """A matcher for markers which are present. @@ -218,17 +224,31 @@ class MarkMatcher: Tries to match on any marker names, attached to the given colitem. """ - __slots__ = ("own_mark_names",) + __slots__ = ("own_mark_name_mapping",) - own_mark_names: AbstractSet[str] + own_mark_name_mapping: dict[str, list[Mark]] @classmethod def from_item(cls, item: Item) -> MarkMatcher: - mark_names = {mark.name for mark in item.iter_markers()} - return cls(mark_names) + mark_name_mapping = collections.defaultdict(list) + for mark in item.iter_markers(): + mark_name_mapping[mark.name].append(mark) + return cls(mark_name_mapping) + + def __call__(self, name: str, /, **kwargs: object) -> bool: + if not (matches := self.own_mark_name_mapping.get(name, [])): + return False + + if not kwargs: + return True - def __call__(self, name: str) -> bool: - return name in self.own_mark_names + for mark in matches: + if all( + mark.kwargs.get(k, NOT_NONE_SENTINEL) == v for k, v in kwargs.items() + ): + return True + + return False def deselect_by_mark(items: list[Item], config: Config) -> None: diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index e65b028589b..16883c6b7f5 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -5,7 +5,8 @@ expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident +not_expr: 'not' not_expr | '(' expr ')' | ident ( '(' name '=' value ( ', ' name '=' value )* ')')* + ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ The semantics are: @@ -20,12 +21,13 @@ import ast import dataclasses import enum +import keyword import re import types -from typing import Callable from typing import Iterator from typing import Mapping from typing import NoReturn +from typing import Protocol from typing import Sequence @@ -43,6 +45,9 @@ class TokenType(enum.Enum): NOT = "not" IDENT = "identifier" EOF = "end of input" + EQUAL = "=" + STRING = "str" + COMMA = "," @dataclasses.dataclass(frozen=True) @@ -86,6 +91,27 @@ def lex(self, input: str) -> Iterator[Token]: elif input[pos] == ")": yield Token(TokenType.RPAREN, ")", pos) pos += 1 + elif input[pos] == "=": + yield Token(TokenType.EQUAL, "=", pos) + pos += 1 + elif input[pos] == ",": + yield Token(TokenType.COMMA, ",", pos) + pos += 1 + elif (quote_char := input[pos]) == "'" or input[pos] == '"': + quote_position = input[pos + 1 :].find(quote_char) + if quote_position == -1: + raise ParseError( + pos + 1, + f'closing quote "{quote_char}" is missing', + ) + value = input[pos : pos + 2 + quote_position] + if "\\" in value: + raise ParseError( + pos + 1, + "escaping not supported in marker expression", + ) + yield Token(TokenType.STRING, value, pos) + pos += len(value) else: match = re.match(r"(:?\w|:|\+|-|\.|\[|\]|\\|/)+", input[pos:]) if match: @@ -166,18 +192,84 @@ def not_expr(s: Scanner) -> ast.expr: return ret ident = s.accept(TokenType.IDENT) if ident: - return ast.Name(IDENT_PREFIX + ident.value, ast.Load()) + name = ast.Name(IDENT_PREFIX + ident.value, ast.Load()) + if s.accept(TokenType.LPAREN): + ret = ast.Call(func=name, args=[], keywords=all_kwargs(s)) + s.accept(TokenType.RPAREN, reject=True) + else: + ret = name + return ret + s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT)) -class MatcherAdapter(Mapping[str, bool]): +BUILTIN_MATCHERS = {"True": True, "False": False, "None": None} + + +def single_kwarg(s: Scanner) -> ast.keyword: + keyword_name = s.accept(TokenType.IDENT, reject=True) + assert keyword_name is not None # for mypy + if not keyword_name.value.isidentifier() or keyword.iskeyword(keyword_name.value): + raise ParseError( + keyword_name.pos + 1, + f'unexpected character/s "{keyword_name.value}"', + ) + s.accept(TokenType.EQUAL, reject=True) + + if value_token := s.accept(TokenType.STRING): + value: str | int | bool | None = value_token.value[1:-1] # strip quotes + else: + value_token = s.accept(TokenType.IDENT, reject=True) + assert value_token is not None # for mypy + if ( + (number := value_token.value).isdigit() + or number.startswith("-") + and number[1:].isdigit() + ): + value = int(number) + elif value_token.value in BUILTIN_MATCHERS: + value = BUILTIN_MATCHERS[value_token.value] + else: + raise ParseError( + value_token.pos + 1, + f'unexpected character/s "{value_token.value}"', + ) + + ret = ast.keyword(keyword_name.value, ast.Constant(value)) + return ret + + +def all_kwargs(s: Scanner) -> list[ast.keyword]: + ret = [single_kwarg(s)] + while s.accept(TokenType.COMMA): + ret.append(single_kwarg(s)) + return ret + + +class MatcherCall(Protocol): + def __call__(self, name: str, /, **kwargs: object) -> bool: ... + + +@dataclasses.dataclass +class MatcherNameAdapter: + matcher: MatcherCall + name: str + + def __bool__(self) -> bool: + return self.matcher(self.name) + + def __call__(self, **kwargs: object) -> bool: + return self.matcher(self.name, **kwargs) + + +class MatcherAdapter(Mapping[str, MatcherNameAdapter]): """Adapts a matcher function to a locals mapping as required by eval().""" - def __init__(self, matcher: Callable[[str], bool]) -> None: + def __init__(self, matcher: MatcherCall) -> None: self.matcher = matcher - def __getitem__(self, key: str) -> bool: - return self.matcher(key[len(IDENT_PREFIX) :]) + def __getitem__(self, key: str) -> MatcherNameAdapter: + return MatcherNameAdapter(matcher=self.matcher, name=key[len(IDENT_PREFIX) :]) def __iter__(self) -> Iterator[str]: raise NotImplementedError() @@ -211,7 +303,7 @@ def compile(self, input: str) -> Expression: ) return Expression(code) - def evaluate(self, matcher: Callable[[str], bool]) -> bool: + def evaluate(self, matcher: MatcherCall) -> bool: """Evaluate the match expression. :param matcher: @@ -220,5 +312,5 @@ def evaluate(self, matcher: Callable[[str], bool]) -> bool: :returns: Whether the expression matches or not. """ - ret: bool = eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher)) + ret: bool = bool(eval(self.code, {"__builtins__": {}}, MatcherAdapter(matcher))) return ret diff --git a/testing/test_mark.py b/testing/test_mark.py index 090e10ee9c4..721bb71d3da 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -233,6 +233,54 @@ def test_two(): assert passed_str == expected_passed +@pytest.mark.parametrize( + ("expr", "expected_passed"), + [ # TODO: improve/sort out + ("car(color='red')", ["test_one"]), + ("car(color='red') or car(color='blue')", ["test_one", "test_two"]), + ("car and not car(temp=5)", ["test_one", "test_three"]), + ("car(temp=4)", ["test_one"]), + ("car(temp=4) or car(temp=5)", ["test_one", "test_two"]), + ("car(temp=4) and car(temp=5)", []), + ("car(temp=-5)", ["test_three"]), + ("car(ac=True)", ["test_one"]), + ("car(ac=False)", ["test_two"]), + ("car(ac=None)", ["test_three"]), # test NOT_NONE_SENTINEL + ], + ids=str, +) +def test_mark_option_with_kwargs( + expr: str, expected_passed: list[str | None], pytester: Pytester +) -> None: + pytester.makepyfile( + """ + import pytest + @pytest.mark.car + @pytest.mark.car(ac=True) + @pytest.mark.car(temp=4) + @pytest.mark.car(color="red") + def test_one(): + pass + @pytest.mark.car + @pytest.mark.car(ac=False) + @pytest.mark.car(temp=5) + @pytest.mark.car(color="blue") + def test_two(): + pass + @pytest.mark.car + @pytest.mark.car(ac=None) + @pytest.mark.car(temp=-5) + def test_three(): + pass + + """ + ) + rec = pytester.inline_run("-m", expr) + passed, skipped, fail = rec.listoutcomes() + passed_str = [x.nodeid.split("::")[-1] for x in passed] + assert passed_str == expected_passed + + @pytest.mark.parametrize( ("expr", "expected_passed"), [("interface", ["test_interface"]), ("not interface", ["test_nointer"])], diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 5bce004cb1c..0c1e73809f7 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -1,14 +1,19 @@ from __future__ import annotations +import collections from typing import Callable +from typing import cast +from _pytest.mark import MarkMatcher +from _pytest.mark import structures from _pytest.mark.expression import Expression +from _pytest.mark.expression import MatcherCall from _pytest.mark.expression import ParseError import pytest def evaluate(input: str, matcher: Callable[[str], bool]) -> bool: - return Expression.compile(input).evaluate(matcher) + return Expression.compile(input).evaluate(cast(MatcherCall, matcher)) def test_empty_is_false() -> None: @@ -153,6 +158,8 @@ def test_syntax_errors(expr: str, column: int, message: str) -> None: "1234", "1234abcd", "1234and", + "1234or", + "1234not", "notandor", "not_and_or", "not[and]or", @@ -195,3 +202,123 @@ def test_valid_idents(ident: str) -> None: def test_invalid_idents(ident: str) -> None: with pytest.raises(ParseError): evaluate(ident, lambda ident: True) + + +@pytest.mark.parametrize( + "expr, expected_error_msg", + ( + ("mark(1=2)", 'unexpected character/s "1"'), + ("mark(/=2)", 'unexpected character/s "/"'), + ("mark(True=False)", 'unexpected character/s "True"'), + ("mark(def=False)", 'unexpected character/s "def"'), + ("mark(class=False)", 'unexpected character/s "class"'), + ("mark(if=False)", 'unexpected character/s "if"'), + ("mark(else=False)", 'unexpected character/s "else"'), + ("mark(1)", 'unexpected character/s "1"'), + ("mark(var:=False", 'unexpected character/s "var:"'), + ("mark(valid=False, def=1)", 'unexpected character/s "def"'), + ("mark(var==", "expected identifier; got ="), + ("mark(var=none)", 'unexpected character/s "none"'), + ("mark(var=1.1)", 'unexpected character/s "1.1"'), + ("mark(var)", "expected =; got right parenthesis"), + ("mark(var=')", """closing quote "'" is missing"""), + ('mark(var=")', 'closing quote """ is missing'), + ("""mark(var="')""", 'closing quote """ is missing'), + ("""mark(var='")""", """closing quote "'" is missing"""), + (r"mark(var='\hugo')", "escaping not supported in marker expression"), + ), +) +def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? + expr: str, expected_error_msg: str, mark_matcher: MarkMatcher +) -> None: + with pytest.raises(ParseError, match=expected_error_msg): + assert evaluate(expr, mark_matcher) + + +@pytest.fixture(scope="session") +def mark_matcher() -> MarkMatcher: + markers = [] + mark_name_mapping = collections.defaultdict(list) + + def create_marker(name: str, kwargs: dict[str, object]) -> structures.Mark: + return structures.Mark(name=name, args=tuple(), kwargs=kwargs, _ispytest=True) + + markers.append(create_marker("number_mark", {"a": 1, "b": 2, "c": 3, "d": 999_999})) + markers.append( + create_marker("builtin_matchers_mark", {"x": True, "y": False, "z": None}) + ) + markers.append( + create_marker( + "str_mark", + {"m": "M", "space": "with space", "aaאבגדcc": "aaאבגדcc", "אבגד": "אבגד"}, + ) + ) + + for marker in markers: + mark_name_mapping[marker.name].append(marker) + + return MarkMatcher(mark_name_mapping) + + +@pytest.mark.parametrize( + "expr, expected", + ( + # happy cases + ("number_mark(a=1)", True), + ("number_mark(b=2)", True), + ("number_mark(a=1,b=2)", True), + ("number_mark(a=1, b=2)", True), + ("number_mark(d=999999)", True), + ("number_mark(a = 1,b= 2, c = 3)", True), + # sad cases + ("number_mark(a=6)", False), + ("number_mark(b=6)", False), + ("number_mark(a=1,b=6)", False), + ("number_mark(a=6,b=2)", False), + ("number_mark(a = 1,b= 2, c = 6)", False), + ("number_mark(a='1')", False), + ), +) +def test_keyword_expressions_with_numbers( + expr: str, expected: bool, mark_matcher: MarkMatcher +) -> None: + assert evaluate(expr, mark_matcher) is expected + + +@pytest.mark.parametrize( + "expr, expected", + ( + ("builtin_matchers_mark(x=True)", True), + ("builtin_matchers_mark(x=False)", False), + ("builtin_matchers_mark(y=True)", False), + ("builtin_matchers_mark(y=False)", True), + ("builtin_matchers_mark(z=None)", True), + ("builtin_matchers_mark(z=False)", False), + ("builtin_matchers_mark(z=True)", False), + ("builtin_matchers_mark(z=0)", False), + ("builtin_matchers_mark(z=1)", False), + ), +) +def test_builtin_matchers_keyword_expressions( # TODO: naming when decided + expr: str, expected: bool, mark_matcher: MarkMatcher +) -> None: + assert evaluate(expr, mark_matcher) is expected + + +@pytest.mark.parametrize( + "expr, expected", + ( + ("str_mark(m='M')", True), + ('str_mark(m="M")', True), + ("str_mark(aaאבגדcc='aaאבגדcc')", True), + ("str_mark(אבגד='אבגד')", True), + ("str_mark(space='with space')", True), + ("str_mark(m='wrong')", False), + ("str_mark(aaאבגדcc='wrong')", False), + ("str_mark(אבגד='wrong')", False), + ), +) +def test_str_keyword_expressions( + expr: str, expected: bool, mark_matcher: MarkMatcher +) -> None: + assert evaluate(expr, mark_matcher) is expected From 04f457c4f4fb38ea7ba8c88cd3a15fdc83722840 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Thu, 20 Jun 2024 23:05:22 +0200 Subject: [PATCH 120/199] style: use `@overload` to get rid of mypy only assertions --- src/_pytest/mark/expression.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 16883c6b7f5..39d262c801a 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -25,8 +25,10 @@ import re import types from typing import Iterator +from typing import Literal from typing import Mapping from typing import NoReturn +from typing import overload from typing import Protocol from typing import Sequence @@ -132,6 +134,14 @@ def lex(self, input: str) -> Iterator[Token]: ) yield Token(TokenType.EOF, "", pos) + @overload + def accept(self, type: TokenType, *, reject: Literal[True]) -> Token: ... + + @overload + def accept( + self, type: TokenType, *, reject: Literal[False] = False + ) -> Token | None: ... + def accept(self, type: TokenType, *, reject: bool = False) -> Token | None: if self.current.type is type: token = self.current @@ -208,7 +218,6 @@ def not_expr(s: Scanner) -> ast.expr: def single_kwarg(s: Scanner) -> ast.keyword: keyword_name = s.accept(TokenType.IDENT, reject=True) - assert keyword_name is not None # for mypy if not keyword_name.value.isidentifier() or keyword.iskeyword(keyword_name.value): raise ParseError( keyword_name.pos + 1, @@ -220,7 +229,6 @@ def single_kwarg(s: Scanner) -> ast.keyword: value: str | int | bool | None = value_token.value[1:-1] # strip quotes else: value_token = s.accept(TokenType.IDENT, reject=True) - assert value_token is not None # for mypy if ( (number := value_token.value).isdigit() or number.startswith("-") From 1cc35ecc3fdf28c201fbaf775945bae24be17ce7 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Thu, 20 Jun 2024 23:23:02 +0200 Subject: [PATCH 121/199] test: add empty string keyword argument marker test cases --- testing/test_mark_expression.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 0c1e73809f7..e7ecaa7db10 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -250,7 +250,13 @@ def create_marker(name: str, kwargs: dict[str, object]) -> structures.Mark: markers.append( create_marker( "str_mark", - {"m": "M", "space": "with space", "aaאבגדcc": "aaאבגדcc", "אבגד": "אבגד"}, + { + "m": "M", + "space": "with space", + "aaאבגדcc": "aaאבגדcc", + "אבגד": "אבגד", + "empty": "", + }, ) ) @@ -313,9 +319,13 @@ def test_builtin_matchers_keyword_expressions( # TODO: naming when decided ("str_mark(aaאבגדcc='aaאבגדcc')", True), ("str_mark(אבגד='אבגד')", True), ("str_mark(space='with space')", True), + ("str_mark(empty='')", True), + ('str_mark(empty="")', True), ("str_mark(m='wrong')", False), ("str_mark(aaאבגדcc='wrong')", False), ("str_mark(אבגד='wrong')", False), + ("str_mark(m='')", False), + ('str_mark(m="")', False), ), ) def test_str_keyword_expressions( From f4897391ec65b6f7b162d93b7b48a6587f9b659b Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 14:04:42 +0200 Subject: [PATCH 122/199] refactor(mark): use existing `NOT_SET` sentinel --- src/_pytest/mark/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index ae6940a6256..ddfa8355a73 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -22,6 +22,7 @@ from _pytest.config import ExitCode from _pytest.config import hookimpl from _pytest.config import UsageError +from _pytest.config.argparsing import NOT_SET from _pytest.config.argparsing import Parser from _pytest.stash import StashKey @@ -214,9 +215,6 @@ def deselect_by_keyword(items: list[Item], config: Config) -> None: items[:] = remaining -NOT_NONE_SENTINEL = object() - - @dataclasses.dataclass class MarkMatcher: """A matcher for markers which are present. @@ -243,9 +241,7 @@ def __call__(self, name: str, /, **kwargs: object) -> bool: return True for mark in matches: - if all( - mark.kwargs.get(k, NOT_NONE_SENTINEL) == v for k, v in kwargs.items() - ): + if all(mark.kwargs.get(k, NOT_SET) == v for k, v in kwargs.items()): return True return False From 1e7eb20347819eb86cd22be998fef0dc50db47da Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 12:07:31 +0200 Subject: [PATCH 123/199] perf(expression): improve string lexing & error messages --- src/_pytest/mark/expression.py | 23 ++++++++++++++--------- testing/test_mark_expression.py | 28 ++++++++++++++++------------ 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 39d262c801a..c1770c197b8 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -99,18 +99,18 @@ def lex(self, input: str) -> Iterator[Token]: elif input[pos] == ",": yield Token(TokenType.COMMA, ",", pos) pos += 1 - elif (quote_char := input[pos]) == "'" or input[pos] == '"': - quote_position = input[pos + 1 :].find(quote_char) - if quote_position == -1: + elif (quote_char := input[pos]) in ("'", '"'): + end_quote_pos = input.find(quote_char, pos + 1) + if end_quote_pos == -1: raise ParseError( pos + 1, f'closing quote "{quote_char}" is missing', ) - value = input[pos : pos + 2 + quote_position] - if "\\" in value: + value = input[pos : end_quote_pos + 1] + if (backslash_pos := input.find("\\")) != -1: raise ParseError( - pos + 1, - "escaping not supported in marker expression", + backslash_pos + 1, + r'escaping with "\" not supported in marker expression', ) yield Token(TokenType.STRING, value, pos) pos += len(value) @@ -218,10 +218,15 @@ def not_expr(s: Scanner) -> ast.expr: def single_kwarg(s: Scanner) -> ast.keyword: keyword_name = s.accept(TokenType.IDENT, reject=True) - if not keyword_name.value.isidentifier() or keyword.iskeyword(keyword_name.value): + if not keyword_name.value.isidentifier(): + raise ParseError( + keyword_name.pos + 1, + f"not a valid python identifier {keyword_name.value}", + ) + if keyword.iskeyword(keyword_name.value): raise ParseError( keyword_name.pos + 1, - f'unexpected character/s "{keyword_name.value}"', + f"unexpected reserved python keyword `{keyword_name.value}`", ) s.accept(TokenType.EQUAL, reject=True) diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index e7ecaa7db10..3c42cc96737 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -207,25 +207,29 @@ def test_invalid_idents(ident: str) -> None: @pytest.mark.parametrize( "expr, expected_error_msg", ( - ("mark(1=2)", 'unexpected character/s "1"'), - ("mark(/=2)", 'unexpected character/s "/"'), - ("mark(True=False)", 'unexpected character/s "True"'), - ("mark(def=False)", 'unexpected character/s "def"'), - ("mark(class=False)", 'unexpected character/s "class"'), - ("mark(if=False)", 'unexpected character/s "if"'), - ("mark(else=False)", 'unexpected character/s "else"'), - ("mark(1)", 'unexpected character/s "1"'), - ("mark(var:=False", 'unexpected character/s "var:"'), - ("mark(valid=False, def=1)", 'unexpected character/s "def"'), + ("mark(True=False)", "unexpected reserved python keyword `True`"), + ("mark(def=False)", "unexpected reserved python keyword `def`"), + ("mark(class=False)", "unexpected reserved python keyword `class`"), + ("mark(if=False)", "unexpected reserved python keyword `if`"), + ("mark(else=False)", "unexpected reserved python keyword `else`"), + ("mark(valid=False, def=1)", "unexpected reserved python keyword `def`"), + ("mark(1)", "not a valid python identifier 1"), + ("mark(var:=False", "not a valid python identifier var:"), + ("mark(1=2)", "not a valid python identifier 1"), + ("mark(/=2)", "not a valid python identifier /"), ("mark(var==", "expected identifier; got ="), + ("mark(var)", "expected =; got right parenthesis"), ("mark(var=none)", 'unexpected character/s "none"'), ("mark(var=1.1)", 'unexpected character/s "1.1"'), - ("mark(var)", "expected =; got right parenthesis"), ("mark(var=')", """closing quote "'" is missing"""), ('mark(var=")', 'closing quote """ is missing'), ("""mark(var="')""", 'closing quote """ is missing'), ("""mark(var='")""", """closing quote "'" is missing"""), - (r"mark(var='\hugo')", "escaping not supported in marker expression"), + ( + r"mark(var='\hugo')", + r'escaping with "\\" not supported in marker expression', + ), + ("mark(empty_list=[])", r'unexpected character/s "\[\]"'), ), ) def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? From 6dd8ad60a49d3ba1e4071cf40a955c050ad78b08 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 14:41:22 +0200 Subject: [PATCH 124/199] refactor(MarkMatcher): replace `from_item` with `from_markers` method --- src/_pytest/mark/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index ddfa8355a73..dccc6c529c9 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -6,6 +6,7 @@ import dataclasses from typing import AbstractSet from typing import Collection +from typing import Iterable from typing import Optional from typing import TYPE_CHECKING @@ -227,9 +228,9 @@ class MarkMatcher: own_mark_name_mapping: dict[str, list[Mark]] @classmethod - def from_item(cls, item: Item) -> MarkMatcher: + def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher: mark_name_mapping = collections.defaultdict(list) - for mark in item.iter_markers(): + for mark in markers: mark_name_mapping[mark.name].append(mark) return cls(mark_name_mapping) @@ -256,7 +257,7 @@ def deselect_by_mark(items: list[Item], config: Config) -> None: remaining: list[Item] = [] deselected: list[Item] = [] for item in items: - if expr.evaluate(MarkMatcher.from_item(item)): + if expr.evaluate(MarkMatcher.from_markers(item.iter_markers())): remaining.append(item) else: deselected.append(item) From 3921d94316866954db9b60f05e6d7c627cd20bbf Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 14:45:27 +0200 Subject: [PATCH 125/199] test: use new `MarkMatcher.from_markers` method & register test markers --- pyproject.toml | 3 +++ testing/test_mark_expression.py | 38 ++++++++------------------------- 2 files changed, 12 insertions(+), 29 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 14f69bc3422..e0ed2b90061 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -358,6 +358,9 @@ markers = [ "foo", "bar", "baz", + "number_mark", + "builtin_matchers_mark", + "str_mark", # conftest.py reorders tests moving slow ones to the end of the list "slow", # experimental mark for all tests using pexpect diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 3c42cc96737..c31ab44704c 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -1,11 +1,9 @@ from __future__ import annotations -import collections from typing import Callable from typing import cast from _pytest.mark import MarkMatcher -from _pytest.mark import structures from _pytest.mark.expression import Expression from _pytest.mark.expression import MatcherCall from _pytest.mark.expression import ParseError @@ -241,33 +239,15 @@ def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? @pytest.fixture(scope="session") def mark_matcher() -> MarkMatcher: - markers = [] - mark_name_mapping = collections.defaultdict(list) - - def create_marker(name: str, kwargs: dict[str, object]) -> structures.Mark: - return structures.Mark(name=name, args=tuple(), kwargs=kwargs, _ispytest=True) - - markers.append(create_marker("number_mark", {"a": 1, "b": 2, "c": 3, "d": 999_999})) - markers.append( - create_marker("builtin_matchers_mark", {"x": True, "y": False, "z": None}) - ) - markers.append( - create_marker( - "str_mark", - { - "m": "M", - "space": "with space", - "aaאבגדcc": "aaאבגדcc", - "אבגד": "אבגד", - "empty": "", - }, - ) - ) - - for marker in markers: - mark_name_mapping[marker.name].append(marker) - - return MarkMatcher(mark_name_mapping) + markers = [ + pytest.mark.number_mark(a=1, b=2, c=3, d=999_999).mark, + pytest.mark.builtin_matchers_mark(x=True, y=False, z=None).mark, + pytest.mark.str_mark( + m="M", space="with space", empty="", aaאבגדcc="aaאבגדcc", אבגד="אבגד" + ).mark, + ] + + return MarkMatcher.from_markers(markers) @pytest.mark.parametrize( From 7c7c36d7e07134242b2af4c8a291268a94cd4619 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 15:11:56 +0200 Subject: [PATCH 126/199] test(test_mark.py): add sad case that `-k` doesn't support keyword expressions --- testing/test_mark.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/testing/test_mark.py b/testing/test_mark.py index 721bb71d3da..6a94cc9f7c2 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -420,6 +420,10 @@ def test_func(arg): "not or", "at column 5: expected not OR left parenthesis OR identifier; got or", ), + ( + "nonexistent_mark(non_supported='kwarg')", + "Keyword expressions do not support call parameters", + ), ], ) def test_keyword_option_wrong_arguments( From 598d881c9c21e47c00755b9a40e66aca8eec17d1 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 15:57:13 +0200 Subject: [PATCH 127/199] docs: document keyword argument support in marker expressions --- doc/en/example/markers.rst | 24 ++++++++++++++++++++++++ doc/en/how-to/usage.rst | 10 +++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index c04d2a078dd..33c5cf95fe2 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -25,10 +25,12 @@ You can "mark" a test function with custom metadata like this: pass # perform some webtest test for your app + @pytest.mark.device(serial="123") def test_something_quick(): pass + @pytest.mark.device(serial="abc") def test_another(): pass @@ -71,6 +73,28 @@ Or the inverse, running all tests except the webtest ones: ===================== 3 passed, 1 deselected in 0.12s ====================== +.. _`marker_keyword_expression_example`: + +Additionally, you can restrict a test run to only run tests matching one or multiple marker +keyword arguments, e.g. to run only tests marked with ``device`` and the specific ``serial="123"``: + +.. code-block:: pytest + + $ pytest -v -m 'device(serial="123")' + =========================== test session starts ============================ + platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python + cachedir: .pytest_cache + rootdir: /home/sweet/project + collecting ... collected 4 items / 3 deselected / 1 selected + + test_server.py::test_something_quick PASSED [100%] + + ===================== 1 passed, 3 deselected in 0.12s ====================== + +.. note:: Only keyword argument matching is supported in marker expressions. + +.. note:: Only ``int``, (unescaped) ``str``, ``bool`` & ``None`` values are supported in marker expressions. + Selecting tests based on their node ID -------------------------------------- diff --git a/doc/en/how-to/usage.rst b/doc/en/how-to/usage.rst index 705fa009ed5..05ee0460019 100644 --- a/doc/en/how-to/usage.rst +++ b/doc/en/how-to/usage.rst @@ -76,11 +76,19 @@ Specifying a specific parametrization of a test: **Run tests by marker expressions** +To run all tests which are decorated with the ``@pytest.mark.slow`` decorator: + .. code-block:: bash pytest -m slow -Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator. + +To run all tests which are decorated with the annotated ``@pytest.mark.slow(phase=1)`` decorator, +with the ``phase`` keyword argument set to ``1``: + +.. code-block:: bash + + pytest -m slow(phase=1) For more information see :ref:`marks `. From 9cf9cfabcb2dfa41c9f168259e84b7d658cbf690 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 16:23:16 +0200 Subject: [PATCH 128/199] docs(12281.feature.rst): add changelog fragment --- changelog/12281.feature.rst | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/12281.feature.rst diff --git a/changelog/12281.feature.rst b/changelog/12281.feature.rst new file mode 100644 index 00000000000..e5dd63824e4 --- /dev/null +++ b/changelog/12281.feature.rst @@ -0,0 +1,8 @@ +Added support for keyword matching in marker expressions. + +Now tests can be selected by marker keyword arguments. +Supported values are ``int``, (unescaped) ``str``, ``bool`` & ``None``. + +See :ref:`marker examples ` for more information. + +-- by :user:`lovetheguitar`. From c3e898353bda6fb7954e1634a0cadddd1c73b1a4 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 16:31:30 +0200 Subject: [PATCH 129/199] docs(AUTHORS): add myself as contributor --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 4d34d2ba9b4..5ae80eb02d3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -245,6 +245,7 @@ Levon Saldamli Lewis Cowles Llandy Riveron Del Risco Loic Esteve +lovetheguitar Lukas Bednar Luke Murphy Maciek Fijalkowski From b1255a9aae1bd94d8c226b207c38942c857d4794 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Fri, 21 Jun 2024 17:11:39 +0200 Subject: [PATCH 130/199] style(mark): type hint `**kwargs` as `str | int | bool | None` --- src/_pytest/mark/__init__.py | 4 ++-- src/_pytest/mark/expression.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index dccc6c529c9..702732c9708 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -184,7 +184,7 @@ def from_item(cls, item: Item) -> KeywordMatcher: return cls(mapped_names) - def __call__(self, subname: str, /, **kwargs: object) -> bool: + def __call__(self, subname: str, /, **kwargs: str | int | bool | None) -> bool: if kwargs: raise UsageError("Keyword expressions do not support call parameters.") subname = subname.lower() @@ -234,7 +234,7 @@ def from_markers(cls, markers: Iterable[Mark]) -> MarkMatcher: mark_name_mapping[mark.name].append(mark) return cls(mark_name_mapping) - def __call__(self, name: str, /, **kwargs: object) -> bool: + def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: if not (matches := self.own_mark_name_mapping.get(name, [])): return False diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index c1770c197b8..3f4071dcef8 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -260,7 +260,7 @@ def all_kwargs(s: Scanner) -> list[ast.keyword]: class MatcherCall(Protocol): - def __call__(self, name: str, /, **kwargs: object) -> bool: ... + def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: ... @dataclasses.dataclass @@ -271,7 +271,7 @@ class MatcherNameAdapter: def __bool__(self) -> bool: return self.matcher(self.name) - def __call__(self, **kwargs: object) -> bool: + def __call__(self, **kwargs: str | int | bool | None) -> bool: return self.matcher(self.name, **kwargs) From 24450e33e364f1aa7f4e2cc3e029e7d1b186774b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Fri, 21 Jun 2024 22:05:43 +0200 Subject: [PATCH 131/199] =?UTF-8?q?=F0=9F=93=9D=20Use=20explicit=20RST=20r?= =?UTF-8?q?oles=20for=20built-in=20types=20in=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12281.feature.rst | 2 +- doc/en/example/markers.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog/12281.feature.rst b/changelog/12281.feature.rst index e5dd63824e4..bcf882c511b 100644 --- a/changelog/12281.feature.rst +++ b/changelog/12281.feature.rst @@ -1,7 +1,7 @@ Added support for keyword matching in marker expressions. Now tests can be selected by marker keyword arguments. -Supported values are ``int``, (unescaped) ``str``, ``bool`` & ``None``. +Supported values are :class:`int`, (unescaped) :class:`str`, :class:`bool` & :data:`None`. See :ref:`marker examples ` for more information. diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 33c5cf95fe2..159ff2cd1d7 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -93,7 +93,7 @@ keyword arguments, e.g. to run only tests marked with ``device`` and the specifi .. note:: Only keyword argument matching is supported in marker expressions. -.. note:: Only ``int``, (unescaped) ``str``, ``bool`` & ``None`` values are supported in marker expressions. +.. note:: Only :class:`int`, (unescaped) :class:`str`, :class:`bool` & :data:`None` values are supported in marker expressions. Selecting tests based on their node ID -------------------------------------- From 329662e2eca801be53678fde7e1ef23583eb5fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Fri, 21 Jun 2024 22:06:33 +0200 Subject: [PATCH 132/199] =?UTF-8?q?=F0=9F=93=9D=20Drop=20stray=20trailing?= =?UTF-8?q?=20period=20from=20the=20change=20note=20byline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12281.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/12281.feature.rst b/changelog/12281.feature.rst index bcf882c511b..c6e8e3b3098 100644 --- a/changelog/12281.feature.rst +++ b/changelog/12281.feature.rst @@ -5,4 +5,4 @@ Supported values are :class:`int`, (unescaped) :class:`str`, :class:`bool` & :da See :ref:`marker examples ` for more information. --- by :user:`lovetheguitar`. +-- by :user:`lovetheguitar` From 75a2225ed1d387e6a224be9b0f9d7297bfb08284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Fri, 21 Jun 2024 22:09:45 +0200 Subject: [PATCH 133/199] =?UTF-8?q?=F0=9F=94=A5=20Drop=20the=20missing=20k?= =?UTF-8?q?wargs=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/_pytest/mark/__init__.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/_pytest/mark/__init__.py b/src/_pytest/mark/__init__.py index 702732c9708..a4f942c5ae3 100644 --- a/src/_pytest/mark/__init__.py +++ b/src/_pytest/mark/__init__.py @@ -238,9 +238,6 @@ def __call__(self, name: str, /, **kwargs: str | int | bool | None) -> bool: if not (matches := self.own_mark_name_mapping.get(name, [])): return False - if not kwargs: - return True - for mark in matches: if all(mark.kwargs.get(k, NOT_SET) == v for k, v in kwargs.items()): return True From 1b85ac126eb5490f7afa1499a502d01c64be2cf5 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Fri, 21 Jun 2024 22:52:17 +0200 Subject: [PATCH 134/199] =?UTF-8?q?=F0=9F=9A=91=F0=9F=A7=AA=20Set=20the=20?= =?UTF-8?q?Codecov=20token=20directly=20in=20GHA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It's necessary since it seems that the currently used Codecov uploader doesn't read the token from config sometimes. This is a follow-up for #12508 which wasn't enough. --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab4ea3db75c..e9d0f233c42 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -250,6 +250,7 @@ jobs: with: fail_ci_if_error: false files: ./coverage.xml + token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token; cfg read fails verbose: true check: # This job does nothing and is only used for the branch protection From c716e0baefb31e62ae8275c3b8d7c8e976b010b4 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Fri, 21 Jun 2024 23:06:26 +0200 Subject: [PATCH 135/199] =?UTF-8?q?=F0=9F=A7=AA=20Bump=20the=20`alls-green?= =?UTF-8?q?`=20action=20to=20223erbb7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This version drops the use of the outdated GHA syntax for setting action output values. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ab4ea3db75c..d78e0900fd9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -262,6 +262,6 @@ jobs: steps: - name: Decide whether the needed jobs succeeded or failed - uses: re-actors/alls-green@198badcb65a1a44528f27d5da555c4be9f12eac6 + uses: re-actors/alls-green@223e4bb7a751b91f43eda76992bcfbf23b8b0302 with: jobs: ${{ toJSON(needs) }} From 66eff85e5424f0274e15081eb4185a2e23b4c167 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:48:15 +0200 Subject: [PATCH 136/199] docs: use double quotes for cross-platform compatibility in example code --- doc/en/example/markers.rst | 2 +- doc/en/how-to/usage.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst index 159ff2cd1d7..babcd9e2f3a 100644 --- a/doc/en/example/markers.rst +++ b/doc/en/example/markers.rst @@ -80,7 +80,7 @@ keyword arguments, e.g. to run only tests marked with ``device`` and the specifi .. code-block:: pytest - $ pytest -v -m 'device(serial="123")' + $ pytest -v -m "device(serial='123')" =========================== test session starts ============================ platform linux -- Python 3.x.y, pytest-8.x.y, pluggy-1.x.y -- $PYTHON_PREFIX/bin/python cachedir: .pytest_cache diff --git a/doc/en/how-to/usage.rst b/doc/en/how-to/usage.rst index 05ee0460019..0e0a0310fd8 100644 --- a/doc/en/how-to/usage.rst +++ b/doc/en/how-to/usage.rst @@ -88,7 +88,7 @@ with the ``phase`` keyword argument set to ``1``: .. code-block:: bash - pytest -m slow(phase=1) + pytest -m "slow(phase=1)" For more information see :ref:`marks `. From 73bc35ce2b22723a87c26cad6aa7835dcdc060cf Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:49:59 +0200 Subject: [PATCH 137/199] docs(expression.py): correct grammar definition of lexer --- src/_pytest/mark/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 3f4071dcef8..3b7f4c51b42 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -5,7 +5,7 @@ expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident ( '(' name '=' value ( ', ' name '=' value )* ')')* +not_expr: 'not' not_expr | '(' expr ')' | ident ('(' name '=' value ( ', ' name '=' value )* ')')? ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ From 3cce243774b03125bb123a7b69f684ea0abba34b Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:50:26 +0200 Subject: [PATCH 138/199] docs(expression.py): fix typo --- src/_pytest/mark/expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 3b7f4c51b42..2067110d23a 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -12,7 +12,7 @@ The semantics are: - Empty expression evaluates to False. -- ident evaluates to True of False according to a provided matcher function. +- ident evaluates to True or False according to a provided matcher function. - or/and/not evaluate according to the usual boolean semantics. """ From 66dbab697bb2a14801cd744a0d78c3cd69c3f809 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 22 Jun 2024 21:54:45 +0200 Subject: [PATCH 139/199] =?UTF-8?q?=F0=9F=93=9D=20Rename=20`:pull:`=20RST?= =?UTF-8?q?=20role=20to=20`:pr:`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a preparatory patch for integrating the third party `sphinx-issues` extension. --- doc/en/announce/release-2.9.0.rst | 2 +- doc/en/announce/release-2.9.1.rst | 2 +- doc/en/announce/release-2.9.2.rst | 4 +- doc/en/changelog.rst | 98 +++++++++++++++---------------- doc/en/conf.py | 2 +- doc/en/historical-notes.rst | 2 +- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/doc/en/announce/release-2.9.0.rst b/doc/en/announce/release-2.9.0.rst index 3aea08cb225..753bb7bf6f0 100644 --- a/doc/en/announce/release-2.9.0.rst +++ b/doc/en/announce/release-2.9.0.rst @@ -45,7 +45,7 @@ The py.test Development Team **New Features** * New ``pytest.mark.skip`` mark, which unconditionally skips marked tests. - Thanks :user:`MichaelAquilina` for the complete PR (:pull:`1040`). + Thanks :user:`MichaelAquilina` for the complete PR (:pr:`1040`). * ``--doctest-glob`` may now be passed multiple times in the command-line. Thanks :user:`jab` and :user:`nicoddemus` for the PR. diff --git a/doc/en/announce/release-2.9.1.rst b/doc/en/announce/release-2.9.1.rst index 6a627ad3cd6..7a46d2ae690 100644 --- a/doc/en/announce/release-2.9.1.rst +++ b/doc/en/announce/release-2.9.1.rst @@ -44,7 +44,7 @@ The py.test Development Team Thanks :user:`nicoddemus` for the PR. * Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs - contain ``::``. Thanks :user:`tomviner` for the PR (:pull:`1431`). + contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`1431`). * Fix (:issue:`578`): SyntaxErrors containing non-ascii lines at the point of failure generated an internal diff --git a/doc/en/announce/release-2.9.2.rst b/doc/en/announce/release-2.9.2.rst index 2dc82a1117b..3e75af7fe69 100644 --- a/doc/en/announce/release-2.9.2.rst +++ b/doc/en/announce/release-2.9.2.rst @@ -44,14 +44,14 @@ The py.test Development Team * Fix Xfail does not work with condition keyword argument. Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner` - for PR the (:pull:`1524`). + for PR the (:pr:`1524`). * Fix win32 path issue when putting custom config file with absolute path in ``pytest.main("-c your_absolute_path")``. * Fix maximum recursion depth detection when raised error class is not aware of unicode/encoded bytes. - Thanks :user:`prusse-martin` for the PR (:pull:`1506`). + Thanks :user:`prusse-martin` for the PR (:pr:`1506`). * Fix ``pytest.mark.skip`` mark when used in strict mode. Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 7e32b943446..2c07f208ac3 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -268,7 +268,7 @@ Bug Fixes - `#11904 `_: Fixed a regression in pytest 8.0.0 that would cause test collection to fail due to permission errors when using ``--pyargs``. - This change improves the collection tree for tests specified using ``--pyargs``, see :pull:`12043` for a comparison with pytest 8.0 and <8. + This change improves the collection tree for tests specified using ``--pyargs``, see :pr:`12043` for a comparison with pytest 8.0 and <8. - `#12011 `_: Fixed a regression in 8.0.1 whereby ``setup_module`` xunit-style fixtures are not executed when ``--doctest-modules`` is passed. @@ -1422,7 +1422,7 @@ Bug Fixes tests/link -> tests/real running ``pytest tests`` now imports the conftest twice, once as ``tests/real/conftest.py`` and once as ``tests/link/conftest.py``. - This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pull:`6523` for details). + This is a fix to match a similar change made to test collection itself in pytest 6.0 (see :pr:`6523` for details). - `#9626 `_: Fixed count of selected tests on terminal collection summary when there were errors or skipped modules. @@ -2591,7 +2591,7 @@ Breaking Changes Resolving symlinks for the current directory and during collection was introduced as a bugfix in 3.9.0, but it actually is a new feature which had unfortunate consequences in Windows and surprising results in other platforms. The team decided to step back on resolving symlinks at all, planning to review this in the future with a more solid solution (see discussion in - :pull:`6523` for details). + :pr:`6523` for details). This might break test suites which made use of this feature; the fix is to create a symlink for the entire test tree, and not only to partial files/tress as it was possible previously. @@ -2874,7 +2874,7 @@ Bug Fixes - :issue:`6871`: Fix crash with captured output when using :fixture:`capsysbinary`. -- :issue:`6909`: Revert the change introduced by :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. +- :issue:`6909`: Revert the change introduced by :pr:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted. @@ -3044,7 +3044,7 @@ pytest 5.4.1 (2020-03-13) Bug Fixes --------- -- :issue:`6909`: Revert the change introduced by :pull:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. +- :issue:`6909`: Revert the change introduced by :pr:`6330`, which required all arguments to ``@pytest.mark.parametrize`` to be explicitly defined in the function signature. The intention of the original change was to remove what was expected to be an unintended/surprising behavior, but it turns out many people relied on it, so the restriction has been reverted. @@ -4067,7 +4067,7 @@ Bug Fixes (``--collect-only``) when ``--log-cli-level`` is used. -- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. +- :issue:`5389`: Fix regressions of :pr:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. - :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods. @@ -4268,7 +4268,7 @@ Bug Fixes (``--collect-only``) when ``--log-cli-level`` is used. -- :issue:`5389`: Fix regressions of :pull:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. +- :issue:`5389`: Fix regressions of :pr:`5063` for ``importlib_metadata.PathDistribution`` which have their ``files`` attribute being ``None``. - :issue:`5390`: Fix regression where the ``obj`` attribute of ``TestCase`` items was no longer bound to methods. @@ -7229,10 +7229,10 @@ New Features * Added ``junit_suite_name`` ini option to specify root ```` name for JUnit XML reports (:issue:`533`). * Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files. - Thanks :user:`wheerd` for the PR (:pull:`2101`). + Thanks :user:`wheerd` for the PR (:pr:`2101`). * ``pytest.warns`` now checks for subclass relationship rather than - class equality. Thanks :user:`lesteve` for the PR (:pull:`2166`) + class equality. Thanks :user:`lesteve` for the PR (:pr:`2166`) * ``pytest.raises`` now asserts that the error message matches a text or regex with the ``match`` keyword argument. Thanks :user:`Kriechi` for the PR. @@ -7260,7 +7260,7 @@ Changes the failure. (:issue:`2228`) Thanks to :user:`kkoukiou` for the PR. * Testcase reports with a ``url`` attribute will now properly write this to junitxml. - Thanks :user:`fushi` for the PR (:pull:`1874`). + Thanks :user:`fushi` for the PR (:pr:`1874`). * Remove common items from dict comparison output when verbosity=1. Also update the truncation message to make it clearer that pytest truncates all @@ -7269,7 +7269,7 @@ Changes * ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use ``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks :user:`davidszotten` for - the PR (:pull:`1952`). + the PR (:pr:`1952`). * fix :issue:`2013`: turn RecordedWarning into ``namedtuple``, to give it a comprehensible repr while preventing unwarranted modification. @@ -7523,7 +7523,7 @@ Bug Fixes a sequence of strings) when modules are considered for assertion rewriting. Due to this bug, much more modules were being rewritten than necessary if a test suite uses ``pytest_plugins`` to load internal plugins (:issue:`1888`). - Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pull:`1891`). + Thanks :user:`jaraco` for the report and :user:`nicoddemus` for the PR (:pr:`1891`). * Do not call tearDown and cleanups when running tests from ``unittest.TestCase`` subclasses with ``--pdb`` @@ -7578,12 +7578,12 @@ time or change existing behaviors in order to make them less surprising/more use * ``--nomagic``: use ``--assert=plain`` instead; * ``--report``: use ``-r`` instead; - Thanks to :user:`RedBeardCode` for the PR (:pull:`1664`). + Thanks to :user:`RedBeardCode` for the PR (:pr:`1664`). * ImportErrors in plugins now are a fatal error instead of issuing a pytest warning (:issue:`1479`). Thanks to :user:`The-Compiler` for the PR. -* Removed support code for Python 3 versions < 3.3 (:pull:`1627`). +* Removed support code for Python 3 versions < 3.3 (:pr:`1627`). * Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points were never documented and a leftover from a pre-virtualenv era. These entry @@ -7594,19 +7594,19 @@ time or change existing behaviors in order to make them less surprising/more use * ``pytest.skip()`` now raises an error when used to decorate a test function, as opposed to its original intent (to imperatively skip a test inside a test function). Previously this usage would cause the entire module to be skipped (:issue:`607`). - Thanks :user:`omarkohl` for the complete PR (:pull:`1519`). + Thanks :user:`omarkohl` for the complete PR (:pr:`1519`). * Exit tests if a collection error occurs. A poll indicated most users will hit CTRL-C anyway as soon as they see collection errors, so pytest might as well make that the default behavior (:issue:`1421`). A ``--continue-on-collection-errors`` option has been added to restore the previous behaviour. - Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pull:`1628`). + Thanks :user:`olegpidsadnyi` and :user:`omarkohl` for the complete PR (:pr:`1628`). * Renamed the pytest ``pdb`` module (plugin) into ``debugging`` to avoid clashes with the builtin ``pdb`` module. * Raise a helpful failure message when requesting a parametrized fixture at runtime, e.g. with ``request.getfixturevalue``. Previously these parameters were simply never defined, so a fixture decorated like ``@pytest.fixture(params=[0, 1, 2])`` - only ran once (:pull:`460`). + only ran once (:pr:`460`). Thanks to :user:`nikratio` for the bug report, :user:`RedBeardCode` and :user:`tomviner` for the PR. * ``_pytest.monkeypatch.monkeypatch`` class has been renamed to ``_pytest.monkeypatch.MonkeyPatch`` @@ -7624,7 +7624,7 @@ time or change existing behaviors in order to make them less surprising/more use * New ``doctest_namespace`` fixture for injecting names into the namespace in which doctests run. - Thanks :user:`milliams` for the complete PR (:pull:`1428`). + Thanks :user:`milliams` for the complete PR (:pr:`1428`). * New ``--doctest-report`` option available to change the output format of diffs when running (failing) doctests (implements :issue:`1749`). @@ -7632,23 +7632,23 @@ time or change existing behaviors in order to make them less surprising/more use * New ``name`` argument to ``pytest.fixture`` decorator which allows a custom name for a fixture (to solve the funcarg-shadowing-fixture problem). - Thanks :user:`novas0x2a` for the complete PR (:pull:`1444`). + Thanks :user:`novas0x2a` for the complete PR (:pr:`1444`). * New ``approx()`` function for easily comparing floating-point numbers in tests. - Thanks :user:`kalekundert` for the complete PR (:pull:`1441`). + Thanks :user:`kalekundert` for the complete PR (:pr:`1441`). * Ability to add global properties in the final xunit output file by accessing the internal ``junitxml`` plugin (experimental). - Thanks :user:`tareqalayan` for the complete PR :pull:`1454`). + Thanks :user:`tareqalayan` for the complete PR :pr:`1454`). * New ``ExceptionInfo.match()`` method to match a regular expression on the string representation of an exception (:issue:`372`). - Thanks :user:`omarkohl` for the complete PR (:pull:`1502`). + Thanks :user:`omarkohl` for the complete PR (:pr:`1502`). * ``__tracebackhide__`` can now also be set to a callable which then can decide whether to filter the traceback based on the ``ExceptionInfo`` object passed - to it. Thanks :user:`The-Compiler` for the complete PR (:pull:`1526`). + to it. Thanks :user:`The-Compiler` for the complete PR (:pr:`1526`). * New ``pytest_make_parametrize_id(config, val)`` hook which can be used by plugins to provide friendly strings for custom types. @@ -7666,7 +7666,7 @@ time or change existing behaviors in order to make them less surprising/more use * Introduce ``pytest`` command as recommended entry point. Note that ``py.test`` still works and is not scheduled for removal. Closes proposal :issue:`1629`. Thanks :user:`obestwalter` and :user:`davehunt` for the complete PR - (:pull:`1633`). + (:pr:`1633`). * New cli flags: @@ -7710,19 +7710,19 @@ time or change existing behaviors in order to make them less surprising/more use * Change ``report.outcome`` for ``xpassed`` tests to ``"passed"`` in non-strict mode and ``"failed"`` in strict mode. Thanks to :user:`hackebrot` for the PR - (:pull:`1795`) and :user:`gprasad84` for report (:issue:`1546`). + (:pr:`1795`) and :user:`gprasad84` for report (:issue:`1546`). * Tests marked with ``xfail(strict=False)`` (the default) now appear in JUnitXML reports as passing tests instead of skipped. - Thanks to :user:`hackebrot` for the PR (:pull:`1795`). + Thanks to :user:`hackebrot` for the PR (:pr:`1795`). * Highlight path of the file location in the error report to make it easier to copy/paste. - Thanks :user:`suzaku` for the PR (:pull:`1778`). + Thanks :user:`suzaku` for the PR (:pr:`1778`). * Fixtures marked with ``@pytest.fixture`` can now use ``yield`` statements exactly like those marked with the ``@pytest.yield_fixture`` decorator. This change renders ``@pytest.yield_fixture`` deprecated and makes ``@pytest.fixture`` with ``yield`` statements - the preferred way to write teardown code (:pull:`1461`). + the preferred way to write teardown code (:pr:`1461`). Thanks :user:`csaftoiu` for bringing this to attention and :user:`nicoddemus` for the PR. * Explicitly passed parametrize ids do not get escaped to ascii (:issue:`1351`). @@ -7733,11 +7733,11 @@ time or change existing behaviors in order to make them less surprising/more use Thanks :user:`nicoddemus` for the PR. * ``pytest_terminal_summary`` hook now receives the ``exitstatus`` - of the test session as argument. Thanks :user:`blueyed` for the PR (:pull:`1809`). + of the test session as argument. Thanks :user:`blueyed` for the PR (:pr:`1809`). * Parametrize ids can accept ``None`` as specific test id, in which case the automatically generated id for that argument will be used. - Thanks :user:`palaviv` for the complete PR (:pull:`1468`). + Thanks :user:`palaviv` for the complete PR (:pr:`1468`). * The parameter to xunit-style setup/teardown methods (``setup_method``, ``setup_module``, etc.) is now optional and may be omitted. @@ -7745,32 +7745,32 @@ time or change existing behaviors in order to make them less surprising/more use * Improved automatic id generation selection in case of duplicate ids in parametrize. - Thanks :user:`palaviv` for the complete PR (:pull:`1474`). + Thanks :user:`palaviv` for the complete PR (:pr:`1474`). * Now pytest warnings summary is shown up by default. Added a new flag ``--disable-pytest-warnings`` to explicitly disable the warnings summary (:issue:`1668`). * Make ImportError during collection more explicit by reminding the user to check the name of the test module/package(s) (:issue:`1426`). - Thanks :user:`omarkohl` for the complete PR (:pull:`1520`). + Thanks :user:`omarkohl` for the complete PR (:pr:`1520`). * Add ``build/`` and ``dist/`` to the default ``--norecursedirs`` list. Thanks :user:`mikofski` for the report and :user:`tomviner` for the PR (:issue:`1544`). * ``pytest.raises`` in the context manager form accepts a custom ``message`` to raise when no exception occurred. - Thanks :user:`palaviv` for the complete PR (:pull:`1616`). + Thanks :user:`palaviv` for the complete PR (:pr:`1616`). * ``conftest.py`` files now benefit from assertion rewriting; previously it was only available for test modules. Thanks :user:`flub`, :user:`sober7` and :user:`nicoddemus` for the PR (:issue:`1619`). * Text documents without any doctests no longer appear as "skipped". - Thanks :user:`graingert` for reporting and providing a full PR (:pull:`1580`). + Thanks :user:`graingert` for reporting and providing a full PR (:pr:`1580`). * Ensure that a module within a namespace package can be found when it is specified on the command line together with the ``--pyargs`` - option. Thanks to :user:`taschini` for the PR (:pull:`1597`). + option. Thanks to :user:`taschini` for the PR (:pr:`1597`). * Always include full assertion explanation during assertion rewriting. The previous behaviour was hiding sub-expressions that happened to be ``False``, assuming this was redundant information. @@ -7786,20 +7786,20 @@ time or change existing behaviors in order to make them less surprising/more use Thanks :user:`nicoddemus` for the PR. * ``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]`` - to avoid conflicts with other distutils commands (see :pull:`567`). ``[pytest]`` sections in + to avoid conflicts with other distutils commands (see :pr:`567`). ``[pytest]`` sections in ``pytest.ini`` or ``tox.ini`` files are supported and unchanged. Thanks :user:`nicoddemus` for the PR. * Using ``pytest_funcarg__`` prefix to declare fixtures is considered deprecated and will be - removed in pytest-4.0 (:pull:`1684`). + removed in pytest-4.0 (:pr:`1684`). Thanks :user:`nicoddemus` for the PR. * Passing a command-line string to ``pytest.main()`` is considered deprecated and scheduled - for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pull:`1723`). + for removal in pytest-4.0. It is recommended to pass a list of arguments instead (:pr:`1723`). * Rename ``getfuncargvalue`` to ``getfixturevalue``. ``getfuncargvalue`` is still present but is now considered deprecated. Thanks to :user:`RedBeardCode` and :user:`tomviner` - for the PR (:pull:`1626`). + for the PR (:pr:`1626`). * ``optparse`` type usage now triggers DeprecationWarnings (:issue:`1740`). @@ -7857,11 +7857,11 @@ time or change existing behaviors in order to make them less surprising/more use :user:`tomviner` for the PR. * ``ConftestImportFailure`` now shows the traceback making it easier to - identify bugs in ``conftest.py`` files (:pull:`1516`). Thanks :user:`txomon` for + identify bugs in ``conftest.py`` files (:pr:`1516`). Thanks :user:`txomon` for the PR. * Text documents without any doctests no longer appear as "skipped". - Thanks :user:`graingert` for reporting and providing a full PR (:pull:`1580`). + Thanks :user:`graingert` for reporting and providing a full PR (:pr:`1580`). * Fixed collection of classes with custom ``__new__`` method. Fixes :issue:`1579`. Thanks to :user:`Stranger6667` for the PR. @@ -7869,7 +7869,7 @@ time or change existing behaviors in order to make them less surprising/more use * Fixed scope overriding inside metafunc.parametrize (:issue:`634`). Thanks to :user:`Stranger6667` for the PR. -* Fixed the total tests tally in junit xml output (:pull:`1798`). +* Fixed the total tests tally in junit xml output (:pr:`1798`). Thanks to :user:`cboelsen` for the PR. * Fixed off-by-one error with lines from ``request.node.warn``. @@ -7886,14 +7886,14 @@ time or change existing behaviors in order to make them less surprising/more use * Fix Xfail does not work with condition keyword argument. Thanks :user:`astraw38` for reporting the issue (:issue:`1496`) and :user:`tomviner` - for PR the (:pull:`1524`). + for PR the (:pr:`1524`). * Fix win32 path issue when putting custom config file with absolute path in ``pytest.main("-c your_absolute_path")``. * Fix maximum recursion depth detection when raised error class is not aware of unicode/encoded bytes. - Thanks :user:`prusse-martin` for the PR (:pull:`1506`). + Thanks :user:`prusse-martin` for the PR (:pr:`1506`). * Fix ``pytest.mark.skip`` mark when used in strict mode. Thanks :user:`pquentin` for the PR and :user:`RonnyPfannschmidt` for @@ -7920,7 +7920,7 @@ time or change existing behaviors in order to make them less surprising/more use Thanks :user:`nicoddemus` for the PR. * Fix (:issue:`469`): junit parses report.nodeid incorrectly, when params IDs - contain ``::``. Thanks :user:`tomviner` for the PR (:pull:`1431`). + contain ``::``. Thanks :user:`tomviner` for the PR (:pr:`1431`). * Fix (:issue:`578`): SyntaxErrors containing non-ascii lines at the point of failure generated an internal @@ -7941,7 +7941,7 @@ time or change existing behaviors in order to make them less surprising/more use **New Features** * New ``pytest.mark.skip`` mark, which unconditionally skips marked tests. - Thanks :user:`MichaelAquilina` for the complete PR (:pull:`1040`). + Thanks :user:`MichaelAquilina` for the complete PR (:pr:`1040`). * ``--doctest-glob`` may now be passed multiple times in the command-line. Thanks :user:`jab` and :user:`nicoddemus` for the PR. @@ -7952,14 +7952,14 @@ time or change existing behaviors in order to make them less surprising/more use * ``pytest.mark.xfail`` now has a ``strict`` option, which makes ``XPASS`` tests to fail the test suite (defaulting to ``False``). There's also a ``xfail_strict`` ini option that can be used to configure it project-wise. - Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pull:`1355`). + Thanks :user:`rabbbit` for the request and :user:`nicoddemus` for the PR (:pr:`1355`). * ``Parser.addini`` now supports options of type ``bool``. Thanks :user:`nicoddemus` for the PR. * New ``ALLOW_BYTES`` doctest option. This strips ``b`` prefixes from byte strings in doctest output (similar to ``ALLOW_UNICODE``). - Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pull:`1287`). + Thanks :user:`jaraco` for the request and :user:`nicoddemus` for the PR (:pr:`1287`). * Give a hint on ``KeyboardInterrupt`` to use the ``--fulltrace`` option to show the errors. Fixes :issue:`1366`. @@ -7991,7 +7991,7 @@ time or change existing behaviors in order to make them less surprising/more use * Removed code and documentation for Python 2.5 or lower versions, including removal of the obsolete ``_pytest.assertion.oldinterpret`` module. - Thanks :user:`nicoddemus` for the PR (:pull:`1226`). + Thanks :user:`nicoddemus` for the PR (:pr:`1226`). * Comparisons now always show up in full when ``CI`` or ``BUILD_NUMBER`` is found in the environment, even when ``-vv`` isn't used. diff --git a/doc/en/conf.py b/doc/en/conf.py index 53056024b37..c4dd252f9b4 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -175,7 +175,7 @@ "bpo": ("https://bugs.python.org/issue%s", "bpo-%s"), "pypi": ("https://pypi.org/project/%s/", "%s"), "issue": (f"{_repo}/issues/%s", "issue #%s"), - "pull": (f"{_repo}/pull/%s", "pull request #%s"), + "pr": (f"{_repo}/pull/%s", "pull request #%s"), "user": ("https://github.com/%s", "@%s"), } diff --git a/doc/en/historical-notes.rst b/doc/en/historical-notes.rst index 5eb527c582b..be67036d6ca 100644 --- a/doc/en/historical-notes.rst +++ b/doc/en/historical-notes.rst @@ -107,7 +107,7 @@ Here is a non-exhaustive list of issues fixed by the new implementation: * Marker transfer incompatible with inheritance (:issue:`535`). -More details can be found in the :pull:`original PR <3317>`. +More details can be found in the :pr:`original PR <3317>`. .. note:: From b919a711a44f329fa82fb65a944bcdb5ad3f1e93 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 22 Jun 2024 22:02:59 +0200 Subject: [PATCH 140/199] =?UTF-8?q?=F0=9F=93=9D=20Replace=20GH/PyPI=20`ext?= =?UTF-8?q?links`=20w/=20`sphinx=5Fissues`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This extension implements more generic roles that can also be used more flexibly. Relying on an external extension allows to stop maintaining an in-repo copy of the commonly used behavior. --- doc/en/conf.py | 13 ++++++++----- doc/en/requirements.txt | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index c4dd252f9b4..f69021e94cf 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -89,6 +89,7 @@ "sphinx_removed_in", "sphinxcontrib_trio", "sphinxcontrib.towncrier.ext", # provides `towncrier-draft-entries` directive + "sphinx_issues", # implements `:issue:`, `:pr:` and other GH-related roles ] # Building PDF docs on readthedocs requires inkscape for svg to pdf @@ -170,13 +171,8 @@ linkcheck_workers = 5 -_repo = "https://github.com/pytest-dev/pytest" extlinks = { "bpo": ("https://bugs.python.org/issue%s", "bpo-%s"), - "pypi": ("https://pypi.org/project/%s/", "%s"), - "issue": (f"{_repo}/issues/%s", "issue #%s"), - "pr": (f"{_repo}/pull/%s", "pull request #%s"), - "user": ("https://github.com/%s", "@%s"), } @@ -451,6 +447,13 @@ towncrier_draft_config_path = "pyproject.toml" # relative to cwd +# -- Options for sphinx_issues extension ----------------------------------- + +issues_github_path = "pytest-dev/pytest" +issues_prefix = "issue #" +issues_pr_prefix = "pull request #" + + intersphinx_mapping = { "pluggy": ("https://pluggy.readthedocs.io/en/stable", None), "python": ("https://docs.python.org/3", None), diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index e40a15f1f15..0637c967b8a 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -10,3 +10,4 @@ sphinxcontrib-svg2pdfconverter packaging furo sphinxcontrib-towncrier +sphinx-issues From 0971a9590264b0be09ce269edbfea1ed1bff3ed2 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 22 Jun 2024 22:14:33 +0200 Subject: [PATCH 141/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PR=20#12522?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12522.contrib.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelog/12522.contrib.rst diff --git a/changelog/12522.contrib.rst b/changelog/12522.contrib.rst new file mode 100644 index 00000000000..dd994317165 --- /dev/null +++ b/changelog/12522.contrib.rst @@ -0,0 +1,4 @@ +The ``:pull:`` RST role has been replaced with a shorter +``:pr:`` due to starting to use the implementation from +the third-party :pypi:`sphinx-issues` Sphinx extension +-- by :user:`webknjaz`. From 8f2ef30f4af63d435592b7338807dd7048b54ddf Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Sat, 22 Jun 2024 22:16:40 +0200 Subject: [PATCH 142/199] =?UTF-8?q?=F0=9F=9A=91=20Stop=20setting=20PR/issu?= =?UTF-8?q?e=20prefix=20@=20`sphinx-issues`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apparently, the extension only supports one of the pre-defined prefixes. --- doc/en/conf.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index f69021e94cf..7ec862a6064 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -450,8 +450,6 @@ # -- Options for sphinx_issues extension ----------------------------------- issues_github_path = "pytest-dev/pytest" -issues_prefix = "issue #" -issues_pr_prefix = "pull request #" intersphinx_mapping = { From 175340e06b10d520ec70a05ffc01bcc106285f4b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 23 Jun 2024 06:22:44 +0000 Subject: [PATCH 143/199] [automated] Update plugin list (#12524) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 48f7cf9722e..c8591ece5e6 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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-apiver` Jun 21, 2024 N/A pytest :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) @@ -100,7 +101,7 @@ This list contains 1478 plugins. :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-assist` load testing library Jun 21, 2024 N/A pytest + :pypi:`pytest-assist` load testing library Jun 22, 2024 N/A pytest :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 @@ -637,7 +638,7 @@ This list contains 1478 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 22, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -1881,6 +1882,13 @@ This list contains 1478 plugins. apistellar plugin for pytest. + :pypi:`pytest-apiver` + *last release*: Jun 21, 2024, + *status*: N/A, + *requires*: pytest + + + :pypi:`pytest-appengine` *last release*: Feb 27, 2017, *status*: N/A, @@ -1980,7 +1988,7 @@ This list contains 1478 plugins. Useful assertion utilities for use with pytest :pypi:`pytest-assist` - *last release*: Jun 21, 2024, + *last release*: Jun 22, 2024, *status*: N/A, *requires*: pytest @@ -5739,7 +5747,7 @@ This list contains 1478 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Jun 16, 2024, + *last release*: Jun 22, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 From d582dcfc1676b1a35fe3c2d34be211d855a4afdb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 18:21:02 -0300 Subject: [PATCH 144/199] build(deps): Bump peter-evans/create-pull-request from 6.0.5 to 6.1.0 (#12527) Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6.0.5 to 6.1.0. - [Release notes](https://github.com/peter-evans/create-pull-request/releases) - [Commits](https://github.com/peter-evans/create-pull-request/compare/6d6857d36972b65feb161a90e484f2984215f83e...c5a7806660adbe173f04e3e038b0ccdcd758773c) --- updated-dependencies: - dependency-name: peter-evans/create-pull-request dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/update-plugin-list.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/update-plugin-list.yml b/.github/workflows/update-plugin-list.yml index 4da55e6d5a2..ade8452afd5 100644 --- a/.github/workflows/update-plugin-list.yml +++ b/.github/workflows/update-plugin-list.yml @@ -47,7 +47,7 @@ jobs: - name: Create Pull Request id: pr - uses: peter-evans/create-pull-request@6d6857d36972b65feb161a90e484f2984215f83e + uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c with: commit-message: '[automated] Update plugin list' author: 'pytest bot ' From 77416d64f5af575460b001c768ced541564a902b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 04:26:26 +0000 Subject: [PATCH 145/199] [pre-commit.ci] pre-commit autoupdate (#12528) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.9 → v0.4.10](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.9...v0.4.10) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c85175f2928..56d9a0c86cb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.9" + rev: "v0.4.10" hooks: - id: ruff args: ["--fix"] From 2b7eadf09042759c641f775a2541332eba52b5ad Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 25 Jun 2024 07:24:50 +0200 Subject: [PATCH 146/199] Update trainings (#12514) --- doc/en/index.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 58527ea7331..8de3b3993dd 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -4,8 +4,7 @@ .. sidebar:: **Next Open Trainings and Events** - - `pytest development sprint `_, **June 17th -- 22nd 2024**, Klaus (AT) / Remote - - `pytest tips and tricks for a better testsuite `_, at `Europython 2024 `_, **July 8th -- 14th 2024** (3h), Prague (CZ) + - `pytest tips and tricks for a better testsuite `_, at `Europython 2024 `_, **July 9th 2024** (3h), Prague (CZ) - `pytest: Professionelles Testen (nicht nur) für Python `_, at `CH Open Workshoptage `_, **September 2nd 2024**, HSLU Rotkreuz (CH) - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training), **March 4th -- 6th 2025**, Leipzig (DE) / Remote From 540ede34397600794e2979f2ee952bb999496582 Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 19:57:05 +0200 Subject: [PATCH 147/199] docs(expression.py): describe new `name` & `value` productions --- src/_pytest/mark/expression.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 2067110d23a..0daaaf22fc0 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -8,12 +8,15 @@ not_expr: 'not' not_expr | '(' expr ')' | ident ('(' name '=' value ( ', ' name '=' value )* ')')? ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ +name: a valid ident, but not a reserved keyword +value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' The semantics are: - Empty expression evaluates to False. - ident evaluates to True or False according to a provided matcher function. - or/and/not evaluate according to the usual boolean semantics. +- ident with parentheses and keyword arguments evaluates to True or False according to a provided matcher function. """ from __future__ import annotations From dd5719695376b19c2ad9db0789a08f459ad7c9fb Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 20:44:50 +0200 Subject: [PATCH 148/199] perf(expression): define `TokenType.STRING` as "string literal" for clearer errors --- src/_pytest/mark/expression.py | 2 +- testing/test_mark_expression.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 0daaaf22fc0..ce98e9c7e06 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -51,7 +51,7 @@ class TokenType(enum.Enum): IDENT = "identifier" EOF = "end of input" EQUAL = "=" - STRING = "str" + STRING = "string literal" COMMA = "," diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index c31ab44704c..b5f1f330a78 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -228,6 +228,7 @@ def test_invalid_idents(ident: str) -> None: r'escaping with "\\" not supported in marker expression', ), ("mark(empty_list=[])", r'unexpected character/s "\[\]"'), + ("'str'", "expected not OR left parenthesis OR identifier; got string literal"), ), ) def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? From 3d07791c36804a0ebcd6b0446f852e8e6745d30d Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Sat, 22 Jun 2024 20:49:21 +0200 Subject: [PATCH 149/199] chore: remove obsolete `TODO`s --- testing/test_mark.py | 2 +- testing/test_mark_expression.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/test_mark.py b/testing/test_mark.py index 6a94cc9f7c2..89eef7920cf 100644 --- a/testing/test_mark.py +++ b/testing/test_mark.py @@ -235,7 +235,7 @@ def test_two(): @pytest.mark.parametrize( ("expr", "expected_passed"), - [ # TODO: improve/sort out + [ ("car(color='red')", ["test_one"]), ("car(color='red') or car(color='blue')", ["test_one", "test_two"]), ("car and not car(temp=5)", ["test_one", "test_three"]), diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index b5f1f330a78..f8f5f9221cf 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -231,7 +231,7 @@ def test_invalid_idents(ident: str) -> None: ("'str'", "expected not OR left parenthesis OR identifier; got string literal"), ), ) -def test_invalid_kwarg_name_or_value( # TODO: move to `test_syntax_errors` ? +def test_invalid_kwarg_name_or_value( expr: str, expected_error_msg: str, mark_matcher: MarkMatcher ) -> None: with pytest.raises(ParseError, match=expected_error_msg): @@ -290,7 +290,7 @@ def test_keyword_expressions_with_numbers( ("builtin_matchers_mark(z=1)", False), ), ) -def test_builtin_matchers_keyword_expressions( # TODO: naming when decided +def test_builtin_matchers_keyword_expressions( expr: str, expected: bool, mark_matcher: MarkMatcher ) -> None: assert evaluate(expr, mark_matcher) is expected From 36b384afc7c2457a2772abcef1f3270a7067d0cb Mon Sep 17 00:00:00 2001 From: lovetheguitar Date: Tue, 25 Jun 2024 09:16:57 +0200 Subject: [PATCH 150/199] docs(expression.py): simplify grammar documentation by defining `kwargs` separately --- src/_pytest/mark/expression.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index ce98e9c7e06..89cc0e94d3b 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -5,9 +5,10 @@ expression: expr? EOF expr: and_expr ('or' and_expr)* and_expr: not_expr ('and' not_expr)* -not_expr: 'not' not_expr | '(' expr ')' | ident ('(' name '=' value ( ', ' name '=' value )* ')')? +not_expr: 'not' not_expr | '(' expr ')' | ident kwargs? ident: (\w|:|\+|-|\.|\[|\]|\\|/)+ +kwargs: ('(' name '=' value ( ', ' name '=' value )* ')') name: a valid ident, but not a reserved keyword value: (unescaped) string literal | (-)?[0-9]+ | 'False' | 'True' | 'None' From 634a83df94f9becbc06665cf52dd335c99af4fd6 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 25 Jun 2024 23:25:04 +0200 Subject: [PATCH 151/199] =?UTF-8?q?=F0=9F=A7=AA=20Unmeasure=20xfail=20test?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These tests are known to only be executed partially or not at all. So we always get incomplete, missing, and sometimes flaky, coverage in the test functions that are expected to fail. This change updates the ``coverage.py`` config to prevent said tests from influencing the coverage level measurement. --- .coveragerc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.coveragerc b/.coveragerc index a335557d4f1..b810471417f 100644 --- a/.coveragerc +++ b/.coveragerc @@ -29,3 +29,5 @@ exclude_lines = ^\s*if TYPE_CHECKING: ^\s*@overload( |$) + + ^\s*@pytest\.mark\.xfail From f2acc65485d7b21a42631df507c78d4c7237766d Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 25 Jun 2024 23:33:43 +0200 Subject: [PATCH 152/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PR=20#12531?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12531.contrib.rst | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 changelog/12531.contrib.rst diff --git a/changelog/12531.contrib.rst b/changelog/12531.contrib.rst new file mode 100644 index 00000000000..fd83b211f94 --- /dev/null +++ b/changelog/12531.contrib.rst @@ -0,0 +1,6 @@ +The coverage reporting configuration has been updated to exclude +pytest's own tests marked as expected to fail from the coverage +report. This has an effect of reducing the influence of flaky +tests on the resulting number. + +-- by :user`webknjaz` From 29e4b6b9f010828594df599fcf0b6164a59da27b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 26 Jun 2024 14:16:30 +0200 Subject: [PATCH 153/199] =?UTF-8?q?=F0=9F=93=9D=F0=9F=94=A5=20Remove=20the?= =?UTF-8?q?=20`:bpo:`=20RST=20role=20declaration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BPO is in read-only mode and all issues have been migrated to GitHub. This patch replaces the use of that role with `:issue:`, recently integrated via #12522. It also disables the built-in `extlinks` Sphinx extension, as it's no longer in use. --- doc/en/changelog.rst | 4 +++- doc/en/conf.py | 6 ------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 2c07f208ac3..8e3efd0479b 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -3360,7 +3360,9 @@ Bug Fixes - :issue:`5914`: pytester: fix :py:func:`~pytest.LineMatcher.no_fnmatch_line` when used after positive matching. -- :issue:`6082`: Fix line detection for doctest samples inside :py:class:`python:property` docstrings, as a workaround to :bpo:`17446`. +- :issue:`6082`: Fix line detection for doctest samples inside + :py:class:`python:property` docstrings, as a workaround to + :issue:`python/cpython#61648`. - :issue:`6254`: Fix compatibility with pytest-parallel (regression in pytest 5.3.0). diff --git a/doc/en/conf.py b/doc/en/conf.py index 7ec862a6064..0d440ec448a 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -82,7 +82,6 @@ "pygments_pytest", "sphinx.ext.autodoc", "sphinx.ext.autosummary", - "sphinx.ext.extlinks", "sphinx.ext.intersphinx", "sphinx.ext.todo", "sphinx.ext.viewcode", @@ -171,11 +170,6 @@ linkcheck_workers = 5 -extlinks = { - "bpo": ("https://bugs.python.org/issue%s", "bpo-%s"), -} - - nitpicky = True nitpick_ignore = [ # TODO (fix in pluggy?) From d75fa9f9b420124b1ad3ee5a43d04367dba3054c Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Wed, 26 Jun 2024 14:22:56 +0200 Subject: [PATCH 154/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PE=20#12533?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12533.contrib.rst | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/12533.contrib.rst diff --git a/changelog/12533.contrib.rst b/changelog/12533.contrib.rst new file mode 100644 index 00000000000..3da7007a0fd --- /dev/null +++ b/changelog/12533.contrib.rst @@ -0,0 +1,7 @@ +The ``extlinks`` Sphinx extension is no longer enabled. The ``:bpo:`` +role it used to declare has been removed with that. BPO itself has +migrated to GitHub some years ago and it is possible to link the +respective issues by using their GitHub issue numbers and the +``:issue:`` role that the ``sphinx-issues`` extension implements. + +-- by :user:`webknjaz` From 0ed2d79457b499119693588b71233db957e972c0 Mon Sep 17 00:00:00 2001 From: joseph-sentry <136376984+joseph-sentry@users.noreply.github.com> Date: Thu, 27 Jun 2024 08:41:02 -0400 Subject: [PATCH 155/199] junitxml: add timezone to testsuite timestamp (#12491) Signed-off-by: joseph-sentry Co-authored-by: Ronny Pfannschmidt --- AUTHORS | 1 + changelog/7662.improvement.rst | 1 + src/_pytest/junitxml.py | 5 ++++- testing/test_junitxml.py | 7 ++++--- 4 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 changelog/7662.improvement.rst diff --git a/AUTHORS b/AUTHORS index 5ae80eb02d3..ad7fccf6d59 100644 --- a/AUTHORS +++ b/AUTHORS @@ -213,6 +213,7 @@ Jordan Guymon Jordan Moldow Jordan Speicher Joseph Hunkeler +Joseph Sawaya Josh Karpel Joshua Bronson Jurko Gospodnetić diff --git a/changelog/7662.improvement.rst b/changelog/7662.improvement.rst new file mode 100644 index 00000000000..b6ae1ba7e4c --- /dev/null +++ b/changelog/7662.improvement.rst @@ -0,0 +1 @@ +Added timezone information to the testsuite timestamp in the JUnit XML report. diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index a525f1d14de..3a2cb59a6c1 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -11,6 +11,7 @@ from __future__ import annotations from datetime import datetime +from datetime import timezone import functools import os import platform @@ -664,7 +665,9 @@ def pytest_sessionfinish(self) -> None: skipped=str(self.stats["skipped"]), tests=str(numtests), time=f"{suite_time_delta:.3f}", - timestamp=datetime.fromtimestamp(self.suite_start_time).isoformat(), + timestamp=datetime.fromtimestamp(self.suite_start_time, timezone.utc) + .astimezone() + .isoformat(), hostname=platform.node(), ) global_properties = self._get_global_properties_node() diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py index 67234302a89..fd1fecb54f1 100644 --- a/testing/test_junitxml.py +++ b/testing/test_junitxml.py @@ -2,6 +2,7 @@ from __future__ import annotations from datetime import datetime +from datetime import timezone import os from pathlib import Path import platform @@ -218,11 +219,11 @@ def test_pass(): pass """ ) - start_time = datetime.now() + start_time = datetime.now(timezone.utc) result, dom = run_and_parse(family=xunit_family) node = dom.find_first_by_tag("testsuite") - timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f") - assert start_time <= timestamp < datetime.now() + timestamp = datetime.strptime(node["timestamp"], "%Y-%m-%dT%H:%M:%S.%f%z") + assert start_time <= timestamp < datetime.now(timezone.utc) def test_timing_function( self, pytester: Pytester, run_and_parse: RunAndParse, mock_timing From 0adcc21f4833f4f5177e9d2b9ac377cab851159f Mon Sep 17 00:00:00 2001 From: Zach Snicker Date: Sat, 29 Jun 2024 03:00:51 +0530 Subject: [PATCH 156/199] Support venv detection on Windows with mingw Python (#12545) Closes #12544 --- AUTHORS | 1 + changelog/12544.improvement.rst | 4 +++ doc/en/reference/reference.rst | 14 ++++----- src/_pytest/main.py | 16 ++-------- testing/test_collection.py | 54 +++++---------------------------- 5 files changed, 23 insertions(+), 66 deletions(-) create mode 100644 changelog/12544.improvement.rst diff --git a/AUTHORS b/AUTHORS index ad7fccf6d59..26fa27f9b2a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -454,6 +454,7 @@ Yusuke Kadowaki Yutian Li Yuval Shimon Zac Hatfield-Dodds +Zach Snicker Zachary Kneupper Zachary OBrien Zhouxin Qiu diff --git a/changelog/12544.improvement.rst b/changelog/12544.improvement.rst new file mode 100644 index 00000000000..9edbf7c4fec --- /dev/null +++ b/changelog/12544.improvement.rst @@ -0,0 +1,4 @@ +The _in_venv function now detects Python virtual environments by checking +for a pyvenv.cfg file, ensuring reliable detection on various platforms. + +-- by :user:`zachsnickers`. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 7c7b99d81c0..6926cd61bd3 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1702,13 +1702,13 @@ passed multiple times. The expected format is ``name=value``. For example:: This would tell ``pytest`` to not look into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory. - Additionally, ``pytest`` will attempt to intelligently identify and ignore a - virtualenv by the presence of an activation script. Any directory deemed to - be the root of a virtual environment will not be considered during test - collection unless ``--collect-in-virtualenv`` is given. Note also that - ``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if - you intend to run tests in a virtualenv with a base directory that matches - ``'.*'`` you *must* override ``norecursedirs`` in addition to using the + Additionally, ``pytest`` will attempt to intelligently identify and ignore + a virtualenv. Any directory deemed to be the root of a virtual environment + will not be considered during test collection unless + ``--collect-in-virtualenv`` is given. Note also that ``norecursedirs`` + takes precedence over ``--collect-in-virtualenv``; e.g. if you intend to + run tests in a virtualenv with a base directory that matches ``'.*'`` you + *must* override ``norecursedirs`` in addition to using the ``--collect-in-virtualenv`` flag. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 47ebad4713d..8ec26906003 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -369,22 +369,12 @@ def pytest_runtestloop(session: Session) -> bool: def _in_venv(path: Path) -> bool: """Attempt to detect if ``path`` is the root of a Virtual Environment by - checking for the existence of the appropriate activate script.""" - bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin") + checking for the existence of the pyvenv.cfg file. + [https://peps.python.org/pep-0405/]""" try: - if not bindir.is_dir(): - return False + return path.joinpath("pyvenv.cfg").is_file() except OSError: return False - activates = ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ) - return any(fname.name in activates for fname in bindir.iterdir()) def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: diff --git a/testing/test_collection.py b/testing/test_collection.py index 821c424196f..f5822240335 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -152,20 +152,8 @@ def test_ignored_certain_directories(self, pytester: Pytester) -> None: assert "test_notfound" not in s assert "test_found" in s - @pytest.mark.parametrize( - "fname", - ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ), - ) - def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None: - bindir = "Scripts" if sys.platform.startswith("win") else "bin" - ensure_file(pytester.path / "virtual" / bindir / fname) + def test_ignored_virtualenvs(self, pytester: Pytester) -> None: + ensure_file(pytester.path / "virtual" / "pyvenv.cfg") testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py") testfile.write_text("def test_hello(): pass", encoding="utf-8") @@ -179,23 +167,11 @@ def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None: result = pytester.runpytest("virtual") assert "test_invenv" in result.stdout.str() - @pytest.mark.parametrize( - "fname", - ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ), - ) def test_ignored_virtualenvs_norecursedirs_precedence( - self, pytester: Pytester, fname: str + self, pytester: Pytester ) -> None: - bindir = "Scripts" if sys.platform.startswith("win") else "bin" # norecursedirs takes priority - ensure_file(pytester.path / ".virtual" / bindir / fname) + ensure_file(pytester.path / ".virtual" / "pyvenv.cfg") testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py") testfile.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest("--collect-in-virtualenv") @@ -204,27 +180,13 @@ def test_ignored_virtualenvs_norecursedirs_precedence( result = pytester.runpytest("--collect-in-virtualenv", ".virtual") assert "test_invenv" in result.stdout.str() - @pytest.mark.parametrize( - "fname", - ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ), - ) - def test__in_venv(self, pytester: Pytester, fname: str) -> None: + def test__in_venv(self, pytester: Pytester) -> None: """Directly test the virtual env detection function""" - bindir = "Scripts" if sys.platform.startswith("win") else "bin" - # no bin/activate, not a virtualenv + # no pyvenv.cfg, not a virtualenv base_path = pytester.mkdir("venv") assert _in_venv(base_path) is False - # with bin/activate, totally a virtualenv - bin_path = base_path.joinpath(bindir) - bin_path.mkdir() - bin_path.joinpath(fname).touch() + # with pyvenv.cfg, totally a virtualenv + base_path.joinpath("pyvenv.cfg").touch() assert _in_venv(base_path) is True def test_custom_norecursedirs(self, pytester: Pytester) -> None: From dbf7dee8c8818de27d83d4593b0048ac5e06bfc7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 30 Jun 2024 12:05:11 +0000 Subject: [PATCH 157/199] [automated] Update plugin list (#12547) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 90 ++++++++++++++++++++------------ 1 file changed, 57 insertions(+), 33 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index c8591ece5e6..38f855de03c 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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 @@ -119,7 +119,7 @@ This list contains 1479 plugins. :pypi:`pytest-atf-allure` 基于allure-pytest进行自定义 Nov 29, 2023 N/A pytest (>=7.4.2,<8.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-attributes` A plugin that allows users to add attributes to their tests. These attributes can then be referenced by fixtures or the test itself. Jun 06, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-attributes` A plugin that allows users to add attributes to their tests. These attributes can then be referenced by fixtures or the test itself. Jun 24, 2024 4 - Beta pytest>=6.2.0 :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 @@ -127,7 +127,7 @@ This list contains 1479 plugins. :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-aux` templates/examples and aux for pytest Jun 18, 2024 N/A N/A + :pypi:`pytest-aux` templates/examples and aux for pytest Jun 27, 2024 N/A N/A :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 @@ -143,6 +143,7 @@ This list contains 1479 plugins. :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 Jan 31, 2024 5 - Production/Stable pytest>=7.0.0 :pypi:`pytest-batch-regression` A pytest plugin to repeat the entire test suite in batches. May 08, 2024 N/A pytest>=6.0.0 + :pypi:`pytest-bazel` A pytest runner with bazel support Jun 29, 2024 4 - Beta pytest :pypi:`pytest-bdd` BDD for pytest Jun 04, 2024 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 Dec 31, 2023 4 - Beta pytest >=5.0 @@ -152,7 +153,7 @@ This list contains 1479 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 21, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 28, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -241,7 +242,7 @@ This list contains 1479 plugins. :pypi:`pytest-cleanslate` Collects and executes pytest tests separately Jun 17, 2024 N/A pytest :pypi:`pytest_cleanup` Automated, comprehensive and well-organised pytest test cases. Jan 28, 2020 N/A N/A :pypi:`pytest-cleanuptotal` A cleanup plugin for pytest Mar 19, 2024 5 - Production/Stable N/A - :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Apr 19, 2024 N/A pytest<9.0.0,>=8.0.0 + :pypi:`pytest-clerk` A set of pytest fixtures to help with integration testing with Clerk. Jun 27, 2024 N/A pytest<9.0.0,>=8.0.0 :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) @@ -279,7 +280,7 @@ This list contains 1479 plugins. :pypi:`pytest-contexts` A plugin to run tests written with the Contexts framework using pytest May 19, 2021 4 - Beta N/A :pypi:`pytest-continuous` A pytest plugin to run tests continuously until failure or interruption. Apr 23, 2024 N/A N/A :pypi:`pytest-cookies` The pytest plugin for your Cookiecutter templates. 🍪 Mar 22, 2023 5 - Production/Stable pytest (>=3.9.0) - :pypi:`pytest-copie` The pytest plugin for your copier templates 📒 Jan 27, 2024 3 - Alpha pytest + :pypi:`pytest-copie` The pytest plugin for your copier templates 📒 Jun 26, 2024 3 - Alpha pytest :pypi:`pytest-copier` A pytest plugin to help testing Copier templates Dec 11, 2023 4 - Beta pytest>=7.3.2 :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 @@ -306,7 +307,7 @@ This list contains 1479 plugins. :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-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. Jun 17, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-custom-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. Jun 28, 2024 4 - Beta pytest>=6.2.0 :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 Apr 05, 2024 5 - Production/Stable pytest>=8 @@ -638,7 +639,7 @@ This list contains 1479 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 22, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jun 29, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -704,7 +705,7 @@ This list contains 1479 plugins. :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. Feb 09, 2024 4 - Beta pytest - :pypi:`pytest-invenio` Pytest fixtures for Invenio. Feb 28, 2024 5 - Production/Stable pytest <7.2.0,>=6 + :pypi:`pytest-invenio` Pytest fixtures for Invenio. Jun 27, 2024 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 @@ -790,7 +791,7 @@ This list contains 1479 plugins. :pypi:`pytest-logger` Plugin configuring handlers for loggers from Python logging module. Mar 10, 2024 5 - Production/Stable 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 May 23, 2024 5 - Production/Stable pytest==8.2.1 + :pypi:`pytest-logikal` Common testing environment Jun 27, 2024 5 - Production/Stable pytest==8.2.2 :pypi:`pytest-log-report` Package for creating a pytest test run reprot Dec 26, 2019 N/A N/A :pypi:`pytest-loguru` Pytest Loguru Mar 20, 2024 5 - Production/Stable pytest; extra == "test" :pypi:`pytest-loop` pytest plugin for looping tests Mar 30, 2024 5 - Production/Stable pytest @@ -1085,7 +1086,7 @@ This list contains 1479 plugins. :pypi:`pytest-ranking` A Pytest plugin for automatically prioritizing/ranking tests to speed up failure detection Jun 07, 2024 4 - Beta pytest>=7.4.3 :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A :pypi:`pytest-reana` Pytest fixtures for REANA. Mar 14, 2024 3 - Alpha N/A - :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Nov 21, 2023 N/A N/A + :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Jun 27, 2024 N/A N/A :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 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. Jun 19, 2024 5 - Production/Stable pytest>=6.2 @@ -1110,7 +1111,7 @@ This list contains 1479 plugins. :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 Feb 28, 2024 4 - Beta pytest - :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Feb 28, 2024 4 - Beta N/A + :pypi:`pytest-reporter-html1` A basic HTML report template for Pytest Jun 28, 2024 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) @@ -1181,7 +1182,7 @@ This list contains 1479 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jun 07, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jun 29, 2024 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-scenario-files` A pytest plugin that generates unit test scenarios from data files. May 19, 2024 5 - Production/Stable pytest>=7.2.0 :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A @@ -1191,13 +1192,14 @@ This list contains 1479 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jun 07, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jun 29, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 :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 25, 2024 N/A pytest :pypi:`pytest-sequence-markers` Pytest plugin for sequencing markers for execution of tests May 23, 2023 5 - Production/Stable N/A + :pypi:`pytest-server` test server exec cmd Jun 24, 2024 N/A N/A :pypi:`pytest-server-fixtures` Extensible server fixures for py.test Dec 19, 2023 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 Jun 17, 2024 3 - Alpha pytest>=6.2 @@ -1268,7 +1270,7 @@ This list contains 1479 plugins. :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 Jun 03, 2024 N/A pytest<8,>5.4.0 + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jun 26, 2024 N/A pytest<8,>5.4.0 :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 14, 2024 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 @@ -1358,6 +1360,7 @@ This list contains 1479 plugins. :pypi:`pytest-testreport-new` Oct 07, 2023 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-tracer-for-pytest` A plugin that allows coll test data for use on Test Tracer Jun 28, 2024 4 - Beta pytest>=6.2.0 :pypi:`pytest-test-utils` Feb 08, 2024 N/A pytest >=3.9 :pypi:`pytest-tesults` Tesults plugin for pytest Feb 15, 2024 5 - Production/Stable pytest >=3.5.0 :pypi:`pytest-textual-snapshot` Snapshot testing for Textual apps Aug 23, 2023 4 - Beta pytest (>=7.0.0) @@ -1988,7 +1991,7 @@ This list contains 1479 plugins. Useful assertion utilities for use with pytest :pypi:`pytest-assist` - *last release*: Jun 22, 2024, + *last release*: Jun 24, 2024, *status*: N/A, *requires*: pytest @@ -2114,7 +2117,7 @@ This list contains 1479 plugins. pytest plugin to select tests based on attributes similar to the nose-attrib plugin :pypi:`pytest-attributes` - *last release*: Jun 06, 2024, + *last release*: Jun 24, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 @@ -2170,7 +2173,7 @@ This list contains 1479 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. :pypi:`pytest-aux` - *last release*: Jun 18, 2024, + *last release*: Jun 27, 2024, *status*: N/A, *requires*: N/A @@ -2281,6 +2284,13 @@ This list contains 1479 plugins. A pytest plugin to repeat the entire test suite in batches. + :pypi:`pytest-bazel` + *last release*: Jun 29, 2024, + *status*: 4 - Beta, + *requires*: pytest + + A pytest runner with bazel support + :pypi:`pytest-bdd` *last release*: Jun 04, 2024, *status*: 6 - Mature, @@ -2345,7 +2355,7 @@ This list contains 1479 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Jun 21, 2024, + *last release*: Jun 28, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -2968,7 +2978,7 @@ This list contains 1479 plugins. A cleanup plugin for pytest :pypi:`pytest-clerk` - *last release*: Apr 19, 2024, + *last release*: Jun 27, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.0.0 @@ -3234,7 +3244,7 @@ This list contains 1479 plugins. The pytest plugin for your Cookiecutter templates. 🍪 :pypi:`pytest-copie` - *last release*: Jan 27, 2024, + *last release*: Jun 26, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -3423,7 +3433,7 @@ This list contains 1479 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report :pypi:`pytest-custom-outputs` - *last release*: Jun 17, 2024, + *last release*: Jun 28, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 @@ -5747,7 +5757,7 @@ This list contains 1479 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Jun 22, 2024, + *last release*: Jun 29, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 @@ -6209,9 +6219,9 @@ This list contains 1479 plugins. Pytest plugin for checking charm relation interface protocol compliance. :pypi:`pytest-invenio` - *last release*: Feb 28, 2024, + *last release*: Jun 27, 2024, *status*: 5 - Production/Stable, - *requires*: pytest <7.2.0,>=6 + *requires*: pytest<7.2.0,>=6 Pytest fixtures for Invenio. @@ -6811,9 +6821,9 @@ This list contains 1479 plugins. :pypi:`pytest-logikal` - *last release*: May 23, 2024, + *last release*: Jun 27, 2024, *status*: 5 - Production/Stable, - *requires*: pytest==8.2.1 + *requires*: pytest==8.2.2 Common testing environment @@ -8876,7 +8886,7 @@ This list contains 1479 plugins. Pytest fixtures for REANA. :pypi:`pytest-recorder` - *last release*: Nov 21, 2023, + *last release*: Jun 27, 2024, *status*: N/A, *requires*: N/A @@ -9051,7 +9061,7 @@ This list contains 1479 plugins. Generate Pytest reports with templates :pypi:`pytest-reporter-html1` - *last release*: Feb 28, 2024, + *last release*: Jun 28, 2024, *status*: 4 - Beta, *requires*: N/A @@ -9548,7 +9558,7 @@ This list contains 1479 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: Jun 07, 2024, + *last release*: Jun 29, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9618,7 +9628,7 @@ This list contains 1479 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Jun 07, 2024, + *last release*: Jun 29, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9666,6 +9676,13 @@ This list contains 1479 plugins. Pytest plugin for sequencing markers for execution of tests + :pypi:`pytest-server` + *last release*: Jun 24, 2024, + *status*: N/A, + *requires*: N/A + + test server exec cmd + :pypi:`pytest-server-fixtures` *last release*: Dec 19, 2023, *status*: 5 - Production/Stable, @@ -10157,7 +10174,7 @@ This list contains 1479 plugins. :pypi:`pytest-splunk-addon` - *last release*: Jun 03, 2024, + *last release*: Jun 26, 2024, *status*: N/A, *requires*: pytest<8,>5.4.0 @@ -10786,6 +10803,13 @@ This list contains 1479 plugins. Plugin for py.test to run relevant tests, based on naively checking if a test contains a reference to the symbol you supply + :pypi:`pytest-test-tracer-for-pytest` + *last release*: Jun 28, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin that allows coll test data for use on Test Tracer + :pypi:`pytest-test-utils` *last release*: Feb 08, 2024, *status*: N/A, From 41ca4dd44eedd959f3fe562895519e3d9df185ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= Date: Tue, 18 Jun 2024 12:09:47 +0200 Subject: [PATCH 158/199] testresult: correctly apply verbose word markup and avoid crash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The following snippet would have resulted in crash on multiple places since `_get_verbose_word` expects only string, not a tuple. ```python @pytest.hookimpl(tryfirst=True) def pytest_report_teststatus(report: pytest.CollectReport | pytest.TestReport, config: pytest.Config): if report.when == "call": return ("error", "A", ("AVC", {"bold": True, "red": True})) return None ``` ``` Traceback (most recent call last): File "/home/pbrezina/workspace/sssd/.venv/bin/pytest", line 8, in sys.exit(console_main()) ^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/config/__init__.py", line 207, in console_main code = main() ^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/config/__init__.py", line 179, in main ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_hooks.py", line 513, in __call__ return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall raise exception.with_traceback(exception.__traceback__) File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 103, in _multicall res = hook_impl.function(*args) ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/main.py", line 333, in pytest_cmdline_main return wrap_session(config, _main) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/main.py", line 321, in wrap_session config.hook.pytest_sessionfinish( File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_hooks.py", line 513, in __call__ return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall raise exception.with_traceback(exception.__traceback__) File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 122, in _multicall teardown.throw(exception) # type: ignore[union-attr] ^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/logging.py", line 872, in pytest_sessionfinish return (yield) ^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 124, in _multicall teardown.send(result) # type: ignore[union-attr] ^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/terminal.py", line 899, in pytest_sessionfinish self.config.hook.pytest_terminal_summary( File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_hooks.py", line 513, in __call__ return self._hookexec(self.name, self._hookimpls.copy(), kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_manager.py", line 120, in _hookexec return self._inner_hookexec(hook_name, methods, kwargs, firstresult) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 139, in _multicall raise exception.with_traceback(exception.__traceback__) File "/home/pbrezina/workspace/sssd/.venv/lib64/python3.11/site-packages/pluggy/_callers.py", line 124, in _multicall teardown.send(result) # type: ignore[union-attr] ^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/terminal.py", line 923, in pytest_terminal_summary self.short_test_summary() File "/home/pbrezina/workspace/pytest/src/_pytest/terminal.py", line 1272, in short_test_summary action(lines) File "/home/pbrezina/workspace/pytest/src/_pytest/terminal.py", line 1205, in show_simple line = _get_line_with_reprcrash_message( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/terminal.py", line 1429, in _get_line_with_reprcrash_message word = tw.markup(verbose_word, **word_markup) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/pbrezina/workspace/pytest/src/_pytest/_io/terminalwriter.py", line 114, in markup text = "".join(f"\x1b[{cod}m" for cod in esc) + text + "\x1b[0m" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~ TypeError: can only concatenate str (not "tuple") to str ``` Signed-off-by: Pavel Březina --- changelog/12472.bugfix.rst | 1 + src/_pytest/reports.py | 21 +++++++++++++++++++-- src/_pytest/terminal.py | 24 +++++++++++++----------- testing/test_terminal.py | 13 ++++++++----- 4 files changed, 41 insertions(+), 18 deletions(-) create mode 100644 changelog/12472.bugfix.rst diff --git a/changelog/12472.bugfix.rst b/changelog/12472.bugfix.rst new file mode 100644 index 00000000000..f08e9d1f90b --- /dev/null +++ b/changelog/12472.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when returning category ``"error"`` or ``"failed"`` with a custom test status from :hook:`pytest_report_teststatus` hook -- :user:`pbrezina`. diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 2f39adbfa6f..9e61d7def40 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -13,6 +13,7 @@ from typing import Literal from typing import Mapping from typing import NoReturn +from typing import Sequence from typing import TYPE_CHECKING from _pytest._code.code import ExceptionChainRepr @@ -30,6 +31,7 @@ from _pytest.config import Config from _pytest.nodes import Collector from _pytest.nodes import Item +from _pytest.outcomes import fail from _pytest.outcomes import skip @@ -190,11 +192,26 @@ def head_line(self) -> str | None: return domain return None - def _get_verbose_word(self, config: Config): + def _get_verbose_word_with_markup( + self, config: Config, default_markup: Mapping[str, bool] + ) -> tuple[str, Mapping[str, bool]]: _category, _short, verbose = config.hook.pytest_report_teststatus( report=self, config=config ) - return verbose + + if isinstance(verbose, str): + return verbose, default_markup + + if isinstance(verbose, Sequence) and len(verbose) == 2: + word, markup = verbose + if isinstance(word, str) and isinstance(markup, Mapping): + return word, markup + + fail( + "pytest_report_teststatus() hook (from a plugin) returned " + "an invalid verbose value: {verbose!r}.\nExpected either a string " + "or a tuple of (word, markup)." + ) def _to_json(self) -> dict[str, Any]: """Return the contents of this report as a dict of builtin entries, diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 26c573f583e..f364316e2e2 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -1206,10 +1206,10 @@ def show_simple(lines: list[str], *, stat: str) -> None: def show_xfailed(lines: list[str]) -> None: xfailed = self.stats.get("xfailed", []) for rep in xfailed: - verbose_word = rep._get_verbose_word(self.config) - markup_word = self._tw.markup( - verbose_word, **{_color_for_type["warnings"]: True} + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) nodeid = _get_node_id_with_markup(self._tw, self.config, rep) line = f"{markup_word} {nodeid}" reason = rep.wasxfail @@ -1221,10 +1221,10 @@ def show_xfailed(lines: list[str]) -> None: def show_xpassed(lines: list[str]) -> None: xpassed = self.stats.get("xpassed", []) for rep in xpassed: - verbose_word = rep._get_verbose_word(self.config) - markup_word = self._tw.markup( - verbose_word, **{_color_for_type["warnings"]: True} + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) nodeid = _get_node_id_with_markup(self._tw, self.config, rep) line = f"{markup_word} {nodeid}" reason = rep.wasxfail @@ -1237,10 +1237,10 @@ def show_skipped(lines: list[str]) -> None: fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: return - verbose_word = skipped[0]._get_verbose_word(self.config) - markup_word = self._tw.markup( - verbose_word, **{_color_for_type["warnings"]: True} + verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) prefix = "Skipped: " for num, fspath, lineno, reason in fskips: if reason.startswith(prefix): @@ -1421,8 +1421,10 @@ def _get_line_with_reprcrash_message( config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool] ) -> str: """Get summary line for a report, trying to add reprcrash message.""" - verbose_word = rep._get_verbose_word(config) - word = tw.markup(verbose_word, **word_markup) + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + config, word_markup + ) + word = tw.markup(verbose_word, **verbose_markup) node = _get_node_id_with_markup(tw, config, rep) line = f"{word} {node}" diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 01a84fd8d2c..5e3f631e22b 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -326,16 +326,17 @@ def test_rewrite(self, pytester: Pytester, monkeypatch) -> None: tr.rewrite("hey", erase=True) assert f.getvalue() == "hello" + "\r" + "hey" + (6 * " ") + @pytest.mark.parametrize("category", ["foo", "failed", "error", "passed"]) def test_report_teststatus_explicit_markup( - self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping + self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping, category: str ) -> None: """Test that TerminalReporter handles markup explicitly provided by a pytest_report_teststatus hook.""" monkeypatch.setenv("PY_COLORS", "1") pytester.makeconftest( - """ + f""" def pytest_report_teststatus(report): - return 'foo', 'F', ('FOO', {'red': True}) + return {category !r}, 'F', ('FOO', {{'red': True}}) """ ) pytester.makepyfile( @@ -344,7 +345,9 @@ def test_foobar(): pass """ ) + result = pytester.runpytest("-v") + assert not result.stderr.lines result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"]) ) @@ -2385,8 +2388,8 @@ def __init__(self): self.option = Namespace(verbose=0) class rep: - def _get_verbose_word(self, *args): - return mocked_verbose_word + def _get_verbose_word_with_markup(self, *args): + return mocked_verbose_word, {} class longrepr: class reprcrash: From 269ded17a6fabf632d5b270421b9ad4e6bbff9ac Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 27 Jun 2024 10:27:06 -0300 Subject: [PATCH 159/199] Update src/_pytest/reports.py --- src/_pytest/reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 9e61d7def40..1521ad7671f 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -208,7 +208,7 @@ def _get_verbose_word_with_markup( return word, markup fail( - "pytest_report_teststatus() hook (from a plugin) returned " + f"pytest_report_teststatus() hook (from a plugin) returned " "an invalid verbose value: {verbose!r}.\nExpected either a string " "or a tuple of (word, markup)." ) From 2c1ed505d9a969ad97d35000569cd603aae3fe62 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:27:33 +0000 Subject: [PATCH 160/199] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 1521ad7671f..9e61d7def40 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -208,7 +208,7 @@ def _get_verbose_word_with_markup( return word, markup fail( - f"pytest_report_teststatus() hook (from a plugin) returned " + "pytest_report_teststatus() hook (from a plugin) returned " "an invalid verbose value: {verbose!r}.\nExpected either a string " "or a tuple of (word, markup)." ) From 201ed9f6a53445c6851881cb05fb552ff391cd86 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 27 Jun 2024 10:28:42 -0300 Subject: [PATCH 161/199] Update src/_pytest/reports.py --- src/_pytest/reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 9e61d7def40..f23ee0831c5 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -209,7 +209,7 @@ def _get_verbose_word_with_markup( fail( "pytest_report_teststatus() hook (from a plugin) returned " - "an invalid verbose value: {verbose!r}.\nExpected either a string " + f"an invalid verbose value: {verbose!r}.\nExpected either a string " "or a tuple of (word, markup)." ) From f502f1db31ec135b8b3aa73931a12e13d8d88ed7 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Thu, 27 Jun 2024 10:49:38 -0300 Subject: [PATCH 162/199] Update src/_pytest/reports.py --- src/_pytest/reports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index f23ee0831c5..77cbf773e23 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -207,7 +207,7 @@ def _get_verbose_word_with_markup( if isinstance(word, str) and isinstance(markup, Mapping): return word, markup - fail( + fail( # pragma: no cover "pytest_report_teststatus() hook (from a plugin) returned " f"an invalid verbose value: {verbose!r}.\nExpected either a string " "or a tuple of (word, markup)." From de4e3cffc2309c7b9f41ca2e987a625e932ba0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sviatoslav=20Sydorenko=20=28=D0=A1=D0=B2=D1=8F=D1=82=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B2=20=D0=A1=D0=B8=D0=B4=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=BA=D0=BE=29?= Date: Mon, 1 Jul 2024 16:47:47 +0200 Subject: [PATCH 163/199] =?UTF-8?q?Revert=20"=F0=9F=9A=91=F0=9F=A7=AA=20Se?= =?UTF-8?q?t=20the=20Codecov=20token=20directly=20in=20GHA"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 64b87eee217..d78e0900fd9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -250,7 +250,6 @@ jobs: with: fail_ci_if_error: false files: ./coverage.xml - token: 1eca3b1f-31a2-4fb8-a8c3-138b441b50a7 #repo token; cfg read fails verbose: true check: # This job does nothing and is only used for the branch protection From 2455e99ea33a49794caa84f3cd25f2e598fda642 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:00:26 +0200 Subject: [PATCH 164/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20markup=20to=20the?= =?UTF-8?q?=20PR=20#12545=20changelog=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12544.improvement.rst | 7 +++---- changelog/12545.improvement.rst | 1 + 2 files changed, 4 insertions(+), 4 deletions(-) create mode 120000 changelog/12545.improvement.rst diff --git a/changelog/12544.improvement.rst b/changelog/12544.improvement.rst index 9edbf7c4fec..41125f5d939 100644 --- a/changelog/12544.improvement.rst +++ b/changelog/12544.improvement.rst @@ -1,4 +1,3 @@ -The _in_venv function now detects Python virtual environments by checking -for a pyvenv.cfg file, ensuring reliable detection on various platforms. - --- by :user:`zachsnickers`. +The ``_in_venv()`` function now detects Python virtual environments by +checking for a :file:`pyvenv.cfg` file, ensuring reliable detection on +various platforms -- by :user:`zachsnickers`. diff --git a/changelog/12545.improvement.rst b/changelog/12545.improvement.rst new file mode 120000 index 00000000000..41a1e6bfa49 --- /dev/null +++ b/changelog/12545.improvement.rst @@ -0,0 +1 @@ +12544.improvement.rst \ No newline at end of file From 388cf8f381275f518d6626ca83b3e2855a20159a Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:12:27 +0200 Subject: [PATCH 165/199] Correct the `:user:` role @ PR #12531 change note --- changelog/12531.contrib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/12531.contrib.rst b/changelog/12531.contrib.rst index fd83b211f94..12083fc320e 100644 --- a/changelog/12531.contrib.rst +++ b/changelog/12531.contrib.rst @@ -3,4 +3,4 @@ pytest's own tests marked as expected to fail from the coverage report. This has an effect of reducing the influence of flaky tests on the resulting number. --- by :user`webknjaz` +-- by :user:`webknjaz` From a34f2f06dbc9eb441f65022b5e61a2708351a526 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:15:31 +0200 Subject: [PATCH 166/199] =?UTF-8?q?=F0=9F=93=9D=F0=9F=92=84=20Drop=20trail?= =?UTF-8?q?ing=20period=20from=20#12502=20byline?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12502.contrib.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/12502.contrib.rst b/changelog/12502.contrib.rst index a7db422526f..940a2d7a120 100644 --- a/changelog/12502.contrib.rst +++ b/changelog/12502.contrib.rst @@ -4,4 +4,4 @@ the automatically created pull requests and re-open them to trigger the CI runs. From now on, they only need to click the `Ready for review` button instead. --- by :user:`webknjaz`. +-- by :user:`webknjaz` From 9c319d6605683e2236c2d6261386b544dfcbfda8 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:21:24 +0200 Subject: [PATCH 167/199] =?UTF-8?q?=F0=9F=A7=AA=20Lint=20for=20typos=20in?= =?UTF-8?q?=20`:user:`=20RST=20role?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It is easy to forget backticks in change note bylines. It's happened in #12531 already, requiring a hotfix in #12560. The pre-commit based check idea is coming from the Tox project and have been battle-tested in aiohttp, CherryPy, and other ecosystems. --- .pre-commit-config.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 56d9a0c86cb..431b21f9942 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -102,6 +102,14 @@ repos: ) $ files: ^changelog/ + - id: changelogs-user-role + name: Changelog files should use a non-broken :user:`name` role + language: pygrep + entry: :user:([^`]+`?|`[^`]+[\s,]) + pass_filenames: true + types: + - file + - rst - id: py-deprecated name: py library is deprecated language: pygrep From 57fe9f53c64cd9bb127f650774d337a7f82f1445 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:26:35 +0200 Subject: [PATCH 168/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20change=20note?= =?UTF-8?q?=20for=20PR=20#12562?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12562.contrib.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 changelog/12562.contrib.rst diff --git a/changelog/12562.contrib.rst b/changelog/12562.contrib.rst new file mode 100644 index 00000000000..0d30495983a --- /dev/null +++ b/changelog/12562.contrib.rst @@ -0,0 +1,2 @@ +Possible typos in using the ``:user:`` RST role is now being linted +through the pre-commit tool integration -- by :user:`webknjaz`. From 8398609f08360932bb1853da494e9a3cd04961f3 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:30:21 +0200 Subject: [PATCH 169/199] =?UTF-8?q?=F0=9F=93=9D=F0=9F=9A=91=20Fix=20RST=20?= =?UTF-8?q?list=20in=20#12264=20changelog=20entry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It was being rendered inline and now it's not. --- changelog/12204.bugfix.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst index 9690f513a38..801daab994a 100644 --- a/changelog/12204.bugfix.rst +++ b/changelog/12204.bugfix.rst @@ -3,5 +3,8 @@ Fix a regression in pytest 8.0 where tracebacks get longer and longer when multi Also fix a similar regression in pytest 5.4 for collectors which raise during setup. The fix necessitated internal changes which may affect some plugins: -- ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)`` instead of ``exc``. -- ``SetupState.stack`` failures are now a tuple ``(exc, tb)`` instead of ``exc``. + +* ``FixtureDef.cached_result[2]`` is now a tuple ``(exc, tb)`` + instead of ``exc``. +* ``SetupState.stack`` failures are now a tuple ``(exc, tb)`` + instead of ``exc``. From 94c0122a17f1136b44b90d6a93441fae64c47a2b Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:35:43 +0200 Subject: [PATCH 170/199] =?UTF-8?q?=F0=9F=93=9D=20Use=20past=20tense=20in?= =?UTF-8?q?=20#12204=20change=20note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12204.bugfix.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst index 801daab994a..50f9cc744d4 100644 --- a/changelog/12204.bugfix.rst +++ b/changelog/12204.bugfix.rst @@ -1,6 +1,7 @@ -Fix a regression in pytest 8.0 where tracebacks get longer and longer when multiple tests fail due to a shared higher-scope fixture which raised. +Fixed a regression in pytest 8.0 where tracebacks get longer and longer when multiple +tests fail due to a shared higher-scope fixture which raised. -Also fix a similar regression in pytest 5.4 for collectors which raise during setup. +Also fixed a similar regression in pytest 5.4 for collectors which raise during setup. The fix necessitated internal changes which may affect some plugins: From ffcc001562bce004952547fc1462f94838071338 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:46:38 +0200 Subject: [PATCH 171/199] =?UTF-8?q?=F0=9F=93=9D=20Add=20a=20byline=20to=20?= =?UTF-8?q?#12264=20change=20note?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12204.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/12204.bugfix.rst b/changelog/12204.bugfix.rst index 50f9cc744d4..099ad70610a 100644 --- a/changelog/12204.bugfix.rst +++ b/changelog/12204.bugfix.rst @@ -1,5 +1,5 @@ Fixed a regression in pytest 8.0 where tracebacks get longer and longer when multiple -tests fail due to a shared higher-scope fixture which raised. +tests fail due to a shared higher-scope fixture which raised -- by :user:`bluetech`. Also fixed a similar regression in pytest 5.4 for collectors which raise during setup. From b62974e63f432d66b91ee2d48e73e80f02fe66a1 Mon Sep 17 00:00:00 2001 From: Sviatoslav Sydorenko Date: Tue, 2 Jul 2024 15:47:23 +0200 Subject: [PATCH 172/199] =?UTF-8?q?=F0=9F=93=9D=F0=9F=92=85=20Link=20#1226?= =?UTF-8?q?4=20change=20note=20to=20#12204?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- changelog/12264.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 120000 changelog/12264.bugfix.rst diff --git a/changelog/12264.bugfix.rst b/changelog/12264.bugfix.rst new file mode 120000 index 00000000000..e5704e6e819 --- /dev/null +++ b/changelog/12264.bugfix.rst @@ -0,0 +1 @@ +12204.bugfix.rst \ No newline at end of file From 49bb5c89a6111d39455482fa415a3fd526b3b497 Mon Sep 17 00:00:00 2001 From: Virendra Patil <70162563+virendrapatil24@users.noreply.github.com> Date: Wed, 3 Jul 2024 00:28:08 +0530 Subject: [PATCH 173/199] Improved handling of invalid regex pattern in pytest.raises (#12526) --- AUTHORS | 1 + changelog/12505.bugfix.rst | 1 + src/_pytest/python_api.py | 9 +++++++++ testing/python/raises.py | 20 ++++++++++++++++++++ 4 files changed, 31 insertions(+) create mode 100644 changelog/12505.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 26fa27f9b2a..7fd388505dd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -430,6 +430,7 @@ Victor Rodriguez Victor Uriarte Vidar T. Fauske Vijay Arora +Virendra Patil Virgil Dupras Vitaly Lashmanov Vivaan Verma diff --git a/changelog/12505.bugfix.rst b/changelog/12505.bugfix.rst new file mode 100644 index 00000000000..f55a8a17e4b --- /dev/null +++ b/changelog/12505.bugfix.rst @@ -0,0 +1 @@ +Improve handling of invalid regex patterns in :func:`pytest.raises(match=r'...') ` by providing a clear error message. diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index c1e851391b0..4174a55b589 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -7,6 +7,7 @@ import math from numbers import Complex import pprint +import re from types import TracebackType from typing import Any from typing import Callable @@ -986,6 +987,14 @@ def __init__( self.message = message self.match_expr = match_expr self.excinfo: _pytest._code.ExceptionInfo[E] | None = None + if self.match_expr is not None: + re_error = None + try: + re.compile(self.match_expr) + except re.error as e: + re_error = e + if re_error is not None: + fail(f"Invalid regex pattern provided to 'match': {re_error}") def __enter__(self) -> _pytest._code.ExceptionInfo[E]: self.excinfo = _pytest._code.ExceptionInfo.for_later() diff --git a/testing/python/raises.py b/testing/python/raises.py index 271dd3e5a8c..2011c81615e 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -132,6 +132,26 @@ def test_division(example_input, expectation): result = pytester.runpytest() result.stdout.fnmatch_lines(["*2 failed*"]) + def test_raises_with_invalid_regex(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + import pytest + + def test_invalid_regex(): + with pytest.raises(ValueError, match="invalid regex character ["): + raise ValueError() + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + [ + "*Invalid regex pattern provided to 'match': unterminated character set at position 24*", + ] + ) + result.stdout.no_fnmatch_line("*Traceback*") + result.stdout.no_fnmatch_line("*File*") + result.stdout.no_fnmatch_line("*line*") + def test_noclass(self) -> None: with pytest.raises(TypeError): pytest.raises("wrong", lambda: None) # type: ignore[call-overload] From 3a6abcc55963df96d29d12bb67b76a672ab0d429 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 00:16:55 +0000 Subject: [PATCH 174/199] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.10 → v0.5.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.10...v0.5.0) - [github.com/adamchainz/blacken-docs: 1.16.0 → 1.18.0](https://github.com/adamchainz/blacken-docs/compare/1.16.0...1.18.0) - [github.com/pre-commit/mirrors-mypy: v1.10.0 → v1.10.1](https://github.com/pre-commit/mirrors-mypy/compare/v1.10.0...v1.10.1) - pyproject-fmt: 2.1.3 => 2.1.4 Co-authored-by: Pierre Sassoulas --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 431b21f9942..ebcc797cc1e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.4.10" + rev: "v0.5.0" hooks: - id: ruff args: ["--fix"] @@ -12,7 +12,7 @@ repos: - id: end-of-file-fixer - id: check-yaml - repo: https://github.com/adamchainz/blacken-docs - rev: 1.16.0 + rev: 1.18.0 hooks: - id: blacken-docs additional_dependencies: [black==24.1.1] @@ -21,7 +21,7 @@ repos: hooks: - id: python-use-type-annotations - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.10.0 + rev: v1.10.1 hooks: - id: mypy files: ^(src/|testing/|scripts/) @@ -38,7 +38,7 @@ repos: # on <3.11 - exceptiongroup>=1.0.0rc8 - repo: https://github.com/tox-dev/pyproject-fmt - rev: "2.1.3" + rev: "2.1.4" hooks: - id: pyproject-fmt # https://pyproject-fmt.readthedocs.io/en/latest/#calculating-max-supported-python-version From 375e748e7f4ffd087fdc45062f2148c357a64ed6 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 4 Jul 2024 11:08:14 +0200 Subject: [PATCH 175/199] [lint] Handle misplaced-bare-raise with ruff instead of pylint --- pyproject.toml | 1 + src/_pytest/logging.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e0ed2b90061..d140ca43c06 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -221,6 +221,7 @@ disable = [ "method-hidden", "missing-docstring", "missing-timeout", + "misplaced-bare-raise", # PLE0704 from ruff "multiple-statements", # multiple-statements-on-one-line-colon (E701) from ruff "no-else-break", "no-else-continue", diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index fe3be060fdd..44af8ff2041 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -399,7 +399,7 @@ def handleError(self, record: logging.LogRecord) -> None: # The default behavior of logging is to print "Logging error" # to stderr with the call stack and some extra details. # pytest wants to make such mistakes visible during testing. - raise # pylint: disable=misplaced-bare-raise + raise # noqa: PLE0704 @final From b2dc022b3039d23140a50faabdb37676f18d5a2d Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 4 Jul 2024 11:13:22 +0200 Subject: [PATCH 176/199] [ruff] Fix all RUF024 (no mutable objects as values in 'dict.fromkeys' --- testing/test_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_runner.py b/testing/test_runner.py index 79f7a3fd4d3..1b59ff78633 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -533,7 +533,7 @@ class TestClass(object): ) def test_report_extra_parameters(reporttype: type[reports.BaseReport]) -> None: args = list(inspect.signature(reporttype.__init__).parameters.keys())[1:] - basekw: dict[str, list[object]] = dict.fromkeys(args, []) + basekw: dict[str, list[object]] = {arg: [] for arg in args} report = reporttype(newthing=1, **basekw) assert report.newthing == 1 From b2908fd56f998c17f70d74333f81474812aa2b93 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 4 Jul 2024 11:21:33 +0200 Subject: [PATCH 177/199] [pylint] Disable new messages raised since last pre-commit auto-update --- pyproject.toml | 3 ++- testing/test_mark_expression.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d140ca43c06..1eb4871bc21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -284,7 +284,8 @@ disable = [ "useless-import-alias", "useless-return", "using-constant-test", - "wrong-import-order", + "wrong-import-order", # handled by isort / ruff + "wrong-import-position", # handled by isort / ruff ] [tool.check-wheel-contents] diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index f8f5f9221cf..a61a9f21560 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -243,7 +243,7 @@ def mark_matcher() -> MarkMatcher: markers = [ pytest.mark.number_mark(a=1, b=2, c=3, d=999_999).mark, pytest.mark.builtin_matchers_mark(x=True, y=False, z=None).mark, - pytest.mark.str_mark( + pytest.mark.str_mark( # pylint: disable-next=non-ascii-name m="M", space="with space", empty="", aaאבגדcc="aaאבגדcc", אבגד="אבגד" ).mark, ] From 92ec5c349dc92d59bc77233612471d048b19364e Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Thu, 4 Jul 2024 11:46:14 +0200 Subject: [PATCH 178/199] [python 3.13] Mark functool.partial test to be skipped Closes #12552 --- testing/python/fixtures.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index bc091bb1f27..5c3a6a35b34 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -76,6 +76,16 @@ class B(A): assert getfuncargnames(B.static, cls=B) == ("arg1", "arg2") +@pytest.mark.skipif( + sys.version_info >= (3, 13), + reason="""\ +In python 3.13, this will raise FutureWarning: +functools.partial will be a method descriptor in future Python versions; +wrap it in staticmethod() if you want to preserve the old behavior + +But the wrapped 'functools.partial' is tested by 'test_getfuncargnames_staticmethod_partial' below. +""", +) def test_getfuncargnames_partial(): """Check getfuncargnames for methods defined with functools.partial (#5701)""" import functools From cf5369db0f14df40c6145b0a90a85577782a13c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pavel=20B=C5=99ezina?= Date: Fri, 5 Jul 2024 14:19:07 +0200 Subject: [PATCH 179/199] Add --no-fold-skipped cli option (#12567) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows users to disable the default behavior of folding skipped tests in the short summary. Fix #9876 Signed-off-by: Pavel Březina --- changelog/12567.feature.rst | 7 +++++++ doc/en/how-to/output.rst | 5 +++++ src/_pytest/terminal.py | 35 +++++++++++++++++++++++++++++++++- testing/test_terminal.py | 38 +++++++++++++++++++++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 changelog/12567.feature.rst diff --git a/changelog/12567.feature.rst b/changelog/12567.feature.rst new file mode 100644 index 00000000000..3690d7aff68 --- /dev/null +++ b/changelog/12567.feature.rst @@ -0,0 +1,7 @@ +Added ``--no-fold-skipped`` command line option + +If this option is set, then skipped tests in short summary are no longer grouped +by reason but all tests are printed individually with correct nodeid in the same +way as other statuses. + +-- by :user:`pbrezina` diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst index 7a4e32edc78..4994ad1af69 100644 --- a/doc/en/how-to/output.rst +++ b/doc/en/how-to/output.rst @@ -552,6 +552,11 @@ captured output: PASSED test_example.py::test_ok == 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s === +.. note:: + + By default, parametrized variants of skipped tests are grouped together if + they share the same skip reason. You can use ``--no-fold-skipped`` to print each skipped test separately. + Creating resultlog format files -------------------------------------------------- diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index f364316e2e2..8c722124d04 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -154,6 +154,13 @@ def pytest_addoption(parser: Parser) -> None: dest="no_summary", help="Disable summary", ) + group._addoption( + "--no-fold-skipped", + action="store_false", + dest="fold_skipped", + default=True, + help="Do not fold skipped tests in short summary.", + ) group._addoption( "-q", "--quiet", @@ -371,6 +378,7 @@ def __init__(self, config: Config, file: TextIO | None = None) -> None: self._screen_width = self._tw.fullwidth self.currentfspath: None | Path | str | int = None self.reportchars = getreportopt(config) + self.foldskipped = config.option.fold_skipped self.hasmarkup = self._tw.hasmarkup self.isatty = file.isatty() self._progress_nodeids_reported: set[str] = set() @@ -1232,7 +1240,7 @@ def show_xpassed(lines: list[str]) -> None: line += " - " + str(reason) lines.append(line) - def show_skipped(lines: list[str]) -> None: + def show_skipped_folded(lines: list[str]) -> None: skipped: list[CollectReport] = self.stats.get("skipped", []) fskips = _folded_skips(self.startpath, skipped) if skipped else [] if not fskips: @@ -1252,6 +1260,31 @@ def show_skipped(lines: list[str]) -> None: else: lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason)) + def show_skipped_unfolded(lines: list[str]) -> None: + skipped: list[CollectReport] = self.stats.get("skipped", []) + + for rep in skipped: + assert rep.longrepr is not None + assert isinstance(rep.longrepr, tuple), (rep, rep.longrepr) + assert len(rep.longrepr) == 3, (rep, rep.longrepr) + + verbose_word, verbose_markup = rep._get_verbose_word_with_markup( + self.config, {_color_for_type["warnings"]: True} + ) + markup_word = self._tw.markup(verbose_word, **verbose_markup) + nodeid = _get_node_id_with_markup(self._tw, self.config, rep) + line = f"{markup_word} {nodeid}" + reason = rep.longrepr[2] + if reason: + line += " - " + str(reason) + lines.append(line) + + def show_skipped(lines: list[str]) -> None: + if self.foldskipped: + show_skipped_folded(lines) + else: + show_skipped_unfolded(lines) + REPORTCHAR_ACTIONS: Mapping[str, Callable[[list[str]], None]] = { "x": show_xfailed, "X": show_xpassed, diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 5e3f631e22b..11ad623fb6b 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1150,6 +1150,44 @@ def test(): result.stdout.fnmatch_lines([expected]) assert result.stdout.lines.count(expected) == 1 + def test_summary_s_folded(self, pytester: Pytester) -> None: + """Test that skipped tests are correctly folded""" + pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("param", [True, False]) + @pytest.mark.skip("Some reason") + def test(param): + pass + """ + ) + result = pytester.runpytest("-rs") + expected = "SKIPPED [2] test_summary_s_folded.py:3: Some reason" + result.stdout.fnmatch_lines([expected]) + assert result.stdout.lines.count(expected) == 1 + + def test_summary_s_unfolded(self, pytester: Pytester) -> None: + """Test that skipped tests are not folded if --no-fold-skipped is set""" + pytester.makepyfile( + """ + import pytest + + @pytest.mark.parametrize("param", [True, False]) + @pytest.mark.skip("Some reason") + def test(param): + pass + """ + ) + result = pytester.runpytest("-rs", "--no-fold-skipped") + expected = [ + "SKIPPED test_summary_s_unfolded.py::test[True] - Skipped: Some reason", + "SKIPPED test_summary_s_unfolded.py::test[False] - Skipped: Some reason", + ] + result.stdout.fnmatch_lines(expected) + assert result.stdout.lines.count(expected[0]) == 1 + assert result.stdout.lines.count(expected[1]) == 1 + @pytest.mark.parametrize( ("use_ci", "expected_message"), From 9de29bfe1b02e5b9924dac06ed24ecbaa7b192a5 Mon Sep 17 00:00:00 2001 From: Cornelius Riemenschneider Date: Sat, 6 Jul 2024 19:01:29 +0200 Subject: [PATCH 180/199] Cache class: Fix crash on data race on Windows. (#12579) * Cache class: Fix crash on data race on Windows. On Windows, the error gets mapped to a different errno when a race happens, see https://github.com/python/cpython/blob/cecd6012b0ed5dca3916ae341e705ae44172991d/PC/errmap.h#L107. Therefore, we were observing crashes from that code path, as the error wasn't being handled, but bubbled up. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- AUTHORS | 1 + changelog/12580.bugfix.rst | 1 + src/_pytest/cacheprovider.py | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 changelog/12580.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 7fd388505dd..8d31170560c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -96,6 +96,7 @@ Christopher Gilling Claire Cecil Claudio Madotto Clément M.T. Robert +Cornelius Riemenschneider CrazyMerlyn Cristian Vera Cyrus Maden diff --git a/changelog/12580.bugfix.rst b/changelog/12580.bugfix.rst new file mode 100644 index 00000000000..9186ef1a4c9 --- /dev/null +++ b/changelog/12580.bugfix.rst @@ -0,0 +1 @@ +Fixed a crash when using the cache class on Windows and the cache directory was created concurrently. diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 51778c456c4..20bb262e05d 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -232,7 +232,8 @@ def _ensure_cache_dir_and_supporting_files(self) -> None: # 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: + # On Windows, the error is a FileExistsError which translates to EEXIST. + if e.errno not in (errno.ENOTEMPTY, errno.EEXIST): raise else: # Create a directory in place of the one we just moved so that From aa70d2982d12c7bf47447a1dab65bbfe186962df Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sun, 7 Jul 2024 00:22:31 +0000 Subject: [PATCH 181/199] [automated] Update plugin list --- doc/en/reference/plugin_list.rst | 152 +++++++++++++++++-------------- 1 file changed, 84 insertions(+), 68 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index 38f855de03c..c0020a2bfb7 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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 Apr 22, 2024 5 - Production/Stable pytest>=6 + :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures Jul 03, 2024 5 - Production/Stable pytest>=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 @@ -112,7 +112,7 @@ This list contains 1482 plugins. :pypi:`pytest_async` pytest-async - Run your coroutine in event loop without decorator Feb 26, 2020 N/A N/A :pypi:`pytest-async-generators` Pytest fixtures for async generators Jul 05, 2023 N/A N/A :pypi:`pytest-asyncio` Pytest support for asyncio May 19, 2024 4 - Beta pytest<9,>=7.0.0 - :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Feb 25, 2024 N/A N/A + :pypi:`pytest-asyncio-cooperative` Run all your asynchronous tests cooperatively. Jul 04, 2024 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) @@ -127,7 +127,7 @@ This list contains 1482 plugins. :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-aux` templates/examples and aux for pytest Jun 27, 2024 N/A N/A + :pypi:`pytest-aux` templates/examples and aux for pytest Jul 05, 2024 N/A N/A :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 @@ -143,7 +143,7 @@ This list contains 1482 plugins. :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 Jan 31, 2024 5 - Production/Stable pytest>=7.0.0 :pypi:`pytest-batch-regression` A pytest plugin to repeat the entire test suite in batches. May 08, 2024 N/A pytest>=6.0.0 - :pypi:`pytest-bazel` A pytest runner with bazel support Jun 29, 2024 4 - Beta pytest + :pypi:`pytest-bazel` A pytest runner with bazel support Jul 05, 2024 4 - Beta pytest :pypi:`pytest-bdd` BDD for pytest Jun 04, 2024 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 Dec 31, 2023 4 - Beta pytest >=5.0 @@ -153,7 +153,7 @@ This list contains 1482 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jun 28, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jul 04, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -316,7 +316,7 @@ This list contains 1482 plugins. :pypi:`pytest-dash` pytest fixtures to run dash applications. Mar 18, 2019 N/A N/A :pypi:`pytest-dashboard` May 30, 2024 N/A pytest<8.0.0,>=7.4.3 :pypi:`pytest-data` Useful functions for managing data for pytest fixtures Nov 01, 2016 5 - Production/Stable N/A - :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Jun 11, 2024 4 - Beta pytest + :pypi:`pytest-databases` Reusable database fixtures for any and all databases. Jul 02, 2024 4 - Beta pytest :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 03, 2023 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) @@ -487,8 +487,8 @@ This list contains 1482 plugins. :pypi:`pytest-eventlet` Applies eventlet monkey-patch as a pytest plugin. Oct 04, 2021 N/A pytest ; extra == 'dev' :pypi:`pytest-evm` The testing package containing tools to test Web3-based projects Apr 22, 2024 4 - Beta pytest<9.0.0,>=8.1.1 :pypi:`pytest_exact_fixtures` Parse queries in Lucene and Elasticsearch syntaxes Feb 04, 2019 N/A N/A - :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 11, 2023 4 - Beta pytest>=7 - :pypi:`pytest-exasol-itde` Feb 15, 2024 N/A pytest (>=7,<9) + :pypi:`pytest-examples` Pytest plugin for testing examples in docstrings and markdown files. Jul 02, 2024 4 - Beta pytest>=7 + :pypi:`pytest-exasol-itde` Jul 01, 2024 N/A pytest<9,>=7 :pypi:`pytest-exasol-saas` Jun 07, 2024 N/A pytest<9,>=7 :pypi:`pytest-excel` pytest plugin for generating excel reports Jun 18, 2024 5 - Production/Stable pytest>3.6 :pypi:`pytest-exceptional` Better exceptions Mar 16, 2017 4 - Beta N/A @@ -585,18 +585,18 @@ This list contains 1482 plugins. :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 Feb 07, 2024 4 - Beta pytest >=6.0.0 + :pypi:`pytest-fzf` fzf-based test selector for pytest Jul 03, 2024 4 - Beta pytest>=6.0.0 :pypi:`pytest_gae` pytest plugin for apps written with Google's AppEngine Aug 03, 2016 3 - Alpha N/A :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-gcs` GCS fixtures and fixture factories for Pytest. Mar 01, 2024 5 - Production/Stable pytest >=6.2 - :pypi:`pytest-gee` The Python plugin for your GEE based packages. Feb 15, 2024 3 - Alpha pytest + :pypi:`pytest-gee` The Python plugin for your GEE based packages. Jun 30, 2024 3 - Alpha pytest :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. Apr 12, 2024 N/A pytest>=3.6 + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Jul 02, 2024 N/A pytest>=3.6 :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-gitconfig` Provide a gitconfig sandbox for testing Oct 15, 2023 4 - Beta pytest>=7.1.2 :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A @@ -639,12 +639,12 @@ This list contains 1482 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 29, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jul 06, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 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-houdini` pytest plugin for testing code in Houdini. Feb 09, 2024 N/A pytest + :pypi:`pytest-houdini` pytest plugin for testing code in Houdini. Jul 05, 2024 N/A pytest :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) @@ -679,7 +679,7 @@ This list contains 1482 plugins. :pypi:`pytest-ignore-flaky` ignore failures from flaky tests (pytest plugin) Apr 20, 2024 5 - Production/Stable pytest>=6.0 :pypi:`pytest-ignore-test-results` A pytest plugin to ignore test results. Aug 17, 2023 2 - Pre-Alpha pytest>=7.0 :pypi:`pytest-image-diff` Mar 09, 2023 3 - Alpha pytest - :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Dec 01, 2023 4 - Beta pytest >=3.5.0 + :pypi:`pytest-image-snapshot` A pytest plugin for image snapshot management and comparison. Jul 01, 2024 4 - Beta pytest>=3.5.0 :pypi:`pytest-incremental` an incremental test runner (pytest plugin) Apr 24, 2021 5 - Production/Stable N/A :pypi:`pytest-infinity` Jun 09, 2024 N/A pytest<9.0.0,>=8.0.0 :pypi:`pytest-influxdb` Plugin for influxdb and pytest integration. Apr 20, 2021 N/A N/A @@ -690,9 +690,9 @@ This list contains 1482 plugins. :pypi:`pytest-ini` Reuse pytest.ini to store env variables Apr 26, 2022 N/A N/A :pypi:`pytest-initry` Plugin for sending automation test data from Pytest to the initry Apr 30, 2024 N/A pytest<9.0.0,>=8.1.1 :pypi:`pytest-inline` A pytest plugin for writing inline tests. Oct 19, 2023 4 - Beta pytest >=7.0.0 - :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Dec 13, 2023 5 - Production/Stable pytest - :pypi:`pytest-inmanta-extensions` Inmanta tests package May 24, 2024 5 - Production/Stable N/A - :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules May 10, 2024 5 - Production/Stable N/A + :pypi:`pytest-inmanta` A py.test plugin providing fixtures to simplify inmanta modules testing. Jul 05, 2024 5 - Production/Stable pytest + :pypi:`pytest-inmanta-extensions` Inmanta tests package Jul 05, 2024 5 - Production/Stable N/A + :pypi:`pytest-inmanta-lsm` Common fixtures for inmanta LSM related modules Jul 06, 2024 5 - Production/Stable N/A :pypi:`pytest-inmanta-yang` Common fixtures used in inmanta yang related modules Feb 22, 2024 4 - Beta pytest :pypi:`pytest-Inomaly` A simple image diff plugin for pytest Feb 13, 2018 4 - Beta N/A :pypi:`pytest-in-robotframework` The extension enables easy execution of pytest tests within the Robot Framework environment. Mar 02, 2024 N/A pytest @@ -709,7 +709,7 @@ This list contains 1482 plugins. :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-ipywidgets` Jun 06, 2024 N/A pytest + :pypi:`pytest-ipywidgets` Jul 05, 2024 N/A pytest :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest :pypi:`pytest-isort` py.test plugin to check import ordering using isort Mar 05, 2024 5 - Production/Stable pytest (>=5.0) :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 29, 2024 4 - Beta N/A @@ -827,7 +827,7 @@ This list contains 1482 plugins. :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 Feb 12, 2024 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 13, 2024 N/A pytest + :pypi:`pytest-mh` Pytest multihost plugin Jul 02, 2024 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) @@ -881,7 +881,7 @@ This list contains 1482 plugins. :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-neos` Pytest plugin for neos Jun 11, 2024 1 - Planning N/A - :pypi:`pytest-netdut` "Automated software testing for switches using pytest" Mar 07, 2024 N/A pytest <7.3,>=3.5.0 + :pypi:`pytest-netdut` "Automated software testing for switches using pytest" Jul 05, 2024 N/A pytest<7.3,>=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) @@ -889,7 +889,7 @@ This list contains 1482 plugins. :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 18, 2024 N/A pytest<9.0.0,>=8.2.0 + :pypi:`pytest-nhsd-apim` Pytest plugin accessing NHSDigital's APIM proxies Jul 01, 2024 N/A pytest<9.0.0,>=8.2.0 :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 Apr 11, 2024 N/A N/A @@ -981,7 +981,7 @@ This list contains 1482 plugins. :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 May 06, 2024 N/A N/A + :pypi:`pytest-playwright` A pytest wrapper with fixtures for Playwright to automate web browsers Jul 03, 2024 N/A N/A :pypi:`pytest_playwright_async` ASYNC Pytest plugin for Playwright May 24, 2024 N/A N/A :pypi:`pytest-playwright-asyncio` Aug 29, 2023 N/A N/A :pypi:`pytest-playwright-enhanced` A pytest plugin for playwright python Mar 24, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -1157,7 +1157,7 @@ This list contains 1482 plugins. :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_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them Jun 07, 2024 N/A pytest<9,>=7 + :pypi:`pytest_robotframework` a pytest plugin that can run both python and robotframework tests while generating robot reports for them Jul 01, 2024 N/A pytest<9,>=7 :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) @@ -1182,7 +1182,7 @@ This list contains 1482 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jun 29, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jul 06, 2024 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-scenario-files` A pytest plugin that generates unit test scenarios from data files. May 19, 2024 5 - Production/Stable pytest>=7.2.0 :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A @@ -1192,7 +1192,7 @@ This list contains 1482 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jun 29, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jul 06, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -1270,7 +1270,7 @@ This list contains 1482 plugins. :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 Jun 26, 2024 N/A pytest<8,>5.4.0 + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jul 03, 2024 N/A pytest<8,>5.4.0 :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 14, 2024 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 @@ -1285,6 +1285,7 @@ This list contains 1482 plugins. :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 20, 2024 N/A N/A :pypi:`pytest-static` pytest-static Jun 20, 2024 1 - Planning pytest<8.0.0,>=7.4.3 + :pypi:`pytest-stats` Collects tests metadata for future analysis, easy to extend for any data store Jul 03, 2024 N/A pytest>=8.0.0 :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 @@ -1361,6 +1362,7 @@ This list contains 1482 plugins. :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-tracer-for-pytest` A plugin that allows coll test data for use on Test Tracer Jun 28, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-test-tracer-for-pytest-bdd` A plugin that allows coll test data for use on Test Tracer Jul 01, 2024 4 - Beta pytest>=6.2.0 :pypi:`pytest-test-utils` Feb 08, 2024 N/A pytest >=3.9 :pypi:`pytest-tesults` Tesults plugin for pytest Feb 15, 2024 5 - Production/Stable pytest >=3.5.0 :pypi:`pytest-textual-snapshot` Snapshot testing for Textual apps Aug 23, 2023 4 - Beta pytest (>=7.0.0) @@ -1432,7 +1434,7 @@ This list contains 1482 plugins. :pypi:`pytest-unique` Pytest fixture to generate unique values. Sep 15, 2023 N/A pytest (>=7.4.2,<8.0.0) :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 Mar 13, 2024 4 - Beta pytest >=7.0.0 + :pypi:`pytest-unordered` Test equality of unordered collections in pytest Jul 05, 2024 4 - Beta pytest>=7.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. Apr 08, 2024 4 - Beta pytest>7.3.2 :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 @@ -1494,9 +1496,9 @@ This list contains 1482 plugins. :pypi:`pytest-xskynet` A package to prevent Dependency Confusion attacks against Yandex. Feb 20, 2024 N/A N/A :pypi:`pytest-xstress` Jun 01, 2024 N/A pytest<9.0.0,>=8.0.0 :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-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Oct 01, 2023 4 - Beta pytest >=7.1.0 + :pypi:`pytest-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Jul 03, 2024 4 - Beta pytest>=7.2.2 :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 Apr 23, 2024 N/A pytest>=7.4.0 + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Jul 02, 2024 N/A pytest>=7.4.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 19, 2023 N/A pytest (>=7.2.0) @@ -1509,7 +1511,7 @@ This list contains 1482 plugins. :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-zcc` eee Jun 02, 2024 N/A N/A - :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jan 08, 2024 5 - Production/Stable pytest (>=4.5.0) + :pypi:`pytest-zebrunner` Pytest connector for Zebrunner reporting Jul 04, 2024 5 - Production/Stable pytest>=4.5.0 :pypi:`pytest-zeebe` Pytest fixtures for testing Camunda 8 processes using a Zeebe test engine. Feb 01, 2024 N/A pytest (>=7.4.2,<8.0.0) :pypi:`pytest-zest` Zesty additions to pytest. Nov 17, 2022 N/A N/A :pypi:`pytest-zhongwen-wendang` PyTest 中文文档 Mar 04, 2024 4 - Beta N/A @@ -1802,7 +1804,7 @@ This list contains 1482 plugins. pytest-annotate: Generate PyAnnotate annotations from your pytest tests. :pypi:`pytest-ansible` - *last release*: Apr 22, 2024, + *last release*: Jul 03, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=6 @@ -2068,7 +2070,7 @@ This list contains 1482 plugins. Pytest support for asyncio :pypi:`pytest-asyncio-cooperative` - *last release*: Feb 25, 2024, + *last release*: Jul 04, 2024, *status*: N/A, *requires*: N/A @@ -2173,7 +2175,7 @@ This list contains 1482 plugins. This fixture provides a configured "driver" for Android Automated Testing, using uiautomator2. :pypi:`pytest-aux` - *last release*: Jun 27, 2024, + *last release*: Jul 05, 2024, *status*: N/A, *requires*: N/A @@ -2285,7 +2287,7 @@ This list contains 1482 plugins. A pytest plugin to repeat the entire test suite in batches. :pypi:`pytest-bazel` - *last release*: Jun 29, 2024, + *last release*: Jul 05, 2024, *status*: 4 - Beta, *requires*: pytest @@ -2355,7 +2357,7 @@ This list contains 1482 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Jun 28, 2024, + *last release*: Jul 04, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -3496,7 +3498,7 @@ This list contains 1482 plugins. Useful functions for managing data for pytest fixtures :pypi:`pytest-databases` - *last release*: Jun 11, 2024, + *last release*: Jul 02, 2024, *status*: 4 - Beta, *requires*: pytest @@ -4693,16 +4695,16 @@ This list contains 1482 plugins. Parse queries in Lucene and Elasticsearch syntaxes :pypi:`pytest-examples` - *last release*: Jul 11, 2023, + *last release*: Jul 02, 2024, *status*: 4 - Beta, *requires*: pytest>=7 Pytest plugin for testing examples in docstrings and markdown files. :pypi:`pytest-exasol-itde` - *last release*: Feb 15, 2024, + *last release*: Jul 01, 2024, *status*: N/A, - *requires*: pytest (>=7,<9) + *requires*: pytest<9,>=7 @@ -5379,9 +5381,9 @@ This list contains 1482 plugins. :pypi:`pytest-fzf` - *last release*: Feb 07, 2024, + *last release*: Jul 03, 2024, *status*: 4 - Beta, - *requires*: pytest >=6.0.0 + *requires*: pytest>=6.0.0 fzf-based test selector for pytest @@ -5421,7 +5423,7 @@ This list contains 1482 plugins. GCS fixtures and fixture factories for Pytest. :pypi:`pytest-gee` - *last release*: Feb 15, 2024, + *last release*: Jun 30, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -5456,7 +5458,7 @@ This list contains 1482 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Apr 12, 2024, + *last release*: Jul 02, 2024, *status*: N/A, *requires*: pytest>=3.6 @@ -5757,7 +5759,7 @@ This list contains 1482 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Jun 29, 2024, + *last release*: Jul 06, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 @@ -5792,7 +5794,7 @@ This list contains 1482 plugins. A plugin that tracks test changes :pypi:`pytest-houdini` - *last release*: Feb 09, 2024, + *last release*: Jul 05, 2024, *status*: N/A, *requires*: pytest @@ -6037,9 +6039,9 @@ This list contains 1482 plugins. :pypi:`pytest-image-snapshot` - *last release*: Dec 01, 2023, + *last release*: Jul 01, 2024, *status*: 4 - Beta, - *requires*: pytest >=3.5.0 + *requires*: pytest>=3.5.0 A pytest plugin for image snapshot management and comparison. @@ -6114,21 +6116,21 @@ This list contains 1482 plugins. A pytest plugin for writing inline tests. :pypi:`pytest-inmanta` - *last release*: Dec 13, 2023, + *last release*: Jul 05, 2024, *status*: 5 - Production/Stable, *requires*: pytest A py.test plugin providing fixtures to simplify inmanta modules testing. :pypi:`pytest-inmanta-extensions` - *last release*: May 24, 2024, + *last release*: Jul 05, 2024, *status*: 5 - Production/Stable, *requires*: N/A Inmanta tests package :pypi:`pytest-inmanta-lsm` - *last release*: May 10, 2024, + *last release*: Jul 06, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -6247,7 +6249,7 @@ This list contains 1482 plugins. THIS PROJECT IS ABANDONED :pypi:`pytest-ipywidgets` - *last release*: Jun 06, 2024, + *last release*: Jul 05, 2024, *status*: N/A, *requires*: pytest @@ -7073,7 +7075,7 @@ This list contains 1482 plugins. Custom metrics report for pytest :pypi:`pytest-mh` - *last release*: Jun 13, 2024, + *last release*: Jul 02, 2024, *status*: N/A, *requires*: pytest @@ -7451,9 +7453,9 @@ This list contains 1482 plugins. Pytest plugin for neos :pypi:`pytest-netdut` - *last release*: Mar 07, 2024, + *last release*: Jul 05, 2024, *status*: N/A, - *requires*: pytest <7.3,>=3.5.0 + *requires*: pytest<7.3,>=3.5.0 "Automated software testing for switches using pytest" @@ -7507,7 +7509,7 @@ This list contains 1482 plugins. pytest ngs fixtures :pypi:`pytest-nhsd-apim` - *last release*: Jun 18, 2024, + *last release*: Jul 01, 2024, *status*: N/A, *requires*: pytest<9.0.0,>=8.2.0 @@ -8151,7 +8153,7 @@ This list contains 1482 plugins. Pytest plugin for reading playbooks. :pypi:`pytest-playwright` - *last release*: May 06, 2024, + *last release*: Jul 03, 2024, *status*: N/A, *requires*: N/A @@ -9383,7 +9385,7 @@ This list contains 1482 plugins. pytest plugin for ROAST configuration override and fixtures :pypi:`pytest_robotframework` - *last release*: Jun 07, 2024, + *last release*: Jul 01, 2024, *status*: N/A, *requires*: pytest<9,>=7 @@ -9558,7 +9560,7 @@ This list contains 1482 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: Jun 29, 2024, + *last release*: Jul 06, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9628,7 +9630,7 @@ This list contains 1482 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Jun 29, 2024, + *last release*: Jul 06, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -10174,7 +10176,7 @@ This list contains 1482 plugins. :pypi:`pytest-splunk-addon` - *last release*: Jun 26, 2024, + *last release*: Jul 03, 2024, *status*: N/A, *requires*: pytest<8,>5.4.0 @@ -10278,6 +10280,13 @@ This list contains 1482 plugins. pytest-static + :pypi:`pytest-stats` + *last release*: Jul 03, 2024, + *status*: N/A, + *requires*: pytest>=8.0.0 + + Collects tests metadata for future analysis, easy to extend for any data store + :pypi:`pytest-statsd` *last release*: Nov 30, 2018, *status*: 5 - Production/Stable, @@ -10810,6 +10819,13 @@ This list contains 1482 plugins. A plugin that allows coll test data for use on Test Tracer + :pypi:`pytest-test-tracer-for-pytest-bdd` + *last release*: Jul 01, 2024, + *status*: 4 - Beta, + *requires*: pytest>=6.2.0 + + A plugin that allows coll test data for use on Test Tracer + :pypi:`pytest-test-utils` *last release*: Feb 08, 2024, *status*: N/A, @@ -11308,9 +11324,9 @@ This list contains 1482 plugins. Run only unmarked tests :pypi:`pytest-unordered` - *last release*: Mar 13, 2024, + *last release*: Jul 05, 2024, *status*: 4 - Beta, - *requires*: pytest >=7.0.0 + *requires*: pytest>=7.0.0 Test equality of unordered collections in pytest @@ -11742,9 +11758,9 @@ This list contains 1482 plugins. A pytest plugin to run Xvfb (or Xephyr/Xvnc) for tests. :pypi:`pytest-xvirt` - *last release*: Oct 01, 2023, + *last release*: Jul 03, 2024, *status*: 4 - Beta, - *requires*: pytest >=7.1.0 + *requires*: pytest>=7.2.2 A pytest plugin to virtualize test. For example to transparently running them on a remote box. @@ -11756,7 +11772,7 @@ This list contains 1482 plugins. This plugin is used to load yaml output to your test using pytest framework. :pypi:`pytest-yaml-sanmu` - *last release*: Apr 23, 2024, + *last release*: Jul 02, 2024, *status*: N/A, *requires*: pytest>=7.4.0 @@ -11847,9 +11863,9 @@ This list contains 1482 plugins. eee :pypi:`pytest-zebrunner` - *last release*: Jan 08, 2024, + *last release*: Jul 04, 2024, *status*: 5 - Production/Stable, - *requires*: pytest (>=4.5.0) + *requires*: pytest>=4.5.0 Pytest connector for Zebrunner reporting From 6933bef0b0f193a32a3716a72a26e7184542376b Mon Sep 17 00:00:00 2001 From: Marc Bresson <50196352+MarcBresson@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:26:59 +0200 Subject: [PATCH 182/199] New docs about CI/BUILD_NUMBER env vars (#12578) Closes #12577 Co-authored-by: Marc Bresson Co-authored-by: Bruno Oliveira --- changelog/12577.doc.rst | 3 + doc/en/explanation/ci.rst | 70 ++++++++++++ doc/en/explanation/index.rst | 3 +- doc/en/reference/reference.rst | 203 +++++++++++++++++---------------- src/_pytest/helpconfig.py | 6 + 5 files changed, 183 insertions(+), 102 deletions(-) create mode 100644 changelog/12577.doc.rst create mode 100644 doc/en/explanation/ci.rst diff --git a/changelog/12577.doc.rst b/changelog/12577.doc.rst new file mode 100644 index 00000000000..0bd427e177d --- /dev/null +++ b/changelog/12577.doc.rst @@ -0,0 +1,3 @@ +`CI` and `BUILD_NUMBER` environment variables role is discribed in +the reference doc. They now also appears when doing `pytest -h` +-- by :user:`MarcBresson`. diff --git a/doc/en/explanation/ci.rst b/doc/en/explanation/ci.rst new file mode 100644 index 00000000000..45fe658d14f --- /dev/null +++ b/doc/en/explanation/ci.rst @@ -0,0 +1,70 @@ +.. _`ci-pipelines`: + +CI Pipelines +============ + +Rationale +--------- + +The goal of testing in a CI pipeline is different from testing locally. Indeed, +you can quickly edit some code and run your tests again on your computer, but +it is not possible with CI pipeline. They run on a separate server and are +triggered by specific actions. + +From that observation, pytest can detect when it is in a CI environment and +adapt some of its behaviours. + +How CI is detected +------------------ + +Pytest knows it is in a CI environment when either one of these environment variables are set, +regardless of their value: + +* `CI`: used by many CI systems. +* `BUILD_NUMBER`: used by Jenkins. + +Effects on CI +------------- + +For now, the effects on pytest of being in a CI environment are limited. + +When a CI environment is detected, the output of the short test summary info is no longer truncated to the terminal size i.e. the entire message will be shown. + + .. code-block:: python + + # content of test_ci.py + import pytest + + + def test_db_initialized(): + pytest.fail( + "deliberately failing for demo purpose, Lorem ipsum dolor sit amet, " + "consectetur adipiscing elit. Cras facilisis, massa in suscipit " + "dignissim, mauris lacus molestie nisi, quis varius metus nulla ut ipsum." + ) + + +Running this locally, without any extra options, will output: + + .. code-block:: pytest + + $ pytest test_ci.py + ... + ========================= short test summary info ========================== + FAILED test_backends.py::test_db_initialized[d2] - Failed: deliberately f... + +*(Note the truncated text)* + + +While running this on CI will output: + + .. code-block:: pytest + + $ export CI=true + $ pytest test_ci.py + ... + ========================= short test summary info ========================== + FAILED test_backends.py::test_db_initialized[d2] - Failed: deliberately failing + for demo purpose, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras + facilisis, massa in suscipit dignissim, mauris lacus molestie nisi, quis varius + metus nulla ut ipsum. diff --git a/doc/en/explanation/index.rst b/doc/en/explanation/index.rst index 53910f1eb7b..2edf60a5d8b 100644 --- a/doc/en/explanation/index.rst +++ b/doc/en/explanation/index.rst @@ -11,5 +11,6 @@ Explanation anatomy fixtures goodpractices - flaky pythonpath + ci + flaky diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 6926cd61bd3..d1222728e13 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1120,11 +1120,11 @@ Environment variables that can be used to change pytest's behavior. .. envvar:: CI -When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to ``BUILD_NUMBER`` variable. +When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to ``BUILD_NUMBER`` variable. See also :ref:`ci-pipelines`. .. envvar:: BUILD_NUMBER -When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable. +When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable. See also :ref:`ci-pipelines`. .. envvar:: PYTEST_ADDOPTS @@ -1939,19 +1939,18 @@ All the command-line flags can be obtained by running ``pytest --help``:: general: -k EXPRESSION Only run tests which match the given substring expression. An expression is a Python evaluable - expression where all names are substring-matched - against test names and their parent classes. - Example: -k 'test_method or test_other' matches all - test functions and classes whose name contains - 'test_method' or 'test_other', while -k 'not - test_method' matches those that don't contain - 'test_method' in their names. -k 'not test_method - and not test_other' will eliminate the matches. - Additionally keywords are matched to classes and - functions containing extra names in their - 'extra_keyword_matches' set, as well as functions - which have names assigned directly to them. The - matching is case-insensitive. + expression where all names are substring-matched against + test names and their parent classes. Example: -k + 'test_method or test_other' matches all test functions + and classes whose name contains 'test_method' or + 'test_other', while -k 'not test_method' matches those + that don't contain 'test_method' in their names. -k 'not + test_method and not test_other' will eliminate the + matches. Additionally keywords are matched to classes + and functions containing extra names in their + '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). @@ -1969,28 +1968,28 @@ All the command-line flags can be obtained by running ``pytest --help``:: --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 - --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 + --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. --nf, --new-first Run tests from new files first, then the rest of the tests sorted by file mtime --cache-show=[CACHESHOW] - Show cache contents, don't perform collection or - tests. Optional argument: glob (default: '*'). + Show cache contents, don't perform collection or tests. + Optional argument: glob (default: '*'). --cache-clear Remove all cache contents at start of test run --lfnf={all,none}, --last-failed-no-failures={all,none} - With ``--lf``, determines whether to execute tests - when there are no previously (known) failures or - when no cached ``lastfailed`` data was found. - ``all`` (the default) runs the full test suite - again. ``none`` just emits a message about no known - failures and exits successfully. - --sw, --stepwise Exit on test failure and continue from last failing - test next time + With ``--lf``, determines whether to execute tests when + there are no previously (known) failures or when no + cached ``lastfailed`` data was found. ``all`` (the + default) runs the full test suite again. ``none`` just + emits a message about no known failures and exits + successfully. + --sw, --stepwise Exit on test failure and continue from last failing test + next time --sw-skip, --stepwise-skip Ignore the first failing test but stop on the next failing test. Implicitly enables --stepwise. @@ -2007,55 +2006,53 @@ All the command-line flags can be obtained by running ``pytest --help``:: -r chars Show extra test summary info as specified by chars: (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, (p)assed, (P)assed with output, (a)ll except passed - (p/P), or (A)ll. (w)arnings are enabled by default - (see --disable-warnings), 'N' can be used to reset - the list. (default: 'fE'). + (p/P), or (A)ll. (w)arnings are enabled by default (see + --disable-warnings), 'N' can be used to reset the list. + (default: 'fE'). --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) + --no-showlocals Hide locals in tracebacks (negate --showlocals passed + through addopts) + --tb=style Traceback print mode (auto/long/short/line/native/no) + --xfail-tb Show tracebacks for xfail (as long as --tb != no) --show-capture={no,stdout,stderr,log,all} Controls how captured stdout/stderr/log is shown on failed tests. Default: all. --full-trace Don't cut any tracebacks (default is to cut) --color=color Color terminal output (yes/no/auto) --code-highlight={yes,no} - Whether code should be highlighted (only if --color - is also enabled). Default: yes. + Whether code should be highlighted (only if --color is + also enabled). Default: yes. --pastebin=mode Send failed|all info to bpaste.net pastebin service --junit-xml=path Create junit-xml style report file at given path --junit-prefix=str Prepend prefix to classnames in junit-xml output pytest-warnings: -W PYTHONWARNINGS, --pythonwarnings=PYTHONWARNINGS - Set which warnings to report, see -W option of - Python itself + Set which warnings to report, see -W option of Python + itself --maxfail=num Exit after first num failures or errors --strict-config Any warnings encountered while parsing the `pytest` section of the configuration file raise errors - --strict-markers Markers not registered in the `markers` section of - the configuration file raise errors + --strict-markers Markers not registered in the `markers` section of the + configuration file raise errors --strict (Deprecated) alias to --strict-markers -c FILE, --config-file=FILE Load configuration from `FILE` instead of trying to locate one of the implicit configuration files. --continue-on-collection-errors 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'. + --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: --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) - --ignore-glob=path Ignore path pattern during collection (multi- - allowed) + --ignore-glob=path Ignore path pattern during collection (multi-allowed) --deselect=nodeid_prefix Deselect item (via node id prefix) during collection (multi-allowed) @@ -2065,8 +2062,8 @@ All the command-line flags can be obtained by running ``pytest --help``:: --collect-in-virtualenv Don't ignore tests in a local virtualenv directory --import-mode={prepend,append,importlib} - Prepend/append to sys.path when importing test - modules and conftest files. Default: prepend. + Prepend/append to sys.path when importing test modules + and conftest files. Default: prepend. --doctest-modules Run doctests in all .py modules --doctest-report={none,cdiff,ndiff,udiff,only_first_failure} Choose another output format for diffs on doctest @@ -2079,38 +2076,37 @@ All the command-line flags can be obtained by running ``pytest --help``:: failure 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. + --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`. + Override ini option with "option=value" style, e.g. `-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 - in test modules on import to provide assert - expression information. + 'rewrite' (the default) rewrites assert statements in + test modules on import to provide assert expression + information. --setup-only Only setup fixtures, do not execute tests --setup-show Show setup of fixtures while executing tests - --setup-plan Show what fixtures and tests would be executed but - don't execute anything + --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. + --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 @@ -2134,8 +2130,13 @@ All the command-line flags can be obtained by running ``pytest --help``:: Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer. --log-disable=LOGGER_DISABLE - Disable a logger by name. Can be passed multiple - times. + Disable a logger by name. Can be passed multiple times. + + Custom options: + --lsof Run FD checks if lsof is available + --runpytest={inprocess,subprocess} + Run pytest sub runs in tests using an 'inprocess' or + 'subprocess' (python -m main) method [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found: @@ -2150,37 +2151,33 @@ All the command-line flags can be obtained by running ``pytest --help``:: warnings.filterwarnings. Processed after -W/--pythonwarnings. consider_namespace_packages (bool): - Consider namespace packages when resolving module - names during import - usefixtures (args): List of default fixtures to be used with this - project + Consider namespace packages when resolving module names + during import + usefixtures (args): List of default fixtures to be used with this project python_files (args): Glob-style file patterns for Python test module discovery python_classes (args): - Prefixes or glob names for Python test class - discovery + Prefixes or glob names for Python test class discovery python_functions (args): Prefixes or glob names for Python test function and method discovery disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool): - Disable string escape non-ASCII characters, might - cause unwanted side effects(use at your own risk) + Disable string escape non-ASCII characters, might cause + unwanted side effects(use at your own risk) console_output_style (string): - Console output: "classic", or with additional - progress information ("progress" (percentage) | - "count" | "progress-even-when-capture-no" (forces - progress even when capture=no) + Console output: "classic", or with additional progress + information ("progress" (percentage) | "count" | + "progress-even-when-capture-no" (forces progress even + when capture=no) verbosity_test_cases (string): Specify a verbosity level for test case execution, - overriding the main level. Higher levels will - provide more detailed information about each test - case executed. - xfail_strict (bool): Default for the strict parameter of xfail markers - when not given explicitly (default: False) + overriding the main level. Higher levels will provide + more detailed information about each test case executed. + xfail_strict (bool): Default for the strict parameter of xfail markers when + not given explicitly (default: False) tmp_path_retention_count (string): How many sessions should we keep the `tmp_path` - directories, according to - `tmp_path_retention_policy`. + 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. @@ -2189,9 +2186,9 @@ All the command-line flags can be obtained by running ``pytest --help``:: Enables the pytest_assertion_pass hook. Make sure to delete any previously generated pyc cache files. verbosity_assertions (string): - Specify a verbosity level for assertions, overriding - the main level. Higher levels will provide more - detailed explanation when an assertion fails. + Specify a verbosity level for assertions, overriding the + main level. Higher levels will provide more detailed + explanation when an assertion fails. junit_suite_name (string): Test suite name for JUnit report junit_logging (string): @@ -2213,8 +2210,8 @@ All the command-line flags can be obtained by running ``pytest --help``:: log_format (string): Default value for --log-format log_date_format (string): Default value for --log-date-format - log_cli (bool): Enable log display during test run (also known as - "live logging") + log_cli (bool): Enable log display during test run (also known as "live + logging") log_cli_level (string): Default value for --log-cli-level log_cli_format (string): @@ -2234,14 +2231,18 @@ All the command-line flags can be obtained by running ``pytest --help``:: Default value for --log-auto-indent pythonpath (paths): Add paths to sys.path faulthandler_timeout (string): - Dump the traceback of all threads if a test takes - more than TIMEOUT seconds to finish + Dump the traceback of all threads if a test takes more + than TIMEOUT seconds to finish addopts (args): Extra command line options minversion (string): Minimally required pytest version required_plugins (args): Plugins that must be present for pytest to run + pytester_example_dir (string): + Directory to take the pytester example files from Environment variables: + CI When set (regardless of value), pytest knows it is running in a CI process and does not truncate summary info + BUILD_NUMBER equivalent to CI PYTEST_ADDOPTS Extra command line options PYTEST_PLUGINS Comma-separated plugins to load during startup PYTEST_DISABLE_PLUGIN_AUTOLOAD Set to disable plugin auto-loading diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index f23b839943c..1433607a19b 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -212,6 +212,12 @@ def showhelp(config: Config) -> None: tw.line() tw.line("Environment variables:") vars = [ + ( + "CI", + "When set (regardless of value), pytest knows it is running in a " + "CI process and does not truncate summary info", + ), + ("BUILD_NUMBER", "Equivalent to CI"), ("PYTEST_ADDOPTS", "Extra command line options"), ("PYTEST_PLUGINS", "Comma-separated plugins to load during startup"), ("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "Set to disable plugin auto-loading"), From 99f0662def864400ae5698a1f874cbb0724c2624 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 22:49:38 +0000 Subject: [PATCH 183/199] [pre-commit.ci] pre-commit autoupdate (#12588) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.0 → v0.5.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.0...v0.5.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ebcc797cc1e..ce365ddbef2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.0" + rev: "v0.5.1" hooks: - id: ruff args: ["--fix"] From 73e53ad6f4d87d4dcb68f3167b8dec6b194346cf Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Tue, 9 Jul 2024 14:27:17 +0200 Subject: [PATCH 184/199] Remove done Europython training --- doc/en/index.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 8de3b3993dd..378ed8face5 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -4,7 +4,6 @@ .. sidebar:: **Next Open Trainings and Events** - - `pytest tips and tricks for a better testsuite `_, at `Europython 2024 `_, **July 9th 2024** (3h), Prague (CZ) - `pytest: Professionelles Testen (nicht nur) für Python `_, at `CH Open Workshoptage `_, **September 2nd 2024**, HSLU Rotkreuz (CH) - `Professional Testing with Python `_, via `Python Academy `_ (3 day in-depth training), **March 4th -- 6th 2025**, Leipzig (DE) / Remote From 05979bc4a97491c8b75f8baf95ec16f5b5516353 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 7 Jul 2024 12:53:55 +0100 Subject: [PATCH 185/199] Modernise Sphinx configuration This change brings the configuration closer with what the latest sphinx-quickstart generates. It also makes it easier to read through the documentation configuration file as well. - Remove unset configuration options - Drop configuration options that are set to their default values - Remove placeholder comments, including inline documentation of options - Group configuration options into sections with links to detailed docs - Move all custom logic toward the end of the file --- doc/en/conf.py | 373 +++++++++++++------------------------------------ 1 file changed, 99 insertions(+), 274 deletions(-) diff --git a/doc/en/conf.py b/doc/en/conf.py index 0d440ec448a..9f5f3787c1e 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -1,20 +1,3 @@ -# -# pytest documentation build configuration file, created by -# sphinx-quickstart on Fri Oct 8 17:54:28 2010. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The full version, including alpha/beta/rc tags. -# The short X.Y version. from __future__ import annotations import os @@ -23,61 +6,26 @@ from textwrap import dedent from typing import TYPE_CHECKING -from _pytest import __version__ as full_version +from pytest import __version__ as full_version -version = full_version.split("+")[0] - if TYPE_CHECKING: import sphinx.application - PROJECT_ROOT_DIR = Path(__file__).parents[2].resolve() -IS_RELEASE_ON_RTD = ( - os.getenv("READTHEDOCS", "False") == "True" - and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag" -) -if IS_RELEASE_ON_RTD: - tags: set[str] - # pylint: disable-next=used-before-assignment - tags.add("is_release") # noqa: F821 - -release = ".".join(version.split(".")[:2]) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# sys.path.insert(0, os.path.abspath('.')) - -autodoc_member_order = "bysource" -autodoc_typehints = "description" -autodoc_typehints_description_target = "documented" -todo_include_todos = 1 - -latex_engine = "lualatex" - -latex_elements = { - "preamble": dedent( - r""" - \directlua{ - luaotfload.add_fallback("fallbacks", { - "Noto Serif CJK SC:style=Regular;", - "Symbola:Style=Regular;" - }) - } - \setmainfont{FreeSerif}[RawFeature={fallback=fallbacks}] - """ - ) -} +# -- Project information --------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information -# -- General configuration ----------------------------------------------------- +project = "pytest" +copyright = "2015, holger krekel and pytest-dev team" +version = full_version.split("+")[0] +release = ".".join(version.split(".")[:2]) -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' +# -- General configuration ------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +root_doc = "contents" extensions = [ "pygments_pytest", "sphinx.ext.autodoc", @@ -98,35 +46,6 @@ if shutil.which("inkscape"): extensions.append("sphinxcontrib.inkscapeconverter") -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] - -# The suffix of source filenames. -source_suffix = ".rst" - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = "contents" - -# General information about the project. -project = "pytest" -copyright = "2015, holger krekel and pytest-dev team" - - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. exclude_patterns = [ "_build", "naming20.rst", @@ -138,38 +57,9 @@ "setup.rst", "example/remoteinterp.rst", ] - - -# The reST default role (used for this markup: `text`) to use for all documents. +templates_path = ["_templates"] default_role = "literal" -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -add_module_names = False - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - -# A list of regular expressions that match URIs that should not be checked when -# doing a linkcheck. -linkcheck_ignore = [ - "https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/", - "http://pythontesting.net/framework/pytest-introduction/", - r"https://github.com/pytest-dev/pytest/issues/\d+", - r"https://github.com/pytest-dev/pytest/pull/\d+", -] - -# The number of worker threads to use when checking links (default=5). -linkcheck_workers = 5 - - nitpicky = True nitpick_ignore = [ # TODO (fix in pluggy?) @@ -217,53 +107,54 @@ ("py:class", "_ScopeName"), ] +add_module_names = False -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = "furo" +# -- Options for Autodoc -------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html#configuration -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {"index_logo": None} +autodoc_member_order = "bysource" +autodoc_typehints = "description" +autodoc_typehints_description_target = "documented" -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] +# -- Options for intersphinx ---------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = "pytest documentation" +intersphinx_mapping = { + "pluggy": ("https://pluggy.readthedocs.io/en/stable", None), + "python": ("https://docs.python.org/3", None), + "numpy": ("https://numpy.org/doc/stable", None), + "pip": ("https://pip.pypa.io/en/stable", None), + "tox": ("https://tox.wiki/en/stable", None), + "virtualenv": ("https://virtualenv.pypa.io/en/stable", None), + "setuptools": ("https://setuptools.pypa.io/en/stable", None), + "packaging": ("https://packaging.python.org/en/latest", None), +} -# A shorter title for the navigation bar. Default is the same as html_title. -html_short_title = f"pytest-{release}" +# -- Options for todo ----------------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/extensions/todo.html#configuration -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -html_logo = "img/pytest_logo_curves.svg" +todo_include_todos = True -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -html_favicon = "img/favicon.png" +# -- Options for linkcheck builder ---------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-the-linkcheck-builder -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -# html_static_path = ['_static'] +linkcheck_ignore = [ + "https://blogs.msdn.microsoft.com/bharry/2017/06/28/testing-in-a-cloud-delivery-cadence/", + "http://pythontesting.net/framework/pytest-introduction/", + r"https://github.com/pytest-dev/pytest/issues/\d+", + r"https://github.com/pytest-dev/pytest/pull/\d+", +] +linkcheck_workers = 5 -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' +# -- Options for HTML output ---------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True +html_theme = "furo" +html_title = "pytest documentation" +html_short_title = f"pytest-{release}" -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} -# html_sidebars = {'index': 'indexsidebar.html'} +html_logo = "img/pytest_logo_curves.svg" +html_favicon = "img/favicon.png" html_sidebars = { "index": [ @@ -288,55 +179,35 @@ ], } -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} -# html_additional_pages = {'index': 'index.html'} - - -# If false, no module index is generated. -html_domain_indices = True - -# If false, no index is generated. html_use_index = False - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. html_show_sourcelink = False -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True +html_baseurl = "https://docs.pytest.org/en/stable/" -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True +# -- Options for HTML Help output ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-help-output -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' +htmlhelp_basename = "pytestdoc" -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None -# Output file base name for HTML help builder. -htmlhelp_basename = "pytestdoc" +# -- Options for manual page output --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-manual-page-output -# 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/" +man_pages = [ + ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1) +] -# -- Options for LaTeX output -------------------------------------------------- +# -- Options for epub output ---------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-epub-output -# The paper size ('letter' or 'a4'). -# latex_paper_size = 'letter' +epub_title = "pytest" +epub_author = "holger krekel at merlinux eu" +epub_publisher = "holger krekel at merlinux eu" +epub_copyright = "2013, holger krekel et alii" -# The font size ('10pt', '11pt' or '12pt'). -# latex_font_size = '10pt' +# -- Options for LaTeX output -------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-latex-output -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ( "contents", @@ -346,80 +217,29 @@ "manual", ) ] - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -# latex_use_parts = False - -# If true, show page references after internal links. -# latex_show_pagerefs = False - -# If true, show URL addresses after external links. -# latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -# latex_preamble = '' - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. latex_domain_indices = False +latex_engine = "lualatex" +latex_elements = { + "preamble": dedent( + r""" + \directlua{ + luaotfload.add_fallback("fallbacks", { + "Noto Serif CJK SC:style=Regular;", + "Symbola:Style=Regular;" + }) + } -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ("how-to/usage", "pytest", "pytest usage", ["holger krekel at merlinux eu"], 1) -] - - -# -- Options for Epub output --------------------------------------------------- - -# Bibliographic Dublin Core info. -epub_title = "pytest" -epub_author = "holger krekel at merlinux eu" -epub_publisher = "holger krekel at merlinux eu" -epub_copyright = "2013, holger krekel et alii" - -# The language of the text. It defaults to the language option -# or en if the language is not set. -# epub_language = '' - -# The scheme of the identifier. Typical schemes are ISBN or URL. -# epub_scheme = '' - -# The unique identifier of the text. This can be an ISBN number -# or the project homepage. -# epub_identifier = '' - -# A unique identification for the text. -# epub_uid = '' - -# HTML files that should be inserted before the pages created by sphinx. -# The format is a list of tuples containing the path and title. -# epub_pre_files = [] - -# HTML files that should be inserted after the pages created by sphinx. -# The format is a list of tuples containing the path and title. -# epub_post_files = [] - -# A list of files that should not be packed into the epub file. -# epub_exclude_files = [] - -# The depth of the table of contents in toc.ncx. -# epub_tocdepth = 3 - -# Allow duplicate toc entries. -# epub_tocdup = True - + \setmainfont{FreeSerif}[RawFeature={fallback=fallbacks}] + """ + ) +} -# -- Options for texinfo output ------------------------------------------------ +# -- Options for texinfo output ------------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-texinfo-output texinfo_documents = [ ( - master_doc, + root_doc, "pytest", "pytest Documentation", ( @@ -433,29 +253,34 @@ ) ] -# -- Options for towncrier_draft extension ----------------------------------- +# -- Options for towncrier_draft extension -------------------------------------------- +# https://sphinxcontrib-towncrier.readthedocs.io/en/latest/#how-to-use-this towncrier_draft_autoversion_mode = "draft" # or: 'sphinx-version', 'sphinx-release' towncrier_draft_include_empty = True towncrier_draft_working_directory = PROJECT_ROOT_DIR towncrier_draft_config_path = "pyproject.toml" # relative to cwd - # -- Options for sphinx_issues extension ----------------------------------- +# https://github.com/sloria/sphinx-issues#installation-and-configuration issues_github_path = "pytest-dev/pytest" +# -- Custom Read the Docs build configuration ----------------------------------------- +# https://docs.readthedocs.io/en/stable/reference/environment-variables.html#environment-variable-reference +# https://www.sphinx-doc.org/en/master/usage/restructuredtext/directives.html#including-content-based-on-tags -intersphinx_mapping = { - "pluggy": ("https://pluggy.readthedocs.io/en/stable", None), - "python": ("https://docs.python.org/3", None), - "numpy": ("https://numpy.org/doc/stable", None), - "pip": ("https://pip.pypa.io/en/stable", None), - "tox": ("https://tox.wiki/en/stable", None), - "virtualenv": ("https://virtualenv.pypa.io/en/stable", None), - "setuptools": ("https://setuptools.pypa.io/en/stable", None), - "packaging": ("https://packaging.python.org/en/latest", None), -} +IS_RELEASE_ON_RTD = ( + os.getenv("READTHEDOCS", "False") == "True" + and os.environ["READTHEDOCS_VERSION_TYPE"] == "tag" +) +if IS_RELEASE_ON_RTD: + tags: set[str] + # pylint: disable-next=used-before-assignment + tags.add("is_release") # noqa: F821 + +# -- Custom documentation plugin ------------------------------------------------------ +# https://www.sphinx-doc.org/en/master/development/tutorials/extending_syntax.html#the-setup-function def setup(app: sphinx.application.Sphinx) -> None: From 9e265eee461866195a3835263dc3b350f59b097e Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 7 Jul 2024 13:13:28 +0100 Subject: [PATCH 186/199] Remove `layout.html` This file is not used with Furo. --- doc/en/_templates/layout.html | 52 ----------------------------------- 1 file changed, 52 deletions(-) delete mode 100644 doc/en/_templates/layout.html diff --git a/doc/en/_templates/layout.html b/doc/en/_templates/layout.html deleted file mode 100644 index f7096eaaa5e..00000000000 --- a/doc/en/_templates/layout.html +++ /dev/null @@ -1,52 +0,0 @@ -{# - - Copied from: - - https://raw.githubusercontent.com/pallets/pallets-sphinx-themes/b0c6c41849b4e15cbf62cc1d95c05ef2b3e155c8/src/pallets_sphinx_themes/themes/pocoo/layout.html - - And removed the warning version (see #7331). - -#} - -{% extends "basic/layout.html" %} - -{% set metatags %} - {{- metatags }} - -{%- endset %} - -{% block extrahead %} - {%- if page_canonical_url %} - - {%- endif %} - - {{ super() }} -{%- endblock %} - -{% block sidebarlogo %} - {% if pagename != "index" or theme_index_sidebar_logo %} - {{ super() }} - {% endif %} -{% endblock %} - -{% block relbar2 %}{% endblock %} - -{% block sidebar2 %} - - {{- super() }} -{%- endblock %} - -{% block footer %} - {{ super() }} - {%- if READTHEDOCS and not readthedocs_docsearch %} - - {%- endif %} - {{ js_tag("_static/version_warning_offset.js") }} -{% endblock %} From cc9bf0097df8d22441bea7cf03e38be5198988e2 Mon Sep 17 00:00:00 2001 From: Pradyun Gedam Date: Sun, 7 Jul 2024 13:23:25 +0100 Subject: [PATCH 187/199] Improve how the documentation sidebar is managed Move away from injecting unstyled HTML into the sidebar, to using Furo's default sidebar with Sphinx's `doctree` instead. This also includes moving to a more typical Sphinx documentation structure with the `index` page serving as the "root" of the `doctree` for Sphinx. Additionally, move custom stylesheets into a `pytest-custom.css` file and use standard Sphinx tooling to inject these styles. --- doc/en/_static/pytest-custom.css | 21 +++++++++++++++++ doc/en/_templates/globaltoc.html | 31 ------------------------- doc/en/_templates/relations.html | 19 ---------------- doc/en/_templates/sidebar/brand.html | 7 ------ doc/en/_templates/sidebarintro.html | 5 ---- doc/en/_templates/style.html | 7 ------ doc/en/conf.py | 34 ++++++++-------------------- doc/en/contents.rst | 2 ++ doc/en/index.rst | 32 ++++++++++++++++++++++++-- 9 files changed, 62 insertions(+), 96 deletions(-) create mode 100644 doc/en/_static/pytest-custom.css delete mode 100644 doc/en/_templates/globaltoc.html delete mode 100644 doc/en/_templates/relations.html delete mode 100644 doc/en/_templates/sidebar/brand.html delete mode 100644 doc/en/_templates/sidebarintro.html delete mode 100644 doc/en/_templates/style.html diff --git a/doc/en/_static/pytest-custom.css b/doc/en/_static/pytest-custom.css new file mode 100644 index 00000000000..bc9eef457f1 --- /dev/null +++ b/doc/en/_static/pytest-custom.css @@ -0,0 +1,21 @@ +/* Tweak how the sidebar logo is presented */ +.sidebar-logo { + width: 70%; +} +.sidebar-brand { + padding: 0; +} + +/* The landing pages' sidebar-in-content highlights */ +#features ul { + padding-left: 1rem; + list-style: none; +} +#features ul li { + margin-bottom: 0; +} +@media (min-width: 46em) { + #features { + width: 50%; + } +} diff --git a/doc/en/_templates/globaltoc.html b/doc/en/_templates/globaltoc.html deleted file mode 100644 index 09d970b64ed..00000000000 --- a/doc/en/_templates/globaltoc.html +++ /dev/null @@ -1,31 +0,0 @@ -

Contents

- - - -

About the project

- - - -{%- if display_toc %} -
- {{ toc }} -{%- endif %} - -
diff --git a/doc/en/_templates/relations.html b/doc/en/_templates/relations.html deleted file mode 100644 index 3bbcde85bb4..00000000000 --- a/doc/en/_templates/relations.html +++ /dev/null @@ -1,19 +0,0 @@ -

Related Topics

- diff --git a/doc/en/_templates/sidebar/brand.html b/doc/en/_templates/sidebar/brand.html deleted file mode 100644 index f997c4cca5f..00000000000 --- a/doc/en/_templates/sidebar/brand.html +++ /dev/null @@ -1,7 +0,0 @@ - - - diff --git a/doc/en/_templates/sidebarintro.html b/doc/en/_templates/sidebarintro.html deleted file mode 100644 index ae860c172f0..00000000000 --- a/doc/en/_templates/sidebarintro.html +++ /dev/null @@ -1,5 +0,0 @@ -

About pytest

-

- pytest is a mature full-featured Python testing tool that helps - you write better programs. -

diff --git a/doc/en/_templates/style.html b/doc/en/_templates/style.html deleted file mode 100644 index 400cb75ff97..00000000000 --- a/doc/en/_templates/style.html +++ /dev/null @@ -1,7 +0,0 @@ - diff --git a/doc/en/conf.py b/doc/en/conf.py index 9f5f3787c1e..9558a75f927 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -25,7 +25,7 @@ # -- General configuration ------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration -root_doc = "contents" +root_doc = "index" extensions = [ "pygments_pytest", "sphinx.ext.autodoc", @@ -150,35 +150,19 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output html_theme = "furo" +html_theme_options = {"sidebar_hide_name": True} + +html_static_path = ["_static"] +html_css_files = [ + "pytest-custom.css", +] + html_title = "pytest documentation" html_short_title = f"pytest-{release}" -html_logo = "img/pytest_logo_curves.svg" +html_logo = "_static/pytest1.png" html_favicon = "img/favicon.png" -html_sidebars = { - "index": [ - "sidebar/brand.html", - "sidebar/search.html", - "sidebar/scroll-start.html", - "sidebarintro.html", - "globaltoc.html", - "links.html", - "sidebar/scroll-end.html", - "style.html", - ], - "**": [ - "sidebar/brand.html", - "sidebar/search.html", - "sidebar/scroll-start.html", - "globaltoc.html", - "relations.html", - "links.html", - "sidebar/scroll-end.html", - "style.html", - ], -} - html_use_index = False html_show_sourcelink = False diff --git a/doc/en/contents.rst b/doc/en/contents.rst index 181207203b2..07c0b3ff6b9 100644 --- a/doc/en/contents.rst +++ b/doc/en/contents.rst @@ -1,3 +1,5 @@ +:orphan: + .. _toc: Full pytest documentation diff --git a/doc/en/index.rst b/doc/en/index.rst index 8de3b3993dd..a907d1ff549 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,5 +1,3 @@ -:orphan: - .. _features: .. sidebar:: **Next Open Trainings and Events** @@ -13,6 +11,36 @@ pytest: helps you write better programs ======================================= +.. toctree:: + :hidden: + + getting-started + how-to/index + reference/index + explanation/index + example/index + +.. toctree:: + :caption: About the project + :hidden: + + changelog + contributing + backwards-compatibility + sponsor + tidelift + license + contact + +.. toctree:: + :caption: Useful links + :hidden: + + pytest @ PyPI + pytest @ GitHub + Issue Tracker + PDF Documentation + .. module:: pytest The ``pytest`` framework makes it easy to write small, readable tests, and can From ba2d750d35bd21f02aaf7526083bfd3d1e0e4435 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 12 Jul 2024 09:45:28 -0300 Subject: [PATCH 188/199] build(deps): Bump django in /testing/plugins_integration (#12598) Bumps [django](https://github.com/django/django) from 5.0.6 to 5.0.7. - [Commits](https://github.com/django/django/compare/5.0.6...5.0.7) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index b3ced6e5c9d..12aa89f7644 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -1,5 +1,5 @@ anyio[curio,trio]==4.4.0 -django==5.0.6 +django==5.0.7 pytest-asyncio==0.23.7 pytest-bdd==7.2.0 pytest-cov==5.0.0 From 423f71963c9a4b6a06d8322407d6cef05184c9a2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Fri, 12 Jul 2024 19:50:44 +0200 Subject: [PATCH 189/199] Update contact channels (#12515) Follow-up to #12427 --- doc/en/contact.rst | 62 +++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/doc/en/contact.rst b/doc/en/contact.rst index 44957a0d4a8..ef9d1e8edca 100644 --- a/doc/en/contact.rst +++ b/doc/en/contact.rst @@ -3,45 +3,51 @@ .. _`contact`: Contact channels -=================================== +================ -- `pytest issue tracker`_ to report bugs or suggest features (for version - 2.0 and above). -- `pytest discussions`_ at github for general questions. -- `pytest discord server `_ - for pytest development visibility and general assistance. +Web +--- + +- `pytest issue tracker`_ to report bugs or suggest features. +- `pytest discussions`_ at GitHub for general questions. - `pytest on stackoverflow.com `_ - to post precise questions with the tag ``pytest``. New Questions will usually + to post precise questions with the tag ``pytest``. New questions will usually be seen by pytest users or developers and answered quickly. -- `Testing In Python`_: a mailing list for Python testing tools and discussion. - -- `pytest-dev at python.org (mailing list)`_ pytest specific announcements and discussions. - -- :doc:`contribution guide ` for help on submitting pull - requests to GitHub. +Chat +---- +- `pytest discord server `_ + for pytest development visibility and general assistance. - ``#pytest`` `on irc.libera.chat `_ IRC channel for random questions (using an IRC client, or `via webchat - `) -- ``#pytest`` `on Matrix https://matrix.to/#/#pytest:matrix.org>`. + `_) +- ``#pytest`` `on Matrix `_. +Mail +---- -.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues -.. _`old issue tracker`: https://bitbucket.org/hpk42/py-trunk/issues/ - -.. _`pytest discussions`: https://github.com/pytest-dev/pytest/discussions - -.. _`get an account`: +- `Testing In Python`_: a mailing list for Python testing tools and discussion. +- `pytest-dev at python.org`_ a mailing list for pytest specific announcements and discussions. +- Mail to `core@pytest.org `_ for topics that cannot be + discussed in public. Mails sent there will be distributed among the members + in the pytest core team, who can also be contacted individually: -.. _tetamap: https://tetamap.wordpress.com/ + * Ronny Pfannschmidt (:user:`RonnyPfannschmidt`, `ronny@pytest.org `_) + * Florian Bruhin (:user:`The-Compiler`, `florian@pytest.org `_) + * Bruno Oliveira (:user:`nicoddemus`, `bruno@pytest.org `_) + * Ran Benita (:user:`bluetech`, `ran@pytest.org `_) + * Zac Hatfield-Dodds (:user:`Zac-HD`, `zac@pytest.org `_) -.. _`@pylibcommit`: https://twitter.com/pylibcommit +Other +----- +- The :doc:`contribution guide ` for help on submitting pull + requests to GitHub. +- Florian Bruhin (:user:`The-Compiler`) offers pytest professional teaching and + consulting via `Bruhin Software `_. +.. _`pytest issue tracker`: https://github.com/pytest-dev/pytest/issues +.. _`pytest discussions`: https://github.com/pytest-dev/pytest/discussions .. _`Testing in Python`: http://lists.idyll.org/listinfo/testing-in-python -.. _FOAF: https://en.wikipedia.org/wiki/FOAF -.. _`py-dev`: -.. _`development mailing list`: -.. _`pytest-dev at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-dev -.. _`pytest-commit at python.org (mailing list)`: http://mail.python.org/mailman/listinfo/pytest-commit +.. _`pytest-dev at python.org`: http://mail.python.org/mailman/listinfo/pytest-dev From c5f15e01206361c5ef38fd3c5e8216fcb6b27ff1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 13:07:47 +0000 Subject: [PATCH 190/199] [automated] Update plugin list (#12607) Co-authored-by: pytest bot --- doc/en/reference/plugin_list.rst | 106 +++++++++++++++++++------------ 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst index c0020a2bfb7..7526b055943 100644 --- a/doc/en/reference/plugin_list.rst +++ b/doc/en/reference/plugin_list.rst @@ -27,7 +27,7 @@ please refer to `the update script =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 Jul 03, 2024 5 - Production/Stable pytest>=6 + :pypi:`pytest-ansible` Plugin for pytest to simplify calling ansible modules from tests or fixtures Jul 10, 2024 5 - Production/Stable pytest>=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 @@ -143,7 +143,7 @@ This list contains 1484 plugins. :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 Jan 31, 2024 5 - Production/Stable pytest>=7.0.0 :pypi:`pytest-batch-regression` A pytest plugin to repeat the entire test suite in batches. May 08, 2024 N/A pytest>=6.0.0 - :pypi:`pytest-bazel` A pytest runner with bazel support Jul 05, 2024 4 - Beta pytest + :pypi:`pytest-bazel` A pytest runner with bazel support Jul 12, 2024 4 - Beta pytest :pypi:`pytest-bdd` BDD for pytest Jun 04, 2024 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 Dec 31, 2023 4 - Beta pytest >=5.0 @@ -153,7 +153,7 @@ This list contains 1484 plugins. :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-beartype` Pytest plugin to run your tests with beartype checking enabled. Jan 25, 2024 N/A pytest - :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jul 04, 2024 3 - Alpha pytest + :pypi:`pytest-bec-e2e` BEC pytest plugin for end-to-end tests Jul 08, 2024 3 - Alpha 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 Jul 04, 2023 4 - Beta N/A :pypi:`pytest-bench` Benchmark utility that plugs into pytest. Jul 21, 2014 3 - Alpha N/A @@ -307,7 +307,7 @@ This list contains 1484 plugins. :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-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. Jun 28, 2024 4 - Beta pytest>=6.2.0 + :pypi:`pytest-custom-outputs` A plugin that allows users to create and use custom outputs instead of the standard Pass and Fail. Also allows users to retrieve test results in fixtures. Jul 10, 2024 4 - Beta pytest>=6.2.0 :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 Apr 05, 2024 5 - Production/Stable pytest>=8 @@ -364,6 +364,7 @@ This list contains 1484 plugins. :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-dir-equal` pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing Dec 11, 2023 4 - Beta pytest>=7.3.2 + :pypi:`pytest-dirty` Static import analysis for thrifty testing. Jul 11, 2024 3 - Alpha pytest>=8.2; extra == "dev" :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. May 11, 2024 4 - Beta pytest!=6.0.0,<9,>=3.3.2 @@ -420,6 +421,7 @@ This list contains 1484 plugins. :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-doctest-mkdocstrings` Run pytest --doctest-modules with markdown docstrings in code blocks (\`\`\`) Mar 02, 2024 N/A pytest :pypi:`pytest-doctestplus` Pytest plugin with advanced doctest features. Mar 10, 2024 5 - Production/Stable pytest >=4.6 + :pypi:`pytest-documentary` A simple pytest plugin to generate test documentation Jul 11, 2024 N/A pytest :pypi:`pytest-dogu-report` pytest plugin for dogu report Jul 07, 2023 N/A N/A :pypi:`pytest-dogu-sdk` pytest plugin for the Dogu Dec 14, 2023 N/A N/A :pypi:`pytest-dolphin` Some extra stuff that we use ininternally Nov 30, 2016 4 - Beta pytest (==3.0.4) @@ -577,6 +579,7 @@ This list contains 1484 plugins. :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 Oct 29, 2023 4 - Beta pytest>=7.0.0 + :pypi:`pytest-freezeblaster` Wrap tests with fixtures in freeze_time Jul 10, 2024 N/A pytest>=6.2.5 :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 21, 2023 N/A pytest >= 3.6 :pypi:`pytest-freeze-reqs` Check if requirement files are frozen Apr 29, 2021 N/A N/A @@ -596,7 +599,7 @@ This list contains 1484 plugins. :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. Jul 02, 2024 N/A pytest>=3.6 + :pypi:`pytest-girder` A set of pytest fixtures for testing Girder applications. Jul 08, 2024 N/A pytest>=3.6 :pypi:`pytest-git` Git repository fixture for py.test May 28, 2019 5 - Production/Stable pytest :pypi:`pytest-gitconfig` Provide a gitconfig sandbox for testing Oct 15, 2023 4 - Beta pytest>=7.1.2 :pypi:`pytest-gitcov` Pytest plugin for reporting on coverage of the last git commit. Jan 11, 2020 2 - Pre-Alpha N/A @@ -639,7 +642,7 @@ This list contains 1484 plugins. :pypi:`pytest-history` Pytest plugin to keep a history of your pytest runs Jan 14, 2024 N/A pytest (>=7.4.3,<8.0.0) :pypi:`pytest-home` Home directory fixtures Oct 09, 2023 5 - Production/Stable 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 Jul 06, 2024 3 - Alpha pytest==8.2.0 + :pypi:`pytest-homeassistant-custom-component` Experimental package to automatically extract test plugins for Home Assistant custom components Jul 11, 2024 3 - Alpha pytest==8.2.0 :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` Apr 18, 2024 N/A N/A @@ -651,7 +654,7 @@ This list contains 1484 plugins. :pypi:`pytest-html` pytest plugin for generating HTML reports Nov 07, 2023 5 - Production/Stable pytest>=7.0.0 :pypi:`pytest-html-cn` pytest plugin for generating HTML reports Aug 01, 2023 5 - Production/Stable N/A :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 Nov 11, 2023 N/A N/A + :pypi:`pytest-html-merger` Pytest HTML reports merging utility Jul 12, 2024 N/A N/A :pypi:`pytest-html-object-storage` Pytest report plugin for send HTML report on object-storage Jan 17, 2024 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 @@ -709,7 +712,7 @@ This list contains 1484 plugins. :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-ipywidgets` Jul 05, 2024 N/A pytest + :pypi:`pytest-ipywidgets` Jul 11, 2024 N/A pytest :pypi:`pytest-isolate` Feb 20, 2023 4 - Beta pytest :pypi:`pytest-isort` py.test plugin to check import ordering using isort Mar 05, 2024 5 - Production/Stable pytest (>=5.0) :pypi:`pytest-it` Pytest plugin to display test reports as a plaintext spec, inspired by Rspec: https://github.com/mattduck/pytest-it. Jan 29, 2024 4 - Beta N/A @@ -721,7 +724,7 @@ This list contains 1484 plugins. :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 Apr 30, 2024 3 - Alpha N/A - :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jun 06, 2024 N/A pytest>=7.2.0 + :pypi:`pytest-jira-xfail` Plugin skips (xfail) tests if unresolved Jira issue(s) linked Jul 09, 2024 N/A pytest>=7.2.0 :pypi:`pytest-jira-xray` pytest plugin to integrate tests with JIRA XRAY Mar 27, 2024 4 - Beta pytest>=6.2.4 :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 @@ -803,7 +806,7 @@ This list contains 1484 plugins. :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-manage` 用例标签化管理 Jun 07, 2024 N/A pytest + :pypi:`pytest-mark-manage` 用例标签化管理 Jul 08, 2024 N/A pytest :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` Easy way to match captured \`pytest\` output against expectations stored in files Mar 15, 2024 5 - Production/Stable pytest @@ -1087,7 +1090,7 @@ This list contains 1484 plugins. :pypi:`pytest-readme` Test your README.md file Sep 02, 2022 5 - Production/Stable N/A :pypi:`pytest-reana` Pytest fixtures for REANA. Mar 14, 2024 3 - Alpha N/A :pypi:`pytest-recorder` Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. Jun 27, 2024 N/A N/A - :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Dec 06, 2023 4 - Beta pytest>=3.5.0 + :pypi:`pytest-recording` A pytest plugin that allows you recording of network interactions via VCR.py Jul 09, 2024 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. Jun 19, 2024 5 - Production/Stable pytest>=6.2 :pypi:`pytest-redislite` Pytest plugin for testing code using Redis Apr 05, 2022 4 - Beta pytest @@ -1164,7 +1167,7 @@ This list contains 1484 plugins. :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. May 02, 2024 4 - Beta pytest>=5 + :pypi:`pytest-ruff` pytest plugin to check ruff requirements. Jul 09, 2024 4 - Beta pytest>=5 :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-run-subprocess` Pytest Plugin for running and testing subprocesses. Nov 12, 2022 5 - Production/Stable pytest @@ -1182,7 +1185,7 @@ This list contains 1484 plugins. :pypi:`pytest-sanity` Dec 07, 2020 N/A N/A :pypi:`pytest-sa-pg` May 14, 2019 N/A N/A :pypi:`pytest_sauce` pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs Jul 14, 2014 3 - Alpha N/A - :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jul 06, 2024 5 - Production/Stable N/A + :pypi:`pytest-sbase` A complete web automation framework for end-to-end testing. Jul 08, 2024 5 - Production/Stable N/A :pypi:`pytest-scenario` pytest plugin for test scenarios Feb 06, 2017 3 - Alpha N/A :pypi:`pytest-scenario-files` A pytest plugin that generates unit test scenarios from data files. May 19, 2024 5 - Production/Stable pytest>=7.2.0 :pypi:`pytest-schedule` The job of test scheduling for humans. Jan 07, 2023 5 - Production/Stable N/A @@ -1192,7 +1195,7 @@ This list contains 1484 plugins. :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 Feb 01, 2024 5 - Production/Stable pytest>=6.0.0 :pypi:`pytest-selenium-auto` pytest plugin to automatically capture screenshots upon selenium webdriver events Nov 07, 2023 N/A pytest >= 7.0.0 - :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jul 06, 2024 5 - Production/Stable N/A + :pypi:`pytest-seleniumbase` A complete web automation framework for end-to-end testing. Jul 08, 2024 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-selfie` A pytest plugin for selfie snapshot testing. Apr 05, 2024 N/A pytest<9.0.0,>=8.0.0 @@ -1270,8 +1273,8 @@ This list contains 1484 plugins. :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 Jul 03, 2024 N/A pytest<8,>5.4.0 - :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jun 14, 2024 N/A N/A + :pypi:`pytest-splunk-addon` A Dynamic test tool for Splunk Apps and Add-ons Jul 11, 2024 N/A pytest<8,>5.4.0 + :pypi:`pytest-splunk-addon-ui-smartx` Library to support testing Splunk Add-on UX Jul 10, 2024 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 @@ -1302,7 +1305,7 @@ This list contains 1484 plugins. :pypi:`pytest-subinterpreter` Run pytest in a subinterpreter Nov 25, 2023 N/A pytest>=7.0.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 Mar 07, 2024 4 - Beta pytest >=7.0 + :pypi:`pytest-subtests` unittest subTest() support and subtests fixture Jul 07, 2024 4 - Beta pytest>=7.0 :pypi:`pytest-subunit` pytest-subunit is a plugin for py.test which outputs testsresult in subunit format. Sep 17, 2023 N/A pytest (>=2.3) :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). Feb 01, 2024 4 - Beta pytest >=6.2.0 :pypi:`pytest-suitemanager` A simple plugin to use with pytest Apr 28, 2023 4 - Beta N/A @@ -1418,7 +1421,7 @@ This list contains 1484 plugins. :pypi:`pytest-tui` Text User Interface (TUI) and HTML report for Pytest test runs Dec 08, 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. Mar 19, 2024 5 - Production/Stable pytest >=2.3 + :pypi:`pytest-twisted` A twisted plugin for pytest. Jul 10, 2024 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-polarion` Typhoontest plugin for Siemens Polarion Feb 01, 2024 4 - Beta N/A @@ -1498,7 +1501,7 @@ This list contains 1484 plugins. :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-xvirt` A pytest plugin to virtualize test. For example to transparently running them on a remote box. Jul 03, 2024 4 - Beta pytest>=7.2.2 :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 Jul 02, 2024 N/A pytest>=7.4.0 + :pypi:`pytest-yaml-sanmu` pytest plugin for generating test cases by yaml Jul 12, 2024 N/A pytest>=7.4.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 19, 2023 N/A pytest (>=7.2.0) @@ -1804,7 +1807,7 @@ This list contains 1484 plugins. pytest-annotate: Generate PyAnnotate annotations from your pytest tests. :pypi:`pytest-ansible` - *last release*: Jul 03, 2024, + *last release*: Jul 10, 2024, *status*: 5 - Production/Stable, *requires*: pytest>=6 @@ -2287,7 +2290,7 @@ This list contains 1484 plugins. A pytest plugin to repeat the entire test suite in batches. :pypi:`pytest-bazel` - *last release*: Jul 05, 2024, + *last release*: Jul 12, 2024, *status*: 4 - Beta, *requires*: pytest @@ -2357,7 +2360,7 @@ This list contains 1484 plugins. Pytest plugin to run your tests with beartype checking enabled. :pypi:`pytest-bec-e2e` - *last release*: Jul 04, 2024, + *last release*: Jul 08, 2024, *status*: 3 - Alpha, *requires*: pytest @@ -3435,7 +3438,7 @@ This list contains 1484 plugins. Custom grouping for pytest-xdist, rename test cases name and test cases nodeid, support allure report :pypi:`pytest-custom-outputs` - *last release*: Jun 28, 2024, + *last release*: Jul 10, 2024, *status*: 4 - Beta, *requires*: pytest>=6.2.0 @@ -3833,6 +3836,13 @@ This list contains 1484 plugins. pytest-dir-equals is a pytest plugin providing helpers to assert directories equality allowing golden testing + :pypi:`pytest-dirty` + *last release*: Jul 11, 2024, + *status*: 3 - Alpha, + *requires*: pytest>=8.2; extra == "dev" + + Static import analysis for thrifty testing. + :pypi:`pytest-disable` *last release*: Sep 10, 2015, *status*: 4 - Beta, @@ -4225,6 +4235,13 @@ This list contains 1484 plugins. Pytest plugin with advanced doctest features. + :pypi:`pytest-documentary` + *last release*: Jul 11, 2024, + *status*: N/A, + *requires*: pytest + + A simple pytest plugin to generate test documentation + :pypi:`pytest-dogu-report` *last release*: Jul 07, 2023, *status*: N/A, @@ -5324,6 +5341,13 @@ This list contains 1484 plugins. Pytest Frappe Plugin - A set of pytest fixtures to test Frappe applications + :pypi:`pytest-freezeblaster` + *last release*: Jul 10, 2024, + *status*: N/A, + *requires*: pytest>=6.2.5 + + Wrap tests with fixtures in freeze_time + :pypi:`pytest-freezegun` *last release*: Jul 19, 2020, *status*: 4 - Beta, @@ -5458,7 +5482,7 @@ This list contains 1484 plugins. For finding/executing Ghost Inspector tests :pypi:`pytest-girder` - *last release*: Jul 02, 2024, + *last release*: Jul 08, 2024, *status*: N/A, *requires*: pytest>=3.6 @@ -5759,7 +5783,7 @@ This list contains 1484 plugins. A pytest plugin for use with homeassistant custom components. :pypi:`pytest-homeassistant-custom-component` - *last release*: Jul 06, 2024, + *last release*: Jul 11, 2024, *status*: 3 - Alpha, *requires*: pytest==8.2.0 @@ -5843,7 +5867,7 @@ This list contains 1484 plugins. optimized pytest plugin for generating HTML reports :pypi:`pytest-html-merger` - *last release*: Nov 11, 2023, + *last release*: Jul 12, 2024, *status*: N/A, *requires*: N/A @@ -6249,7 +6273,7 @@ This list contains 1484 plugins. THIS PROJECT IS ABANDONED :pypi:`pytest-ipywidgets` - *last release*: Jul 05, 2024, + *last release*: Jul 11, 2024, *status*: N/A, *requires*: pytest @@ -6333,7 +6357,7 @@ This list contains 1484 plugins. py.test JIRA integration plugin, using markers :pypi:`pytest-jira-xfail` - *last release*: Jun 06, 2024, + *last release*: Jul 09, 2024, *status*: N/A, *requires*: pytest>=7.2.0 @@ -6907,7 +6931,7 @@ This list contains 1484 plugins. UNKNOWN :pypi:`pytest-mark-manage` - *last release*: Jun 07, 2024, + *last release*: Jul 08, 2024, *status*: N/A, *requires*: pytest @@ -8895,7 +8919,7 @@ This list contains 1484 plugins. Pytest plugin, meant to facilitate unit tests writing for tools consumming Web APIs. :pypi:`pytest-recording` - *last release*: Dec 06, 2023, + *last release*: Jul 09, 2024, *status*: 4 - Beta, *requires*: pytest>=3.5.0 @@ -9434,7 +9458,7 @@ This list contains 1484 plugins. Coverage-based regression test selection (RTS) plugin for pytest :pypi:`pytest-ruff` - *last release*: May 02, 2024, + *last release*: Jul 09, 2024, *status*: 4 - Beta, *requires*: pytest>=5 @@ -9560,7 +9584,7 @@ This list contains 1484 plugins. pytest_sauce provides sane and helpful methods worked out in clearcode to run py.test tests with selenium/saucelabs :pypi:`pytest-sbase` - *last release*: Jul 06, 2024, + *last release*: Jul 08, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -9630,7 +9654,7 @@ This list contains 1484 plugins. pytest plugin to automatically capture screenshots upon selenium webdriver events :pypi:`pytest-seleniumbase` - *last release*: Jul 06, 2024, + *last release*: Jul 08, 2024, *status*: 5 - Production/Stable, *requires*: N/A @@ -10176,14 +10200,14 @@ This list contains 1484 plugins. :pypi:`pytest-splunk-addon` - *last release*: Jul 03, 2024, + *last release*: Jul 11, 2024, *status*: N/A, *requires*: pytest<8,>5.4.0 A Dynamic test tool for Splunk Apps and Add-ons :pypi:`pytest-splunk-addon-ui-smartx` - *last release*: Jun 14, 2024, + *last release*: Jul 10, 2024, *status*: N/A, *requires*: N/A @@ -10400,9 +10424,9 @@ This list contains 1484 plugins. A hack to explicitly set up and tear down fixtures. :pypi:`pytest-subtests` - *last release*: Mar 07, 2024, + *last release*: Jul 07, 2024, *status*: 4 - Beta, - *requires*: pytest >=7.0 + *requires*: pytest>=7.0 unittest subTest() support and subtests fixture @@ -11212,9 +11236,9 @@ This list contains 1484 plugins. :pypi:`pytest-twisted` - *last release*: Mar 19, 2024, + *last release*: Jul 10, 2024, *status*: 5 - Production/Stable, - *requires*: pytest >=2.3 + *requires*: pytest>=2.3 A twisted plugin for pytest. @@ -11772,7 +11796,7 @@ This list contains 1484 plugins. This plugin is used to load yaml output to your test using pytest framework. :pypi:`pytest-yaml-sanmu` - *last release*: Jul 02, 2024, + *last release*: Jul 12, 2024, *status*: N/A, *requires*: pytest>=7.4.0 From 711cee58b5d4a24a17f501f15d6b701bfa458726 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:47:30 +0200 Subject: [PATCH 191/199] build(deps): Bump pytest-twisted in /testing/plugins_integration (#12609) Bumps [pytest-twisted](https://github.com/pytest-dev/pytest-twisted) from 1.14.1 to 1.14.2. - [Release notes](https://github.com/pytest-dev/pytest-twisted/releases) - [Commits](https://github.com/pytest-dev/pytest-twisted/compare/v1.14.1...v1.14.2) --- updated-dependencies: - dependency-name: pytest-twisted dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- testing/plugins_integration/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/plugins_integration/requirements.txt b/testing/plugins_integration/requirements.txt index 12aa89f7644..4c1efcf32ed 100644 --- a/testing/plugins_integration/requirements.txt +++ b/testing/plugins_integration/requirements.txt @@ -10,6 +10,6 @@ pytest-mock==3.14.0 pytest-rerunfailures==14.0 pytest-sugar==1.0.0 pytest-trio==0.8.0 -pytest-twisted==1.14.1 +pytest-twisted==1.14.2 twisted==24.3.0 pytest-xvfb==3.0.0 From d9a2fd04635c57100f25e93d7d98317d824b2e69 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 23:00:01 +0000 Subject: [PATCH 192/199] [pre-commit.ci] pre-commit autoupdate (#12614) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.5.1 → v0.5.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.5.1...v0.5.2) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ce365ddbef2..419addd95be 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: "v0.5.1" + rev: "v0.5.2" hooks: - id: ruff args: ["--fix"] From 28b03b30aab8ed1ad86c2baba756d8486edd70d5 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 11 Jun 2024 15:11:43 +0200 Subject: [PATCH 193/199] [pylint/ruff] Activate PLR / PLC messages in ruff --- pyproject.toml | 42 ++++++++++++++++++++++++++++----------- src/_pytest/helpconfig.py | 1 - 2 files changed, 30 insertions(+), 13 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1eb4871bc21..f3eba4a08a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,9 @@ lint.select = [ "I", # isort "PGH004", # pygrep-hooks - Use specific rule codes when using noqa "PIE", # flake8-pie + "PLC", # pylint convention "PLE", # pylint error + "PLR", # pylint refactor "PLR1714", # Consider merging multiple comparisons "PLW", # pylint warning "PYI", # flake8-pyi @@ -139,6 +141,17 @@ lint.ignore = [ # what we're doing when we use type(..) is ... "E721", # Do not compare types, use `isinstance()` # pylint ignore + "PLC0105", # `TypeVar` name "E" does not reflect its covariance; + "PLC0414", # Import alias does not rename original package + "PLR0124", # Name compared with itself + "PLR0133", # Two constants compared in a comparison (lots of those in tests) + "PLR0402", # Use `from x.y import z` in lieu of alias + "PLR0911", # Too many return statements + "PLR0912", # Too many branches + "PLR0913", # Too many arguments in function definition + "PLR0915", # Too many statements + "PLR2004", # Magic value used in comparison + "PLR2044", # Line with empty comment "PLR5501", # Use `elif` instead of `else` then `if` "PLW0120", # remove the else and dedent its contents "PLW0603", # Using the global statement @@ -191,9 +204,9 @@ disable = [ "broad-exception-caught", "broad-exception-raised", "cell-var-from-loop", # B023 from ruff / flake8-bugbear - "comparison-of-constants", + "comparison-of-constants", # disabled in ruff (PLR0133) "comparison-with-callable", - "comparison-with-itself", + "comparison-with-itself", # PLR0124 from ruff "condition-evals-to-constant", "consider-using-dict-items", "consider-using-from-import", @@ -201,14 +214,17 @@ disable = [ "consider-using-in", "consider-using-ternary", "consider-using-with", + "consider-using-from-import", # not activated by default, PLR0402 disabled in ruff "cyclic-import", "disallowed-name", # foo / bar are used often in tests "duplicate-code", + "else-if-used", # not activated by default, PLR5501 disabled in ruff + "empty-comment", # not activated by default, PLR2044 disabled in ruff "eval-used", "exec-used", "expression-not-assigned", "fixme", - "global-statement", + "global-statement", # PLW0603 disabled in ruff "import-error", "import-outside-toplevel", "inconsistent-return-statements", @@ -218,6 +234,7 @@ disable = [ "invalid-str-returned", "keyword-arg-before-vararg", "line-too-long", + "magic-value-comparison", # not activated by default, PLR2004 disabled in ruff "method-hidden", "missing-docstring", "missing-timeout", @@ -232,14 +249,15 @@ disable = [ "no-self-argument", "not-an-iterable", "not-callable", - "pointless-exception-statement", - "pointless-statement", - "pointless-string-statement", + "pointless-exception-statement", # https://github.com/pytest-dev/pytest/pull/12379 + "pointless-statement", # https://github.com/pytest-dev/pytest/pull/12379 + "pointless-string-statement", # https://github.com/pytest-dev/pytest/pull/12379 "possibly-used-before-assignment", "protected-access", "raise-missing-from", "redefined-argument-from-local", "redefined-builtin", + "redefined-loop-name", # PLW2901 disabled in ruff "redefined-outer-name", "reimported", "simplifiable-condition", @@ -249,18 +267,18 @@ disable = [ "super-init-not-called", "too-few-public-methods", "too-many-ancestors", - "too-many-arguments", - "too-many-branches", + "too-many-arguments", # disabled in ruff + "too-many-branches", # disabled in ruff "too-many-function-args", "too-many-instance-attributes", "too-many-lines", "too-many-locals", "too-many-nested-blocks", "too-many-public-methods", - "too-many-return-statements", - "too-many-statements", + "too-many-return-statements", # disabled in ruff + "too-many-statements", # disabled in ruff "try-except-raise", - "typevar-name-incorrect-variance", + "typevar-name-incorrect-variance", # PLC0105 disabled in ruff "unbalanced-tuple-unpacking", "undefined-loop-variable", "undefined-variable", @@ -280,7 +298,7 @@ disable = [ "use-dict-literal", "use-implicit-booleaness-not-comparison", "use-implicit-booleaness-not-len", - "useless-else-on-loop", + "useless-else-on-loop", # PLC0414 disabled in ruff "useless-import-alias", "useless-return", "using-constant-test", diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py index 1433607a19b..1886d5c9342 100644 --- a/src/_pytest/helpconfig.py +++ b/src/_pytest/helpconfig.py @@ -238,7 +238,6 @@ def showhelp(config: Config) -> None: for warningreport in reporter.stats.get("warnings", []): tw.line("warning : " + warningreport.message, red=True) - return conftest_options = [("pytest_plugins", "list of plugin names to load")] From 6f0faec9e3121affb8d2f19f9b026caa373e56c5 Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Tue, 16 Jul 2024 16:07:40 +0200 Subject: [PATCH 194/199] [ruff] Fix redefined argument 'exc' --- src/_pytest/_io/saferepr.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/_io/saferepr.py b/src/_pytest/_io/saferepr.py index 13b793f0a77..cee70e332f9 100644 --- a/src/_pytest/_io/saferepr.py +++ b/src/_pytest/_io/saferepr.py @@ -18,8 +18,8 @@ def _format_repr_exception(exc: BaseException, obj: object) -> str: exc_info = _try_repr_or_str(exc) except (KeyboardInterrupt, SystemExit): raise - except BaseException as exc: - exc_info = f"unpresentable exception ({_try_repr_or_str(exc)})" + except BaseException as inner_exc: + exc_info = f"unpresentable exception ({_try_repr_or_str(inner_exc)})" return ( f"<[{exc_info} raised in repr()] {type(obj).__name__} object at 0x{id(obj):x}>" ) From 80c0302af8014f44f3f0bac3fb4740a17331152c Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 16 Jul 2024 18:36:38 +0300 Subject: [PATCH 195/199] doc: mention `PYTEST_DISABLE_PLUGIN_AUTOLOAD` in writing_plugins.rst --- doc/en/how-to/writing_plugins.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index 14e5194ce63..1bba9644649 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -43,7 +43,8 @@ Plugin discovery order at tool startup and loading the specified plugin. This happens before normal command-line parsing. 4. by loading all plugins registered through installed third-party package - :ref:`entry points `. + :ref:`entry points `, unless the + :envvar:`PYTEST_DISABLE_PLUGIN_AUTOLOAD` environment variable is set. 5. by loading all plugins specified through the :envvar:`PYTEST_PLUGINS` environment variable. From d489247505a953885a156e61d4473497cbc167ea Mon Sep 17 00:00:00 2001 From: Nicolas Simonds <0xDEC0DE@users.noreply.github.com> Date: Wed, 17 Jul 2024 06:24:03 -0700 Subject: [PATCH 196/199] Fix caching of parameterized fixtures (#12600) The fix for Issue #6541 caused regression where cache hits became cache misses, unexpectedly. Fixes #6962 --------- Co-authored-by: Nicolas Simonds Co-authored-by: Bruno Oliveira Co-authored-by: Ran Benita --- AUTHORS | 1 + changelog/6962.bugfix.rst | 2 ++ src/_pytest/fixtures.py | 14 ++++++++++---- testing/python/fixtures.py | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 changelog/6962.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 8d31170560c..9b6cb6a9d23 100644 --- a/AUTHORS +++ b/AUTHORS @@ -306,6 +306,7 @@ Nicholas Devenish Nicholas Murphy Niclas Olofsson Nicolas Delaby +Nicolas Simonds Nico Vidal Nikolay Kondratyev Nipunn Koorapati diff --git a/changelog/6962.bugfix.rst b/changelog/6962.bugfix.rst new file mode 100644 index 00000000000..030b6e06392 --- /dev/null +++ b/changelog/6962.bugfix.rst @@ -0,0 +1,2 @@ +Parametrization parameters are now compared using `==` instead of `is` (`is` is still used as a fallback if the parameter does not support `==`). +This fixes use of parameters such as lists, which have a different `id` but compare equal, causing fixtures to be re-computed instead of being cached. diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index 0151a4d9c86..7d0b40b150a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -1053,12 +1053,18 @@ def execute(self, request: SubRequest) -> FixtureValue: requested_fixtures_that_should_finalize_us.append(fixturedef) # Check for (and return) cached value/exception. - my_cache_key = self.cache_key(request) if self.cached_result is not None: + request_cache_key = self.cache_key(request) cache_key = self.cached_result[1] - # note: comparison with `==` can fail (or be expensive) for e.g. - # numpy arrays (#6497). - if my_cache_key is cache_key: + try: + # Attempt to make a normal == check: this might fail for objects + # which do not implement the standard comparison (like numpy arrays -- #6497). + cache_hit = bool(request_cache_key == cache_key) + except (ValueError, RuntimeError): + # If the comparison raises, use 'is' as fallback. + cache_hit = request_cache_key is cache_key + + if cache_hit: if self.cached_result[2] is not None: exc, exc_tb = self.cached_result[2] raise exc.with_traceback(exc_tb) diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 5c3a6a35b34..8d2646309a8 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -1557,6 +1557,38 @@ def test_printer_2(self): result = pytester.runpytest() result.stdout.fnmatch_lines(["* 2 passed in *"]) + def test_parameterized_fixture_caching(self, pytester: Pytester) -> None: + """Regression test for #12600.""" + pytester.makepyfile( + """ + import pytest + from itertools import count + + CACHE_MISSES = count(0) + + def pytest_generate_tests(metafunc): + if "my_fixture" in metafunc.fixturenames: + # Use unique objects for parametrization (as opposed to small strings + # and small integers which are singletons). + metafunc.parametrize("my_fixture", [[1], [2]], indirect=True) + + @pytest.fixture(scope='session') + def my_fixture(request): + next(CACHE_MISSES) + + def test1(my_fixture): + pass + + def test2(my_fixture): + pass + + def teardown_module(): + assert next(CACHE_MISSES) == 2 + """ + ) + result = pytester.runpytest() + result.stdout.no_fnmatch_line("* ERROR at teardown *") + class TestFixtureManagerParseFactories: @pytest.fixture From 9eee45a7479cf5fa23b79057708a994a3b8d0eee Mon Sep 17 00:00:00 2001 From: SOUBHIK KUMAR MITRA Date: Mon, 1 Jul 2024 22:37:58 +0530 Subject: [PATCH 197/199] Bump PyPy runtime to v3.9 @ GHA PyPy 3.8 has a flaky bug in the garbage collector that is not going to be fixed there since that version is EoL, but newer versions have it addressed. The problem manifests itself in CI as follows: ```console TypeError: expected some sort of stmt, but got <_ast.Load object ...> ``` This patch makes use of the PyPy version that is still supported and contains a fix for the said bug. Ref: https://www.pypy.org/posts/2024/03/fixing-bug-incremental-gc.html Resolves #11771. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d78e0900fd9..9158d6bcc72 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -150,7 +150,7 @@ jobs: tox_env: "py313" use_coverage: true - name: "ubuntu-pypy3" - python: "pypy-3.8" + python: "pypy-3.9" os: ubuntu-latest tox_env: "pypy3-xdist" From d42b76daadb88d993ee74753766e22711a27395f Mon Sep 17 00:00:00 2001 From: SOUBHIK KUMAR MITRA Date: Fri, 19 Jul 2024 17:26:46 +0530 Subject: [PATCH 198/199] Adjust test_errors_in_xfail_skip_expressions for PyPy It appears that newer PyPy versions have a different syntax error marker offset. This patch stripps 7 whitespaces off of the marker line. --- testing/test_skipping.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 558e3d35c6a..d1a63b1d920 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1140,8 +1140,8 @@ def test_func(): result = pytester.runpytest() markline = " ^" pypy_version_info = getattr(sys, "pypy_version_info", None) - if pypy_version_info is not None and pypy_version_info < (6,): - markline = markline[1:] + if pypy_version_info is not None: + markline = markline[7:] if sys.version_info >= (3, 10): expected = [ From ced7072bb4f7653ad2f1d0d33639d87e7bc5f358 Mon Sep 17 00:00:00 2001 From: SOUBHIK KUMAR MITRA Date: Thu, 4 Jul 2024 15:01:34 +0530 Subject: [PATCH 199/199] Add a change note for PR #11771 Co-authored-by: Sviatoslav Sydorenko --- changelog/11771.contrib.rst | 5 +++++ changelog/12557.contrib.rst | 1 + 2 files changed, 6 insertions(+) create mode 100644 changelog/11771.contrib.rst create mode 120000 changelog/12557.contrib.rst diff --git a/changelog/11771.contrib.rst b/changelog/11771.contrib.rst new file mode 100644 index 00000000000..a3c1ed1099e --- /dev/null +++ b/changelog/11771.contrib.rst @@ -0,0 +1,5 @@ +The PyPy runtime version has been updated to 3.9 from 3.8 that introduced +a flaky bug at the garbage collector which was not expected to fix there +as the V3.8 is EoL. + +-- by :user:`x612skm` diff --git a/changelog/12557.contrib.rst b/changelog/12557.contrib.rst new file mode 120000 index 00000000000..c036c519093 --- /dev/null +++ b/changelog/12557.contrib.rst @@ -0,0 +1 @@ +11771.contrib.rst \ No newline at end of file