From d9bf9dbec104af1e3a2c02b04f4e1d501d5d86a2 Mon Sep 17 00:00:00 2001
From: pytest bot <pytestbot@gmail.com>
Date: Fri, 23 Jun 2023 10:47:41 +0000
Subject: [PATCH 01/55] Prepare release version 7.4.0

[ran: made some fixups]
---
 changelog/10831.bugfix.rst        |  1 -
 changelog/10872.improvement.rst   |  1 -
 changelog/10901.feature.rst       |  2 -
 changelog/10907.improvement.rst   |  5 --
 changelog/10940.improvement.rst   |  3 -
 changelog/10991.improvement.rst   |  1 -
 changelog/11005.improvement.rst   |  1 -
 changelog/11013.improvement.rst   |  1 -
 changelog/11031.trivial.rst       |  1 -
 changelog/11043.improvement.rst   |  3 -
 changelog/11068.bugfix.rst        |  1 -
 changelog/11081.improvement.rst   |  7 ---
 changelog/11104.bugfix.rst        |  3 -
 changelog/1904.bugfix.rst         |  1 -
 changelog/7781.bugfix.rst         |  1 -
 changelog/8711.improvement.rst    |  3 -
 changelog/9146.doc.rst            |  1 -
 doc/en/announce/index.rst         |  1 +
 doc/en/announce/release-7.4.0.rst | 49 ++++++++++++++++
 doc/en/builtin.rst                |  6 +-
 doc/en/changelog.rst              | 92 +++++++++++++++++++++++++++++++
 doc/en/getting-started.rst        |  2 +-
 doc/en/reference/reference.rst    |  4 +-
 23 files changed, 148 insertions(+), 42 deletions(-)
 delete mode 100644 changelog/10831.bugfix.rst
 delete mode 100644 changelog/10872.improvement.rst
 delete mode 100644 changelog/10901.feature.rst
 delete mode 100644 changelog/10907.improvement.rst
 delete mode 100644 changelog/10940.improvement.rst
 delete mode 100644 changelog/10991.improvement.rst
 delete mode 100644 changelog/11005.improvement.rst
 delete mode 100644 changelog/11013.improvement.rst
 delete mode 100644 changelog/11031.trivial.rst
 delete mode 100644 changelog/11043.improvement.rst
 delete mode 100644 changelog/11068.bugfix.rst
 delete mode 100644 changelog/11081.improvement.rst
 delete mode 100644 changelog/11104.bugfix.rst
 delete mode 100644 changelog/1904.bugfix.rst
 delete mode 100644 changelog/7781.bugfix.rst
 delete mode 100644 changelog/8711.improvement.rst
 delete mode 100644 changelog/9146.doc.rst
 create mode 100644 doc/en/announce/release-7.4.0.rst

diff --git a/changelog/10831.bugfix.rst b/changelog/10831.bugfix.rst
deleted file mode 100644
index ea641dee60e..00000000000
--- a/changelog/10831.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.
diff --git a/changelog/10872.improvement.rst b/changelog/10872.improvement.rst
deleted file mode 100644
index fe0c01a0224..00000000000
--- a/changelog/10872.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.
diff --git a/changelog/10901.feature.rst b/changelog/10901.feature.rst
deleted file mode 100644
index 0d99d66f67f..00000000000
--- a/changelog/10901.feature.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
-This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
diff --git a/changelog/10907.improvement.rst b/changelog/10907.improvement.rst
deleted file mode 100644
index 7f011a827d3..00000000000
--- a/changelog/10907.improvement.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
-
-"All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
-
-Previously, the last frame of the traceback was shown, even though it was hidden.
diff --git a/changelog/10940.improvement.rst b/changelog/10940.improvement.rst
deleted file mode 100644
index f3e33cbc68d..00000000000
--- a/changelog/10940.improvement.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
-
-Added :func:`TerminalReporter.wrap_write() <pytest.TerminalReporter.wrap_write>` as a helper for that.
diff --git a/changelog/10991.improvement.rst b/changelog/10991.improvement.rst
deleted file mode 100644
index 768c08e554c..00000000000
--- a/changelog/10991.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.
diff --git a/changelog/11005.improvement.rst b/changelog/11005.improvement.rst
deleted file mode 100644
index 295252514a6..00000000000
--- a/changelog/11005.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added underlying exception to cache provider path creation and write warning messages.
diff --git a/changelog/11013.improvement.rst b/changelog/11013.improvement.rst
deleted file mode 100644
index fe3ece93c23..00000000000
--- a/changelog/11013.improvement.rst
+++ /dev/null
@@ -1 +0,0 @@
-Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.
diff --git a/changelog/11031.trivial.rst b/changelog/11031.trivial.rst
deleted file mode 100644
index fad2cf24d5a..00000000000
--- a/changelog/11031.trivial.rst
+++ /dev/null
@@ -1 +0,0 @@
-Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.
diff --git a/changelog/11043.improvement.rst b/changelog/11043.improvement.rst
deleted file mode 100644
index 1fe0361d7f9..00000000000
--- a/changelog/11043.improvement.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir`.
-Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
-If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.
diff --git a/changelog/11068.bugfix.rst b/changelog/11068.bugfix.rst
deleted file mode 100644
index 45cdb105fb4..00000000000
--- a/changelog/11068.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
diff --git a/changelog/11081.improvement.rst b/changelog/11081.improvement.rst
deleted file mode 100644
index ccfaf6016ae..00000000000
--- a/changelog/11081.improvement.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-The :confval:`norecursedir` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it.
-
-If after updating to this version you see that your `norecursedir` setting is not being respected,
-it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation.
-Most likely, your hook returns `False` for paths it does not want to ignore,
-which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path.
-The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore.
diff --git a/changelog/11104.bugfix.rst b/changelog/11104.bugfix.rst
deleted file mode 100644
index 10f0db92515..00000000000
--- a/changelog/11104.bugfix.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests,
-even when it was not utilized (e.g. when explicit paths were given on the command line).
-Now the ``testpaths`` are only considered when they are in use.
diff --git a/changelog/1904.bugfix.rst b/changelog/1904.bugfix.rst
deleted file mode 100644
index 3e1a292152b..00000000000
--- a/changelog/1904.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
diff --git a/changelog/7781.bugfix.rst b/changelog/7781.bugfix.rst
deleted file mode 100644
index 191fcd4daf4..00000000000
--- a/changelog/7781.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix writing non-encodable text to log file when using ``--debug``.
diff --git a/changelog/8711.improvement.rst b/changelog/8711.improvement.rst
deleted file mode 100644
index 20805e9f94d..00000000000
--- a/changelog/8711.improvement.rst
+++ /dev/null
@@ -1,3 +0,0 @@
-:func:`_pytest.logging.LogCaptureFixture.set_level` and :func:`_pytest.logging.LogCaptureFixture.at_level`
-will temporarily enable the requested ``level`` if ``level`` was disabled globally via
-``logging.disable(LEVEL)``.
diff --git a/changelog/9146.doc.rst b/changelog/9146.doc.rst
deleted file mode 100644
index 95189b96d46..00000000000
--- a/changelog/9146.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improve Documentation for `caplog.set_level`.
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index bcc0669a62f..914e763bd9e 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,7 @@ Release announcements
    :maxdepth: 2
 
 
+   release-7.4.0
    release-7.3.2
    release-7.3.1
    release-7.3.0
diff --git a/doc/en/announce/release-7.4.0.rst b/doc/en/announce/release-7.4.0.rst
new file mode 100644
index 00000000000..5a0d18267d3
--- /dev/null
+++ b/doc/en/announce/release-7.4.0.rst
@@ -0,0 +1,49 @@
+pytest-7.4.0
+=======================================
+
+The pytest team is proud to announce the 7.4.0 release!
+
+This release contains new features, improvements, and bug fixes,
+the full list of changes is available in the changelog:
+
+    https://docs.pytest.org/en/stable/changelog.html
+
+For complete documentation, please visit:
+
+    https://docs.pytest.org/en/stable/
+
+As usual, you can upgrade from PyPI via:
+
+    pip install -U pytest
+
+Thanks to all of the contributors to this release:
+
+* Adam J. Stewart
+* Alessio Izzo
+* Alex
+* Alex Lambson
+* Brian Larsen
+* Bruno Oliveira
+* Bryan Ricker
+* Chris Mahoney
+* Facundo Batista
+* Florian Bruhin
+* Jarrett Keifer
+* Kenny Y
+* Miro Hrončok
+* Ran Benita
+* Roberto Aldera
+* Ronny Pfannschmidt
+* Sergey Kim
+* Stefanie Molin
+* Vijay Arora
+* Ville Skyttä
+* Zac Hatfield-Dodds
+* bzoracler
+* leeyueh
+* nondescryptid
+* theirix
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst
index 7ae185f6c7d..53305eecded 100644
--- a/doc/en/builtin.rst
+++ b/doc/en/builtin.rst
@@ -22,7 +22,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
     cachedir: .pytest_cache
     rootdir: /home/sweet/project
     collected 0 items
-    cache -- .../_pytest/cacheprovider.py:510
+    cache -- .../_pytest/cacheprovider.py:528
         Return a cache object that can persist state between testing sessions.
 
         cache.get(key, default)
@@ -119,7 +119,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
 
         For more details: :ref:`doctest_namespace`.
 
-    pytestconfig [session scope] -- .../_pytest/fixtures.py:1360
+    pytestconfig [session scope] -- .../_pytest/fixtures.py:1353
         Session-scoped fixture that returns the session's :class:`pytest.Config`
         object.
 
@@ -196,7 +196,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
 
         .. _legacy_path: https://py.readthedocs.io/en/latest/path.html
 
-    caplog -- .../_pytest/logging.py:498
+    caplog -- .../_pytest/logging.py:570
         Access and control log capturing.
 
         Captured logs are available through the following properties/methods::
diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index cdc9e66f68b..391721df34d 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -28,6 +28,98 @@ with advance notice in the **Deprecations** section of releases.
 
 .. towncrier release notes start
 
+pytest 7.4.0 (2023-06-23)
+=========================
+
+Features
+--------
+
+- `#10901 <https://github.com/pytest-dev/pytest/issues/10901>`_: Added :func:`ExceptionInfo.from_exception() <pytest.ExceptionInfo.from_exception>`, a simpler way to create an :class:`~pytest.ExceptionInfo` from an exception.
+  This can replace :func:`ExceptionInfo.from_exc_info() <pytest.ExceptionInfo.from_exc_info()>` for most uses.
+
+
+
+Improvements
+------------
+
+- `#10872 <https://github.com/pytest-dev/pytest/issues/10872>`_: Update test log report annotation to named tuple and fixed inconsistency in docs for :hook:`pytest_report_teststatus` hook.
+
+
+- `#10907 <https://github.com/pytest-dev/pytest/issues/10907>`_: When an exception traceback to be displayed is completely filtered out (by mechanisms such as ``__tracebackhide__``, internal frames, and similar), now only the exception string and the following message are shown:
+
+  "All traceback entries are hidden. Pass `--full-trace` to see hidden and internal frames.".
+
+  Previously, the last frame of the traceback was shown, even though it was hidden.
+
+
+- `#10940 <https://github.com/pytest-dev/pytest/issues/10940>`_: Improved verbose output (``-vv``) of ``skip`` and ``xfail`` reasons by performing text wrapping while leaving a clear margin for progress output.
+
+  Added ``TerminalReporter.wrap_write()`` as a helper for that.
+
+
+- `#10991 <https://github.com/pytest-dev/pytest/issues/10991>`_: Added handling of ``%f`` directive to print microseconds in log format options, such as ``log-date-format``.
+
+
+- `#11005 <https://github.com/pytest-dev/pytest/issues/11005>`_: Added the underlying exception to the cache provider's path creation and write warning messages.
+
+
+- `#11013 <https://github.com/pytest-dev/pytest/issues/11013>`_: Added warning when :confval:`testpaths` is set, but paths are not found by glob. In this case, pytest will fall back to searching from the current directory.
+
+
+- `#11043 <https://github.com/pytest-dev/pytest/issues/11043>`_: When `--confcutdir` is not specified, and there is no config file present, the conftest cutoff directory (`--confcutdir`) is now set to the :ref:`rootdir <rootdir>`.
+  Previously in such cases, `conftest.py` files would be probed all the way to the root directory of the filesystem.
+  If you are badly affected by this change, consider adding an empty config file to your desired cutoff directory, or explicitly set `--confcutdir`.
+
+
+- `#11081 <https://github.com/pytest-dev/pytest/issues/11081>`_: The :confval:`norecursedirs` check is now performed in a :hook:`pytest_ignore_collect` implementation, so plugins can affect it.
+
+  If after updating to this version you see that your `norecursedirs` setting is not being respected,
+  it means that a conftest or a plugin you use has a bad `pytest_ignore_collect` implementation.
+  Most likely, your hook returns `False` for paths it does not want to ignore,
+  which ends the processing and doesn't allow other plugins, including pytest itself, to ignore the path.
+  The fix is to return `None` instead of `False` for paths your hook doesn't want to ignore.
+
+
+- `#8711 <https://github.com/pytest-dev/pytest/issues/8711>`_: :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>` and :func:`caplog.at_level() <pytest.LogCaptureFixture.at_level>`
+  will temporarily enable the requested ``level`` if ``level`` was disabled globally via
+  ``logging.disable(LEVEL)``.
+
+
+
+Bug Fixes
+---------
+
+- `#10831 <https://github.com/pytest-dev/pytest/issues/10831>`_: Terminal Reporting: Fixed bug when running in ``--tb=line`` mode where ``pytest.fail(pytrace=False)`` tests report ``None``.
+
+
+- `#11068 <https://github.com/pytest-dev/pytest/issues/11068>`_: Fixed the ``--last-failed`` whole-file skipping functionality ("skipped N files") for :ref:`non-python test files <non-python tests>`.
+
+
+- `#11104 <https://github.com/pytest-dev/pytest/issues/11104>`_: Fixed a regression in pytest 7.3.2 which caused to :confval:`testpaths` to be considered for loading initial conftests,
+  even when it was not utilized (e.g. when explicit paths were given on the command line).
+  Now the ``testpaths`` are only considered when they are in use.
+
+
+- `#1904 <https://github.com/pytest-dev/pytest/issues/1904>`_: Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
+
+
+- `#7781 <https://github.com/pytest-dev/pytest/issues/7781>`_: Fix writing non-encodable text to log file when using ``--debug``.
+
+
+
+Improved Documentation
+----------------------
+
+- `#9146 <https://github.com/pytest-dev/pytest/issues/9146>`_: Improved documentation for :func:`caplog.set_level() <pytest.LogCaptureFixture.set_level>`.
+
+
+
+Trivial/Internal Changes
+------------------------
+
+- `#11031 <https://github.com/pytest-dev/pytest/issues/11031>`_: Enhanced the CLI flag for ``-c`` to now include ``--config-file`` to make it clear that this flag applies to the usage of a custom config file.
+
+
 pytest 7.3.2 (2023-06-10)
 =========================
 
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index 2dbf7d3870c..e295c180454 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -22,7 +22,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 7.3.2
+    pytest 7.4.0
 
 .. _`simpletest`:
 
diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index c6629db84fa..83bbccbcbdc 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -1923,9 +1923,9 @@ All the command-line flags can be obtained by running ``pytest --help``::
       --strict-markers      Markers not registered in the `markers` section of
                             the configuration file raise errors
       --strict              (Deprecated) alias to --strict-markers
-      -c, --config-file FILE
+      -c FILE, --config-file=FILE
                             Load configuration from `FILE` instead of trying to
-                            locate one of the implicit configuration files
+                            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

From b6c55787fec3a3584ee7f73ada9568708f7c004c Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Mon, 3 Jul 2023 13:17:02 -0300
Subject: [PATCH 02/55] Switch to deploy environment and configure for pypi
 oidc (#10925) (#11162)

Closes #10871
Closes #10870

Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
---
 .github/workflows/deploy.yml | 40 ++++++++++++++++++++++++------------
 1 file changed, 27 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 25280994687..e6b4f0ea2d7 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -13,40 +13,54 @@ on:
 permissions: {}
 
 jobs:
-
-  deploy:
-    if: github.repository == 'pytest-dev/pytest'
-
+  build:
     runs-on: ubuntu-latest
-    timeout-minutes: 30
-    permissions:
-      contents: write
-
+    timeout-minutes: 10
     steps:
     - uses: actions/checkout@v3
       with:
         fetch-depth: 0
         persist-credentials: false
-
     - name: Build and Check Package
       uses: hynek/build-and-inspect-python-package@v1.5
 
+  deploy:
+    if: github.repository == 'pytest-dev/pytest'
+    needs: [build]
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+    permissions:
+      id-token: write
+    steps:
     - name: Download Package
       uses: actions/download-artifact@v3
       with:
         name: Packages
         path: dist
-
     - name: Publish package to PyPI
-      uses: pypa/gh-action-pypi-publish@release/v1
-      with:
-        password: ${{ secrets.pypi_token }}
+      uses: pypa/gh-action-pypi-publish@v1.8.5
+
+  release-notes:
 
+    # todo: generate the content in the build  job
+    #       the goal being of using a github action script to push the release data
+    #       after success instead of creating a complete python/tox env
+    needs: [deploy]
+    runs-on: ubuntu-latest
+    timeout-minutes: 30
+    permissions:
+      contents: write
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+        persist-credentials: false
     - name: Set up Python
       uses: actions/setup-python@v4
       with:
         python-version: "3.7"
 
+
     - name: Install tox
       run: |
         python -m pip install --upgrade pip

From a4d7254d18a201016b97ce6a6e3f41a124e7e84c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 3 Jul 2023 16:33:47 +0000
Subject: [PATCH 03/55] [7.4.x] Fix duplicated imports with importlib mode and
 doctest-modules (#11164)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 changelog/10811.bugfix.rst |  2 ++
 src/_pytest/pathlib.py     |  2 ++
 testing/acceptance_test.py | 35 +++++++++++++++++++++++++++++++++++
 testing/test_pathlib.py    | 27 +++++++++++++++++++--------
 4 files changed, 58 insertions(+), 8 deletions(-)
 create mode 100644 changelog/10811.bugfix.rst

diff --git a/changelog/10811.bugfix.rst b/changelog/10811.bugfix.rst
new file mode 100644
index 00000000000..aa26414e457
--- /dev/null
+++ b/changelog/10811.bugfix.rst
@@ -0,0 +1,2 @@
+Fixed issue when using ``--import-mode=importlib`` together with ``--doctest-modules`` that caused modules
+to be imported more than once, causing problems with modules that have import side effects.
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index 70383e4b504..e5fdd639a82 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -523,6 +523,8 @@ def import_path(
 
     if mode is ImportMode.importlib:
         module_name = module_name_from_path(path, root)
+        with contextlib.suppress(KeyError):
+            return sys.modules[module_name]
 
         for meta_importer in sys.meta_path:
             spec = meta_importer.find_spec(module_name, [str(path.parent)])
diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py
index 5658f2fd6b8..ed750a97071 100644
--- a/testing/acceptance_test.py
+++ b/testing/acceptance_test.py
@@ -1317,3 +1317,38 @@ def test_stuff():
     )
     res = pytester.runpytest()
     res.stdout.fnmatch_lines(["*Did you mean to use `assert` instead of `return`?*"])
+
+
+def test_doctest_and_normal_imports_with_importlib(pytester: Pytester) -> None:
+    """
+    Regression test for #10811: previously import_path with ImportMode.importlib would
+    not return a module if already in sys.modules, resulting in modules being imported
+    multiple times, which causes problems with modules that have import side effects.
+    """
+    # Uses the exact reproducer form #10811, given it is very minimal
+    # and illustrates the problem well.
+    pytester.makepyfile(
+        **{
+            "pmxbot/commands.py": "from . import logging",
+            "pmxbot/logging.py": "",
+            "tests/__init__.py": "",
+            "tests/test_commands.py": """
+                import importlib
+                from pmxbot import logging
+
+                class TestCommands:
+                    def test_boo(self):
+                        assert importlib.import_module('pmxbot.logging') is logging
+                """,
+        }
+    )
+    pytester.makeini(
+        """
+        [pytest]
+        addopts=
+            --doctest-modules
+            --import-mode importlib
+        """
+    )
+    result = pytester.runpytest_subprocess()
+    result.stdout.fnmatch_lines("*1 passed*")
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 56c54e484da..945d6f7d746 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -7,6 +7,7 @@
 from types import ModuleType
 from typing import Any
 from typing import Generator
+from typing import Iterator
 
 import pytest
 from _pytest.monkeypatch import MonkeyPatch
@@ -282,29 +283,36 @@ def test_invalid_path(self, tmp_path: Path) -> None:
             import_path(tmp_path / "invalid.py", root=tmp_path)
 
     @pytest.fixture
-    def simple_module(self, tmp_path: Path) -> Path:
-        fn = tmp_path / "_src/tests/mymod.py"
+    def simple_module(
+        self, tmp_path: Path, request: pytest.FixtureRequest
+    ) -> Iterator[Path]:
+        name = f"mymod_{request.node.name}"
+        fn = tmp_path / f"_src/tests/{name}.py"
         fn.parent.mkdir(parents=True)
         fn.write_text("def foo(x): return 40 + x", encoding="utf-8")
-        return fn
+        module_name = module_name_from_path(fn, root=tmp_path)
+        yield fn
+        sys.modules.pop(module_name, None)
 
-    def test_importmode_importlib(self, simple_module: Path, tmp_path: Path) -> None:
+    def test_importmode_importlib(
+        self, simple_module: Path, tmp_path: Path, request: pytest.FixtureRequest
+    ) -> None:
         """`importlib` mode does not change sys.path."""
         module = import_path(simple_module, mode="importlib", root=tmp_path)
         assert module.foo(2) == 42  # type: ignore[attr-defined]
         assert str(simple_module.parent) not in sys.path
         assert module.__name__ in sys.modules
-        assert module.__name__ == "_src.tests.mymod"
+        assert module.__name__ == f"_src.tests.mymod_{request.node.name}"
         assert "_src" in sys.modules
         assert "_src.tests" in sys.modules
 
-    def test_importmode_twice_is_different_module(
+    def test_remembers_previous_imports(
         self, simple_module: Path, tmp_path: Path
     ) -> None:
-        """`importlib` mode always returns a new module."""
+        """`importlib` mode called remembers previous module (#10341, #10811)."""
         module1 = import_path(simple_module, mode="importlib", root=tmp_path)
         module2 = import_path(simple_module, mode="importlib", root=tmp_path)
-        assert module1 is not module2
+        assert module1 is module2
 
     def test_no_meta_path_found(
         self, simple_module: Path, monkeypatch: MonkeyPatch, tmp_path: Path
@@ -317,6 +325,9 @@ def test_no_meta_path_found(
         # mode='importlib' fails if no spec is found to load the module
         import importlib.util
 
+        # Force module to be re-imported.
+        del sys.modules[module.__name__]
+
         monkeypatch.setattr(
             importlib.util, "spec_from_file_location", lambda *args: None
         )

From d53951836dc2ef6cbc454356243460640737c49b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 4 Jul 2023 21:49:24 +0000
Subject: [PATCH 04/55] [7.4.x] Update open trainings (#11172)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
---
 doc/en/index.rst | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/doc/en/index.rst b/doc/en/index.rst
index de07831ac14..50f3e9c10c3 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -2,8 +2,9 @@
 
 .. sidebar:: Next Open Trainings
 
-   - `pytest tips and tricks for a better testsuite <https://ep2023.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2023 <https://ep2023.europython.eu/>`_, July 18th (3h), Prague/Remote
-   - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote
+   - `pytest tips and tricks for a better testsuite <https://ep2023.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2023 <https://ep2023.europython.eu/>`_, **July 18th** (3h), **Prague, Czech Republic / Remote**
+   - `pytest: Professionelles Testen (nicht nur) für Python <https://workshoptage.ch/workshops/2023/pytest-professionelles-testen-nicht-nur-fuer-python-2/>`_, at `Workshoptage 2023 <https://workshoptage.ch/>`_, **September 5th**, `OST <https://www.ost.ch/en>`_ Campus **Rapperswil, Switzerland**
+   - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, **March 5th to 7th 2024** (3 day in-depth training), **Leipzig, Germany / Remote**
 
    Also see :doc:`previous talks and blogposts <talks>`.
 

From c71b5df7347a84ef270888c8b93f5c8853ff76f0 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 8 Jul 2023 15:11:26 -0300
Subject: [PATCH 05/55] [7.4.x] Add child modules as attributes of parent
 modules. (#11163)

* [7.4.x] Add child modules as attributes of parent modules.

* Update 10337.bugfix.rst

---------

Co-authored-by: akhilramkee <31619526+akhilramkee@users.noreply.github.com>
Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 changelog/10337.bugfix.rst |  2 ++
 src/_pytest/pathlib.py     | 14 +++++++++++++-
 testing/test_pathlib.py    | 12 ++++++++++++
 3 files changed, 27 insertions(+), 1 deletion(-)
 create mode 100644 changelog/10337.bugfix.rst

diff --git a/changelog/10337.bugfix.rst b/changelog/10337.bugfix.rst
new file mode 100644
index 00000000000..3270016f926
--- /dev/null
+++ b/changelog/10337.bugfix.rst
@@ -0,0 +1,2 @@
+Fixed bug where fake intermediate modules generated by ``--import-mode=importlib`` would not include the
+child modules as attributes of the parent modules.
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index e5fdd639a82..14fb2e3ae2f 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -635,6 +635,9 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
     otherwise "src.tests.test_foo" is not importable by ``__import__``.
     """
     module_parts = module_name.split(".")
+    child_module: Union[ModuleType, None] = None
+    module: Union[ModuleType, None] = None
+    child_name: str = ""
     while module_name:
         if module_name not in modules:
             try:
@@ -644,13 +647,22 @@ def insert_missing_modules(modules: Dict[str, ModuleType], module_name: str) ->
                 # ourselves to fall back to creating a dummy module.
                 if not sys.meta_path:
                     raise ModuleNotFoundError
-                importlib.import_module(module_name)
+                module = importlib.import_module(module_name)
             except ModuleNotFoundError:
                 module = ModuleType(
                     module_name,
                     doc="Empty module created by pytest's importmode=importlib.",
                 )
+        else:
+            module = modules[module_name]
+        if child_module:
+            # Add child attribute to the parent that can reference the child
+            # modules.
+            if not hasattr(module, child_name):
+                setattr(module, child_name, child_module)
                 modules[module_name] = module
+        # Keep track of the child module while moving up the tree.
+        child_module, child_name = module, module_name.rpartition(".")[-1]
         module_parts.pop(-1)
         module_name = ".".join(module_parts)
 
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 945d6f7d746..3d574e85658 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -603,3 +603,15 @@ def test_insert_missing_modules(
         modules = {}
         insert_missing_modules(modules, "")
         assert modules == {}
+
+    def test_parent_contains_child_module_attribute(
+        self, monkeypatch: MonkeyPatch, tmp_path: Path
+    ):
+        monkeypatch.chdir(tmp_path)
+        # Use 'xxx' and 'xxy' as parent names as they are unlikely to exist and
+        # don't end up being imported.
+        modules = {"xxx.tests.foo": ModuleType("xxx.tests.foo")}
+        insert_missing_modules(modules, "xxx.tests.foo")
+        assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
+        assert modules["xxx"].tests is modules["xxx.tests"]
+        assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]

From 511adf85beb87c96d6b0c9922a17e3acc4e8713f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 8 Jul 2023 18:37:35 +0000
Subject: [PATCH 06/55] [7.4.x] Fix error assertion handling in approx when
 None in dict comparison (#11180)

Co-authored-by: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
---
 changelog/10702.bugfix.rst |  1 +
 src/_pytest/python_api.py  | 25 +++++++++++++------------
 testing/python/approx.py   | 17 +++++++++++++++++
 3 files changed, 31 insertions(+), 12 deletions(-)
 create mode 100644 changelog/10702.bugfix.rst

diff --git a/changelog/10702.bugfix.rst b/changelog/10702.bugfix.rst
new file mode 100644
index 00000000000..4008cc882ec
--- /dev/null
+++ b/changelog/10702.bugfix.rst
@@ -0,0 +1 @@
+Fixed error assertion handling in :func:`pytest.approx` when ``None`` is an expected or received value when comparing dictionaries.
diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py
index 4213bd098c2..183356100c5 100644
--- a/src/_pytest/python_api.py
+++ b/src/_pytest/python_api.py
@@ -266,19 +266,20 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]:
             approx_side_as_map.items(), other_side.values()
         ):
             if approx_value != other_value:
-                max_abs_diff = max(
-                    max_abs_diff, abs(approx_value.expected - other_value)
-                )
-                if approx_value.expected == 0.0:
-                    max_rel_diff = math.inf
-                else:
-                    max_rel_diff = max(
-                        max_rel_diff,
-                        abs(
-                            (approx_value.expected - other_value)
-                            / approx_value.expected
-                        ),
+                if approx_value.expected is not None and other_value is not None:
+                    max_abs_diff = max(
+                        max_abs_diff, abs(approx_value.expected - other_value)
                     )
+                    if approx_value.expected == 0.0:
+                        max_rel_diff = math.inf
+                    else:
+                        max_rel_diff = max(
+                            max_rel_diff,
+                            abs(
+                                (approx_value.expected - other_value)
+                                / approx_value.expected
+                            ),
+                        )
                 different_ids.append(approx_key)
 
         message_data = [
diff --git a/testing/python/approx.py b/testing/python/approx.py
index 631e52b56ac..6ad411a3e07 100644
--- a/testing/python/approx.py
+++ b/testing/python/approx.py
@@ -122,6 +122,23 @@ def test_error_messages_native_dtypes(self, assert_approx_raises_regex):
             ],
         )
 
+        assert_approx_raises_regex(
+            {"a": 1.0, "b": None, "c": None},
+            {
+                "a": None,
+                "b": 1000.0,
+                "c": None,
+            },
+            [
+                r"  comparison failed. Mismatched elements: 2 / 3:",
+                r"  Max absolute difference: -inf",
+                r"  Max relative difference: -inf",
+                r"  Index \| Obtained\s+\| Expected\s+",
+                rf"  a     \| {SOME_FLOAT} \| None",
+                rf"  b     \| None\s+\| {SOME_FLOAT} ± {SOME_FLOAT}",
+            ],
+        )
+
         assert_approx_raises_regex(
             [1.0, 2.0, 3.0, 4.0],
             [1.0, 3.0, 3.0, 5.0],

From a566b78730c6ffad5bee5b41c8d615423a56e836 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 8 Jul 2023 19:07:55 +0000
Subject: [PATCH 07/55] [7.4.x] reference: improve the node types docs a bit
 (#11181)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 doc/en/reference/reference.rst | 138 ++++++++++++++++++---------------
 src/_pytest/main.py            |   5 ++
 src/_pytest/nodes.py           |  21 +++--
 src/_pytest/python.py          |  15 ++--
 4 files changed, 102 insertions(+), 77 deletions(-)

diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index 83bbccbcbdc..9e79397f09d 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -783,25 +783,17 @@ reporting or interaction with exceptions:
 .. autofunction:: pytest_leave_pdb
 
 
-Objects
--------
-
-Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`.
+Collection tree objects
+-----------------------
 
+These are the collector and item classes (collectively called "nodes") which
+make up the collection tree.
 
-CallInfo
-~~~~~~~~
-
-.. autoclass:: pytest.CallInfo()
-    :members:
-
-
-Class
-~~~~~
+Node
+~~~~
 
-.. autoclass:: pytest.Class()
+.. autoclass:: _pytest.nodes.Node()
     :members:
-    :show-inheritance:
 
 Collector
 ~~~~~~~~~
@@ -810,52 +802,52 @@ Collector
     :members:
     :show-inheritance:
 
-CollectReport
-~~~~~~~~~~~~~
+Item
+~~~~
 
-.. autoclass:: pytest.CollectReport()
+.. autoclass:: pytest.Item()
     :members:
     :show-inheritance:
-    :inherited-members:
 
-Config
-~~~~~~
+File
+~~~~
 
-.. autoclass:: pytest.Config()
+.. autoclass:: pytest.File()
     :members:
+    :show-inheritance:
 
-ExceptionInfo
-~~~~~~~~~~~~~
+FSCollector
+~~~~~~~~~~~
 
-.. autoclass:: pytest.ExceptionInfo()
+.. autoclass:: _pytest.nodes.FSCollector()
     :members:
+    :show-inheritance:
 
+Session
+~~~~~~~
 
-ExitCode
-~~~~~~~~
-
-.. autoclass:: pytest.ExitCode
+.. autoclass:: pytest.Session()
     :members:
+    :show-inheritance:
 
-File
-~~~~
+Package
+~~~~~~~
 
-.. autoclass:: pytest.File()
+.. autoclass:: pytest.Package()
     :members:
     :show-inheritance:
 
+Module
+~~~~~~
 
-FixtureDef
-~~~~~~~~~~
-
-.. autoclass:: _pytest.fixtures.FixtureDef()
+.. autoclass:: pytest.Module()
     :members:
     :show-inheritance:
 
-FSCollector
-~~~~~~~~~~~
+Class
+~~~~~
 
-.. autoclass:: _pytest.nodes.FSCollector()
+.. autoclass:: pytest.Class()
     :members:
     :show-inheritance:
 
@@ -873,10 +865,52 @@ FunctionDefinition
     :members:
     :show-inheritance:
 
-Item
-~~~~
 
-.. autoclass:: pytest.Item()
+Objects
+-------
+
+Objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`
+or importable from ``pytest``.
+
+
+CallInfo
+~~~~~~~~
+
+.. autoclass:: pytest.CallInfo()
+    :members:
+
+CollectReport
+~~~~~~~~~~~~~
+
+.. autoclass:: pytest.CollectReport()
+    :members:
+    :show-inheritance:
+    :inherited-members:
+
+Config
+~~~~~~
+
+.. autoclass:: pytest.Config()
+    :members:
+
+ExceptionInfo
+~~~~~~~~~~~~~
+
+.. autoclass:: pytest.ExceptionInfo()
+    :members:
+
+
+ExitCode
+~~~~~~~~
+
+.. autoclass:: pytest.ExitCode
+    :members:
+
+
+FixtureDef
+~~~~~~~~~~
+
+.. autoclass:: _pytest.fixtures.FixtureDef()
     :members:
     :show-inheritance:
 
@@ -907,19 +941,6 @@ Metafunc
 .. autoclass:: pytest.Metafunc()
     :members:
 
-Module
-~~~~~~
-
-.. autoclass:: pytest.Module()
-    :members:
-    :show-inheritance:
-
-Node
-~~~~
-
-.. autoclass:: _pytest.nodes.Node()
-    :members:
-
 Parser
 ~~~~~~
 
@@ -941,13 +962,6 @@ PytestPluginManager
     :inherited-members:
     :show-inheritance:
 
-Session
-~~~~~~~
-
-.. autoclass:: pytest.Session()
-    :members:
-    :show-inheritance:
-
 TestReport
 ~~~~~~~~~~
 
diff --git a/src/_pytest/main.py b/src/_pytest/main.py
index 155d4300e2c..803b95a2033 100644
--- a/src/_pytest/main.py
+++ b/src/_pytest/main.py
@@ -462,6 +462,11 @@ def __missing__(self, path: Path) -> str:
 
 @final
 class Session(nodes.FSCollector):
+    """The root of the collection tree.
+
+    ``Session`` collects the initial paths given as arguments to pytest.
+    """
+
     Interrupted = Interrupted
     Failed = Failed
     # Set on the session by runner.pytest_sessionstart.
diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py
index dbd6b0a4273..667a02b77af 100644
--- a/src/_pytest/nodes.py
+++ b/src/_pytest/nodes.py
@@ -157,10 +157,11 @@ def _create(self, *k, **kw):
 
 
 class Node(metaclass=NodeMeta):
-    """Base class for Collector and Item, the components of the test
-    collection tree.
+    r"""Base class of :class:`Collector` and :class:`Item`, the components of
+    the test collection tree.
 
-    Collector subclasses have children; Items are leaf nodes.
+    ``Collector``\'s are the internal nodes of the tree, and ``Item``\'s are the
+    leaf nodes.
     """
 
     # Implemented in the legacypath plugin.
@@ -525,15 +526,17 @@ def get_fslocation_from_item(node: "Node") -> Tuple[Union[str, Path], Optional[i
 
 
 class Collector(Node):
-    """Collector instances create children through collect() and thus
-    iteratively build a tree."""
+    """Base class of all collectors.
+
+    Collector create children through `collect()` and thus iteratively build
+    the collection tree.
+    """
 
     class CollectError(Exception):
         """An error during collection, contains a custom message."""
 
     def collect(self) -> Iterable[Union["Item", "Collector"]]:
-        """Return a list of children (items and collectors) for this
-        collection node."""
+        """Collect children (items and collectors) for this collector."""
         raise NotImplementedError("abstract")
 
     # TODO: This omits the style= parameter which breaks Liskov Substitution.
@@ -577,6 +580,8 @@ def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[
 
 
 class FSCollector(Collector):
+    """Base class for filesystem collectors."""
+
     def __init__(
         self,
         fspath: Optional[LEGACY_PATH] = None,
@@ -660,7 +665,7 @@ class File(FSCollector):
 
 
 class Item(Node):
-    """A basic test invocation item.
+    """Base class of all test invocation items.
 
     Note that for a single function there might be multiple test invocation items.
     """
diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index ad847c8afe2..b24dc90d8ee 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -522,7 +522,7 @@ def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
 
 
 class Module(nodes.File, PyCollector):
-    """Collector for test classes and functions."""
+    """Collector for test classes and functions in a Python module."""
 
     def _getobj(self):
         return self._importtestmodule()
@@ -659,6 +659,9 @@ def _importtestmodule(self):
 
 
 class Package(Module):
+    """Collector for files and directories in a Python packages -- directories
+    with an `__init__.py` file."""
+
     def __init__(
         self,
         fspath: Optional[LEGACY_PATH],
@@ -788,7 +791,7 @@ def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[o
 
 
 class Class(PyCollector):
-    """Collector for test methods."""
+    """Collector for test methods (and nested classes) in a Python class."""
 
     @classmethod
     def from_parent(cls, parent, *, name, obj=None, **kw):
@@ -1673,7 +1676,7 @@ def write_docstring(tw: TerminalWriter, doc: str, indent: str = "    ") -> None:
 
 
 class Function(PyobjMixin, nodes.Item):
-    """An Item responsible for setting up and executing a Python test function.
+    """Item responsible for setting up and executing a Python test function.
 
     :param name:
         The full function name, including any decorations like those
@@ -1830,10 +1833,8 @@ def repr_failure(  # type: ignore[override]
 
 
 class FunctionDefinition(Function):
-    """
-    This class is a step gap solution until we evolve to have actual function definition nodes
-    and manage to get rid of ``metafunc``.
-    """
+    """This class is a stop gap solution until we evolve to have actual function
+    definition nodes and manage to get rid of ``metafunc``."""
 
     def runtest(self) -> None:
         raise RuntimeError("function definitions are not supposed to be run as tests")

From 6dfe498c7747483b0e42694f1ae68cf7386c57f6 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 8 Jul 2023 19:17:21 +0000
Subject: [PATCH 08/55] [7.4.x] doc: fix EncodingWarnings in examples (#11182)

Co-authored-by: Ran Benita <ran@unusedvar.com>
---
 doc/en/example/nonpython/conftest.py | 2 +-
 doc/en/example/simple.rst            | 2 +-
 doc/en/how-to/fixtures.rst           | 2 +-
 doc/en/how-to/tmp_path.rst           | 4 ++--
 doc/en/how-to/unittest.rst           | 4 ++--
 5 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py
index dd1ebe88d7e..e969e3e2518 100644
--- a/doc/en/example/nonpython/conftest.py
+++ b/doc/en/example/nonpython/conftest.py
@@ -12,7 +12,7 @@ def collect(self):
         # We need a yaml parser, e.g. PyYAML.
         import yaml
 
-        raw = yaml.safe_load(self.path.open())
+        raw = yaml.safe_load(self.path.open(encoding="utf-8"))
         for name, spec in sorted(raw.items()):
             yield YamlItem.from_parent(self, name=name, spec=spec)
 
diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst
index 97a6dd9f436..32e5188b741 100644
--- a/doc/en/example/simple.rst
+++ b/doc/en/example/simple.rst
@@ -817,7 +817,7 @@ case we just write some information out to a ``failures`` file:
         # we only look at actual failing test calls, not setup/teardown
         if rep.when == "call" and rep.failed:
             mode = "a" if os.path.exists("failures") else "w"
-            with open("failures", mode) as f:
+            with open("failures", mode, encoding="utf-8") as f:
                 # let's also access a fixture for the fun of it
                 if "tmp_path" in item.fixturenames:
                     extra = " ({})".format(item.funcargs["tmp_path"])
diff --git a/doc/en/how-to/fixtures.rst b/doc/en/how-to/fixtures.rst
index d8517c2c8f3..a6f4962f083 100644
--- a/doc/en/how-to/fixtures.rst
+++ b/doc/en/how-to/fixtures.rst
@@ -1698,7 +1698,7 @@ and declare its use in a test module via a ``usefixtures`` marker:
     class TestDirectoryInit:
         def test_cwd_starts_empty(self):
             assert os.listdir(os.getcwd()) == []
-            with open("myfile", "w") as f:
+            with open("myfile", "w", encoding="utf-8") as f:
                 f.write("hello")
 
         def test_cwd_again_starts_empty(self):
diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst
index 792933dd87e..d5573f5847d 100644
--- a/doc/en/how-to/tmp_path.rst
+++ b/doc/en/how-to/tmp_path.rst
@@ -24,8 +24,8 @@ created in the `base temporary directory`_.
         d = tmp_path / "sub"
         d.mkdir()
         p = d / "hello.txt"
-        p.write_text(CONTENT)
-        assert p.read_text() == CONTENT
+        p.write_text(CONTENT, encoding="utf-8")
+        assert p.read_text(encoding="utf-8") == CONTENT
         assert len(list(tmp_path.iterdir())) == 1
         assert 0
 
diff --git a/doc/en/how-to/unittest.rst b/doc/en/how-to/unittest.rst
index 37caf6e9fb7..7856c1a49c0 100644
--- a/doc/en/how-to/unittest.rst
+++ b/doc/en/how-to/unittest.rst
@@ -207,10 +207,10 @@ creation of a per-test temporary directory:
         @pytest.fixture(autouse=True)
         def initdir(self, tmp_path, monkeypatch):
             monkeypatch.chdir(tmp_path)  # change to pytest-provided temporary directory
-            tmp_path.joinpath("samplefile.ini").write_text("# testdata")
+            tmp_path.joinpath("samplefile.ini").write_text("# testdata", encoding="utf-8")
 
         def test_method(self):
-            with open("samplefile.ini") as f:
+            with open("samplefile.ini", encoding="utf-8") as f:
                 s = f.read()
             assert "testdata" in s
 

From 06ff7ca13bfe7f860110a829ed48f3d427e6502c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 9 Jul 2023 15:54:59 +0000
Subject: [PATCH 09/55] [7.4.x] Clarify docs for pytest.main default behavior
 (#11188)

Co-authored-by: antosikv <79337398+antosikv@users.noreply.github.com>
---
 doc/en/how-to/usage.rst        | 3 ++-
 src/_pytest/config/__init__.py | 4 +++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/doc/en/how-to/usage.rst b/doc/en/how-to/usage.rst
index 8e2dd86736f..4ec4459a232 100644
--- a/doc/en/how-to/usage.rst
+++ b/doc/en/how-to/usage.rst
@@ -173,7 +173,8 @@ You can invoke ``pytest`` from Python code directly:
 
 this acts as if you would call "pytest" from the command line.
 It will not raise :class:`SystemExit` but return the :ref:`exit code <exit-codes>` instead.
-You can pass in options and arguments:
+If you don't pass it any arguments, ``main`` reads the arguments from the command line arguments of the process (:data:`sys.argv`), which may be undesirable.
+You can pass in options and arguments explicitly:
 
 .. code-block:: python
 
diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py
index c9a4b7f63cb..45f4cf831ff 100644
--- a/src/_pytest/config/__init__.py
+++ b/src/_pytest/config/__init__.py
@@ -137,7 +137,9 @@ def main(
 ) -> Union[int, ExitCode]:
     """Perform an in-process test run.
 
-    :param args: List of command line arguments.
+    :param args:
+        List of command line arguments. If `None` or not given, defaults to reading
+        arguments directly from the process command line (:data:`sys.argv`).
     :param plugins: List of plugin objects to be auto-registered during initialization.
 
     :returns: An exit code.

From 350122abb2674f98144b9267b195f3e84a032e66 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 22 Jul 2023 18:40:56 +0000
Subject: [PATCH 10/55] [7.4.x] Remove ep2023 training (#11242)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
---
 doc/en/index.rst | 1 -
 1 file changed, 1 deletion(-)

diff --git a/doc/en/index.rst b/doc/en/index.rst
index 50f3e9c10c3..69ca1dcfea9 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -2,7 +2,6 @@
 
 .. sidebar:: Next Open Trainings
 
-   - `pytest tips and tricks for a better testsuite <https://ep2023.europython.eu/session/pytest-tips-and-tricks-for-a-better-testsuite>`_, at `Europython 2023 <https://ep2023.europython.eu/>`_, **July 18th** (3h), **Prague, Czech Republic / Remote**
    - `pytest: Professionelles Testen (nicht nur) für Python <https://workshoptage.ch/workshops/2023/pytest-professionelles-testen-nicht-nur-fuer-python-2/>`_, at `Workshoptage 2023 <https://workshoptage.ch/>`_, **September 5th**, `OST <https://www.ost.ch/en>`_ Campus **Rapperswil, Switzerland**
    - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, **March 5th to 7th 2024** (3 day in-depth training), **Leipzig, Germany / Remote**
 

From 34c73944e1d8e89a802a82a71ed75efcd1ee6700 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 7 Aug 2023 10:47:12 +0200
Subject: [PATCH 11/55] [7.4.x] doc: update information about assertion
 messages (#11286)

Co-authored-by: Christoph Anton Mitterer <mail@christoph.anton.mitterer.name>
---
 doc/en/how-to/assert.rst | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/doc/en/how-to/assert.rst b/doc/en/how-to/assert.rst
index 1b10c131389..d99a1ce5c97 100644
--- a/doc/en/how-to/assert.rst
+++ b/doc/en/how-to/assert.rst
@@ -54,14 +54,13 @@ operators. (See :ref:`tbreportdemo`).  This allows you to use the
 idiomatic python constructs without boilerplate code while not losing
 introspection information.
 
-However, if you specify a message with the assertion like this:
+If a message is specified with the assertion like this:
 
 .. code-block:: python
 
     assert a % 2 == 0, "value was odd, should be even"
 
-then no assertion introspection takes places at all and the message
-will be simply shown in the traceback.
+it is printed alongside the assertion introspection in the traceback.
 
 See :ref:`assert-details` for more information on assertion introspection.
 

From e3fe7286f803cff00314b64ffaaedd5cfc9a6b8f Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 7 Aug 2023 10:47:37 +0200
Subject: [PATCH 12/55] [7.4.x] doc: Link pytest.main to how-to guide (#11290)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
---
 doc/en/reference/reference.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index 9e79397f09d..767f8a8f3d3 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -82,6 +82,8 @@ pytest.exit
 pytest.main
 ~~~~~~~~~~~
 
+**Tutorial**: :ref:`pytest.main-usage`
+
 .. autofunction:: pytest.main
 
 pytest.param

From ba40975bb72e8b2ba749a06ccffef42638f8800a Mon Sep 17 00:00:00 2001
From: Florian Bruhin <me@the-compiler.org>
Date: Mon, 7 Aug 2023 12:08:56 +0200
Subject: [PATCH 13/55] ci: Use Python 3.8 to test latest pluggy

Pluggy dropped Python 3.7 support.
Also see 165fbbd12a74ab639d61ce1f28dfef1511a2c2e2

Fixes #11293
---
 .github/workflows/test.yml | 20 ++++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index cf5027223e1..eb2dfa85d26 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -38,17 +38,17 @@ jobs:
       matrix:
         name: [
           "windows-py37",
-          "windows-py37-pluggy",
           "windows-py38",
+          "windows-py38-pluggy",
           "windows-py39",
           "windows-py310",
           "windows-py311",
           "windows-py312",
 
           "ubuntu-py37",
-          "ubuntu-py37-pluggy",
           "ubuntu-py37-freeze",
           "ubuntu-py38",
+          "ubuntu-py38-pluggy",
           "ubuntu-py39",
           "ubuntu-py310",
           "ubuntu-py311",
@@ -70,15 +70,15 @@ jobs:
             python: "3.7"
             os: windows-latest
             tox_env: "py37-numpy"
-          - name: "windows-py37-pluggy"
-            python: "3.7"
-            os: windows-latest
-            tox_env: "py37-pluggymain-pylib-xdist"
           - name: "windows-py38"
             python: "3.8"
             os: windows-latest
             tox_env: "py38-unittestextras"
             use_coverage: true
+          - name: "windows-py38-pluggy"
+            python: "3.8"
+            os: windows-latest
+            tox_env: "py38-pluggymain-pylib-xdist"
           - name: "windows-py39"
             python: "3.9"
             os: windows-latest
@@ -101,10 +101,6 @@ jobs:
             os: ubuntu-latest
             tox_env: "py37-lsof-numpy-pexpect"
             use_coverage: true
-          - name: "ubuntu-py37-pluggy"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "py37-pluggymain-pylib-xdist"
           - name: "ubuntu-py37-freeze"
             python: "3.7"
             os: ubuntu-latest
@@ -113,6 +109,10 @@ jobs:
             python: "3.8"
             os: ubuntu-latest
             tox_env: "py38-xdist"
+          - name: "ubuntu-py38-pluggy"
+            python: "3.8"
+            os: ubuntu-latest
+            tox_env: "py38-pluggymain-pylib-xdist"
           - name: "ubuntu-py39"
             python: "3.9"
             os: ubuntu-latest

From 69140717d468cf93750c3f54254ca960455bfd1a Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 8 Aug 2023 14:44:03 +0300
Subject: [PATCH 14/55] [7.4.x] Improve duplicate values documentation (#11296)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 src/_pytest/python.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/_pytest/python.py b/src/_pytest/python.py
index b24dc90d8ee..5f8be5d9b9e 100644
--- a/src/_pytest/python.py
+++ b/src/_pytest/python.py
@@ -1152,7 +1152,7 @@ def setmulti(
         arg2scope = self._arg2scope.copy()
         for arg, val in zip(argnames, valset):
             if arg in params or arg in funcargs:
-                raise ValueError(f"duplicate {arg!r}")
+                raise ValueError(f"duplicate parametrization of {arg!r}")
             valtype_for_arg = valtypes[arg]
             if valtype_for_arg == "params":
                 params[arg] = val
@@ -1243,8 +1243,9 @@ def parametrize(
         during the collection phase. If you need to setup expensive resources
         see about setting indirect to do it rather than at test setup time.
 
-        Can be called multiple times, in which case each call parametrizes all
-        previous parametrizations, e.g.
+        Can be called multiple times per test function (but only on different
+        argument names), in which case each call parametrizes all previous
+        parametrizations, e.g.
 
         ::
 

From 7a5f2feefbbb976b1c5b4ced18e7e6725edfd3d5 Mon Sep 17 00:00:00 2001
From: Ran Benita <ran@unusedvar.com>
Date: Sun, 27 Aug 2023 01:15:32 +0300
Subject: [PATCH 15/55] [7.4.x] Fixes for typed pluggy (#11355)

Since version 1.3 pluggy added typing, which requires some fixes to
please mypy.
---
 doc/en/reference/reference.rst |  4 ++--
 src/_pytest/config/__init__.py | 13 +++++++------
 src/_pytest/helpconfig.py      |  6 +++++-
 src/_pytest/logging.py         |  2 ++
 src/_pytest/pytester.py        |  2 +-
 testing/test_pluginmanager.py  | 10 ++++++++--
 6 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index 767f8a8f3d3..38b58b5c4c7 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -978,10 +978,10 @@ TestShortLogReport
 .. autoclass:: pytest.TestShortLogReport()
     :members:
 
-_Result
+Result
 ~~~~~~~
 
-Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`_Result in the pluggy documentation <pluggy._callers._Result>` for more information.
+Result object used within :ref:`hook wrappers <hookwrapper>`, see :py:class:`Result in the pluggy documentation <pluggy.Result>` for more information.
 
 Stash
 ~~~~~
diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py
index 45f4cf831ff..dc2b9f6a160 100644
--- a/src/_pytest/config/__init__.py
+++ b/src/_pytest/config/__init__.py
@@ -444,10 +444,10 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
         # so we avoid accessing possibly non-readable attributes
         # (see issue #1073).
         if not name.startswith("pytest_"):
-            return
+            return None
         # Ignore names which can not be hooks.
         if name == "pytest_plugins":
-            return
+            return None
 
         opts = super().parse_hookimpl_opts(plugin, name)
         if opts is not None:
@@ -456,9 +456,9 @@ def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str):
         method = getattr(plugin, name)
         # Consider only actual functions for hooks (#3775).
         if not inspect.isroutine(method):
-            return
+            return None
         # Collect unmarked hooks as long as they have the `pytest_' prefix.
-        return _get_legacy_hook_marks(
+        return _get_legacy_hook_marks(  # type: ignore[return-value]
             method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper")
         )
 
@@ -467,7 +467,7 @@ def parse_hookspec_opts(self, module_or_class, name: str):
         if opts is None:
             method = getattr(module_or_class, name)
             if name.startswith("pytest_"):
-                opts = _get_legacy_hook_marks(
+                opts = _get_legacy_hook_marks(  # type: ignore[assignment]
                     method,
                     "spec",
                     ("firstresult", "historic"),
@@ -1065,9 +1065,10 @@ def _ensure_unconfigure(self) -> None:
             fin()
 
     def get_terminal_writer(self) -> TerminalWriter:
-        terminalreporter: TerminalReporter = self.pluginmanager.get_plugin(
+        terminalreporter: Optional[TerminalReporter] = self.pluginmanager.get_plugin(
             "terminalreporter"
         )
+        assert terminalreporter is not None
         return terminalreporter._tw
 
     def pytest_cmdline_parse(
diff --git a/src/_pytest/helpconfig.py b/src/_pytest/helpconfig.py
index 430870608bd..ea16c438823 100644
--- a/src/_pytest/helpconfig.py
+++ b/src/_pytest/helpconfig.py
@@ -11,6 +11,7 @@
 from _pytest.config import ExitCode
 from _pytest.config import PrintHelp
 from _pytest.config.argparsing import Parser
+from _pytest.terminal import TerminalReporter
 
 
 class HelpAction(Action):
@@ -159,7 +160,10 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
 def showhelp(config: Config) -> None:
     import textwrap
 
-    reporter = config.pluginmanager.get_plugin("terminalreporter")
+    reporter: Optional[TerminalReporter] = config.pluginmanager.get_plugin(
+        "terminalreporter"
+    )
+    assert reporter is not None
     tw = reporter._tw
     tw.write(config._parser.optparser.format_help())
     tw.line()
diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py
index 83813466016..9f2f1c79359 100644
--- a/src/_pytest/logging.py
+++ b/src/_pytest/logging.py
@@ -660,6 +660,8 @@ def __init__(self, config: Config) -> None:
         )
         if self._log_cli_enabled():
             terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
+            # Guaranteed by `_log_cli_enabled()`.
+            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[
diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py
index 3df52ebe88c..cdfc2c04ae1 100644
--- a/src/_pytest/pytester.py
+++ b/src/_pytest/pytester.py
@@ -752,7 +752,7 @@ def preserve_module(name):
 
     def make_hook_recorder(self, pluginmanager: PytestPluginManager) -> HookRecorder:
         """Create a new :class:`HookRecorder` for a :class:`PytestPluginManager`."""
-        pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)
+        pluginmanager.reprec = reprec = HookRecorder(pluginmanager, _ispytest=True)  # type: ignore[attr-defined]
         self._request.addfinalizer(reprec.finish_recording)
         return reprec
 
diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py
index c6f518b1da2..e5773412fbf 100644
--- a/testing/test_pluginmanager.py
+++ b/testing/test_pluginmanager.py
@@ -242,8 +242,12 @@ def test_consider_module(
         mod = types.ModuleType("temp")
         mod.__dict__["pytest_plugins"] = ["pytest_p1", "pytest_p2"]
         pytestpm.consider_module(mod)
-        assert pytestpm.get_plugin("pytest_p1").__name__ == "pytest_p1"
-        assert pytestpm.get_plugin("pytest_p2").__name__ == "pytest_p2"
+        p1 = pytestpm.get_plugin("pytest_p1")
+        assert p1 is not None
+        assert p1.__name__ == "pytest_p1"
+        p2 = pytestpm.get_plugin("pytest_p2")
+        assert p2 is not None
+        assert p2.__name__ == "pytest_p2"
 
     def test_consider_module_import_module(
         self, pytester: Pytester, _config_for_test: Config
@@ -336,6 +340,7 @@ def test_import_plugin_importname(
         len2 = len(pytestpm.get_plugins())
         assert len1 == len2
         plugin1 = pytestpm.get_plugin("pytest_hello")
+        assert plugin1 is not None
         assert plugin1.__name__.endswith("pytest_hello")
         plugin2 = pytestpm.get_plugin("pytest_hello")
         assert plugin2 is plugin1
@@ -351,6 +356,7 @@ def test_import_plugin_dotted_name(
         pluginname = "pkg.plug"
         pytestpm.import_plugin(pluginname)
         mod = pytestpm.get_plugin("pkg.plug")
+        assert mod is not None
         assert mod.x == 3
 
     def test_consider_conftest_deps(

From b170081788ad51a2ccf5d47205e69f98bf6f574d Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 29 Aug 2023 00:40:49 +0000
Subject: [PATCH 16/55] [7.4.x] Issue 11354 fixing docs for lfnf (#11364)

Co-authored-by: Sean Patrick Malloy <spmalloy@ucdavis.edu>
---
 doc/en/how-to/cache.rst        | 17 ++++++++++++-----
 doc/en/reference/reference.rst |  7 +++++--
 src/_pytest/cacheprovider.py   |  6 +++++-
 3 files changed, 22 insertions(+), 8 deletions(-)

diff --git a/doc/en/how-to/cache.rst b/doc/en/how-to/cache.rst
index 8554a984c72..03ab0c77780 100644
--- a/doc/en/how-to/cache.rst
+++ b/doc/en/how-to/cache.rst
@@ -176,14 +176,21 @@ with more recent files coming first.
 Behavior when no tests failed in the last run
 ---------------------------------------------
 
-When no tests failed in the last run, or when no cached ``lastfailed`` data was
-found, ``pytest`` can be configured either to run all of the tests or no tests,
-using the ``--last-failed-no-failures`` option, which takes one of the following values:
+The ``--lfnf/--last-failed-no-failures`` option governs the behavior of ``--last-failed``.
+Determines whether to execute tests when there are no previously (known)
+failures or when no cached ``lastfailed`` data was found.
+
+There are two options:
+
+* ``all``:  when there are no known test failures, runs all tests (the full test suite). This is the default.
+* ``none``: when there are no known test failures, just emits a message stating this and exit successfully.
+
+Example:
 
 .. code-block:: bash
 
-    pytest --last-failed --last-failed-no-failures all    # run all tests (default behavior)
-    pytest --last-failed --last-failed-no-failures none   # run no tests and exit
+    pytest --last-failed --last-failed-no-failures all    # runs the full test suite (default behavior)
+    pytest --last-failed --last-failed-no-failures none   # runs no tests and exits successfully
 
 The new config.cache object
 --------------------------------
diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index 38b58b5c4c7..ef7e19c03d0 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -1887,8 +1887,11 @@ All the command-line flags can be obtained by running ``pytest --help``::
                             tests. Optional argument: glob (default: '*').
       --cache-clear         Remove all cache contents at start of test run
       --lfnf={all,none}, --last-failed-no-failures={all,none}
-                            Which tests to run with no previously (known)
-                            failures
+                            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
diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py
index 855716d8199..1ecb8650580 100755
--- a/src/_pytest/cacheprovider.py
+++ b/src/_pytest/cacheprovider.py
@@ -505,7 +505,11 @@ def pytest_addoption(parser: Parser) -> None:
         dest="last_failed_no_failures",
         choices=("all", "none"),
         default="all",
-        help="Which tests to run with no previously (known) failures",
+        help="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.",
     )
 
 

From fbcfd3a52e1c64349e143aa8b146ac1a71340c68 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 30 Aug 2023 08:57:49 -0300
Subject: [PATCH 17/55] [7.4.x] Update CONTRIBUTING.rst (#11371)

Co-authored-by: Sourabh Beniwal <sourabhbeniwal@outlook.com>
---
 CONTRIBUTING.rst | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
index 791f988306f..5ccc66bf5c4 100644
--- a/CONTRIBUTING.rst
+++ b/CONTRIBUTING.rst
@@ -50,7 +50,7 @@ Fix bugs
 --------
 
 Look through the `GitHub issues for bugs <https://github.com/pytest-dev/pytest/labels/type:%20bug>`_.
-See also the `"status: easy" issues <https://github.com/pytest-dev/pytest/labels/status%3A%20easy>`_
+See also the `"good first issue" issues <https://github.com/pytest-dev/pytest/labels/good%20first%20issue>`_
 that are friendly to new contributors.
 
 :ref:`Talk <contact>` to developers to find out how you can fix specific bugs. To indicate that you are going

From 7a0a0e8b0857a7194001f4860af3fbc0968765ff Mon Sep 17 00:00:00 2001
From: pytest bot <pytestbot@gmail.com>
Date: Sat, 2 Sep 2023 11:03:06 +0000
Subject: [PATCH 18/55] Prepare release version 7.4.1

---
 changelog/10337.bugfix.rst        |  2 --
 changelog/10702.bugfix.rst        |  1 -
 changelog/10811.bugfix.rst        |  2 --
 doc/en/announce/index.rst         |  1 +
 doc/en/announce/release-7.4.1.rst | 20 ++++++++++++++++++++
 doc/en/builtin.rst                |  2 +-
 doc/en/changelog.rst              | 17 +++++++++++++++++
 doc/en/example/reportingdemo.rst  | 20 ++++++++++----------
 doc/en/getting-started.rst        |  2 +-
 doc/en/how-to/tmp_path.rst        |  4 ++--
 doc/en/reference/reference.rst    | 11 ++++++-----
 11 files changed, 58 insertions(+), 24 deletions(-)
 delete mode 100644 changelog/10337.bugfix.rst
 delete mode 100644 changelog/10702.bugfix.rst
 delete mode 100644 changelog/10811.bugfix.rst
 create mode 100644 doc/en/announce/release-7.4.1.rst

diff --git a/changelog/10337.bugfix.rst b/changelog/10337.bugfix.rst
deleted file mode 100644
index 3270016f926..00000000000
--- a/changelog/10337.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fixed bug where fake intermediate modules generated by ``--import-mode=importlib`` would not include the
-child modules as attributes of the parent modules.
diff --git a/changelog/10702.bugfix.rst b/changelog/10702.bugfix.rst
deleted file mode 100644
index 4008cc882ec..00000000000
--- a/changelog/10702.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed error assertion handling in :func:`pytest.approx` when ``None`` is an expected or received value when comparing dictionaries.
diff --git a/changelog/10811.bugfix.rst b/changelog/10811.bugfix.rst
deleted file mode 100644
index aa26414e457..00000000000
--- a/changelog/10811.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-Fixed issue when using ``--import-mode=importlib`` together with ``--doctest-modules`` that caused modules
-to be imported more than once, causing problems with modules that have import side effects.
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index 914e763bd9e..85dfa0894fe 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,7 @@ Release announcements
    :maxdepth: 2
 
 
+   release-7.4.1
    release-7.4.0
    release-7.3.2
    release-7.3.1
diff --git a/doc/en/announce/release-7.4.1.rst b/doc/en/announce/release-7.4.1.rst
new file mode 100644
index 00000000000..efadcf919e8
--- /dev/null
+++ b/doc/en/announce/release-7.4.1.rst
@@ -0,0 +1,20 @@
+pytest-7.4.1
+=======================================
+
+pytest 7.4.1 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Florian Bruhin
+* Ran Benita
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst
index 53305eecded..0d673d0426e 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:528
+    cache -- .../_pytest/cacheprovider.py:532
         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 391721df34d..be7e7fabaef 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -28,6 +28,23 @@ with advance notice in the **Deprecations** section of releases.
 
 .. towncrier release notes start
 
+pytest 7.4.1 (2023-09-02)
+=========================
+
+Bug Fixes
+---------
+
+- `#10337 <https://github.com/pytest-dev/pytest/issues/10337>`_: Fixed bug where fake intermediate modules generated by ``--import-mode=importlib`` would not include the
+  child modules as attributes of the parent modules.
+
+
+- `#10702 <https://github.com/pytest-dev/pytest/issues/10702>`_: Fixed error assertion handling in :func:`pytest.approx` when ``None`` is an expected or received value when comparing dictionaries.
+
+
+- `#10811 <https://github.com/pytest-dev/pytest/issues/10811>`_: Fixed issue when using ``--import-mode=importlib`` together with ``--doctest-modules`` that caused modules
+  to be imported more than once, causing problems with modules that have import side effects.
+
+
 pytest 7.4.0 (2023-06-23)
 =========================
 
diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst
index cb59c4b42e1..d4d3d3ce202 100644
--- a/doc/en/example/reportingdemo.rst
+++ b/doc/en/example/reportingdemo.rst
@@ -554,13 +554,13 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       AssertionError: assert False
     E        +  where False = <built-in method startswith of str object at 0xdeadbeef0027>('456')
     E        +    where <built-in method startswith of str object at 0xdeadbeef0027> = '123'.startswith
-    E        +      where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0029>()
-    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef002a>()
+    E        +      where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0006>()
+    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef0029>()
 
     failure_demo.py:235: AssertionError
     _____________________ TestMoreErrors.test_global_func ______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002a>
 
         def test_global_func(self):
     >       assert isinstance(globf(42), float)
@@ -571,18 +571,18 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:238: AssertionError
     _______________________ TestMoreErrors.test_instance _______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
 
         def test_instance(self):
             self.x = 6 * 7
     >       assert self.x != 42
     E       assert 42 != 42
-    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>.x
+    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>.x
 
     failure_demo.py:242: AssertionError
     _______________________ TestMoreErrors.test_compare ________________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
 
         def test_compare(self):
     >       assert globf(10) < 5
@@ -592,7 +592,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:245: AssertionError
     _____________________ TestMoreErrors.test_try_finally ______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002e>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
 
         def test_try_finally(self):
             x = 1
@@ -603,7 +603,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:250: AssertionError
     ___________________ TestCustomAssertMsg.test_single_line ___________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002e>
 
         def test_single_line(self):
             class A:
@@ -618,7 +618,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:261: AssertionError
     ____________________ TestCustomAssertMsg.test_multiline ____________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
 
         def test_multiline(self):
             class A:
@@ -637,7 +637,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:268: AssertionError
     ___________________ TestCustomAssertMsg.test_custom_repr ___________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0031>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
 
         def test_custom_repr(self):
             class JSON:
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index e295c180454..31cfa68bf38 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -22,7 +22,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 7.4.0
+    pytest 7.4.1
 
 .. _`simpletest`:
 
diff --git a/doc/en/how-to/tmp_path.rst b/doc/en/how-to/tmp_path.rst
index d5573f5847d..3b49d63a5be 100644
--- a/doc/en/how-to/tmp_path.rst
+++ b/doc/en/how-to/tmp_path.rst
@@ -51,8 +51,8 @@ Running this would result in a passed test except for the last
             d = tmp_path / "sub"
             d.mkdir()
             p = d / "hello.txt"
-            p.write_text(CONTENT)
-            assert p.read_text() == CONTENT
+            p.write_text(CONTENT, encoding="utf-8")
+            assert p.read_text(encoding="utf-8") == CONTENT
             assert len(list(tmp_path.iterdir())) == 1
     >       assert 0
     E       assert 0
diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index ef7e19c03d0..6e11d385d12 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -1887,11 +1887,12 @@ All the command-line flags can be obtained by running ``pytest --help``::
                             tests. Optional argument: glob (default: '*').
       --cache-clear         Remove all cache contents at start of test run
       --lfnf={all,none}, --last-failed-no-failures={all,none}
-                            With ``--lf``, determines whether to execute tests when there
-                            are no previously (known) failures or when no
-                            cached ``lastfailed`` data was found.
-                            ``all`` (the default) runs the full test suite again.
-                            ``none`` just emits a message about no known failures and exits successfully.
+                            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

From 7855a72d2c5c810a025460f135ffdd77160bb091 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <bruno@soliv.dev>
Date: Sat, 2 Sep 2023 08:13:00 -0300
Subject: [PATCH 19/55] Improve CI workflow

* Build the package only once, and test on all platforms.
* Deploy is now triggered manually via an Action, which is then responsible for tagging the repository after the package has been uploaded successfully.
* Drop 'docs': we nowadays rely on readthedocs preview PR builds.
---
 .github/workflows/deploy.yml | 33 +++++++++++++++++++++++----------
 .github/workflows/test.yml   | 36 ++++++++++++++++++++++--------------
 RELEASING.rst                | 11 ++++-------
 3 files changed, 49 insertions(+), 31 deletions(-)

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index e6b4f0ea2d7..34145271c34 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -1,33 +1,38 @@
 name: deploy
 
 on:
-  push:
-    tags:
-      # These tags are protected, see:
-      # https://github.com/pytest-dev/pytest/settings/tag_protection
-      - "[0-9]+.[0-9]+.[0-9]+"
-      - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
+  workflow_dispatch:
+    inputs:
+      version:
+        description: 'Release version'
+        required: true
+        default: '1.2.3'
 
 
 # Set permissions at the job level.
 permissions: {}
 
 jobs:
-  build:
+  package:
     runs-on: ubuntu-latest
+    env:
+      SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }}
     timeout-minutes: 10
+
     steps:
     - uses: actions/checkout@v3
       with:
         fetch-depth: 0
         persist-credentials: false
+
     - name: Build and Check Package
       uses: hynek/build-and-inspect-python-package@v1.5
 
   deploy:
     if: github.repository == 'pytest-dev/pytest'
-    needs: [build]
+    needs: [package]
     runs-on: ubuntu-latest
+    environment: deploy
     timeout-minutes: 30
     permissions:
       id-token: write
@@ -37,9 +42,17 @@ jobs:
       with:
         name: Packages
         path: dist
+
     - name: Publish package to PyPI
       uses: pypa/gh-action-pypi-publish@v1.8.5
 
+    - name: Push tag
+      run: |
+        git config user.name "pytest bot"
+        git config user.email "pytestbot@gmail.com"
+        git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }}
+        git push origin v${{ github.event.inputs.version }}
+
   release-notes:
 
     # todo: generate the content in the build  job
@@ -55,11 +68,11 @@ jobs:
       with:
         fetch-depth: 0
         persist-credentials: false
+
     - name: Set up Python
       uses: actions/setup-python@v4
       with:
-        python-version: "3.7"
-
+        python-version: "3.10"
 
     - name: Install tox
       run: |
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index eb2dfa85d26..a3c2d9ed5a1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -27,7 +27,19 @@ concurrency:
 permissions: {}
 
 jobs:
+  package:
+    runs-on: ubuntu-latest
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        fetch-depth: 0
+        persist-credentials: false
+    - name: Build and Check Package
+      uses: hynek/build-and-inspect-python-package@v1.5
+
   build:
+    needs: [package]
+
     runs-on: ${{ matrix.os }}
     timeout-minutes: 45
     permissions:
@@ -60,7 +72,6 @@ jobs:
           "macos-py310",
           "macos-py312",
 
-          "docs",
           "doctesting",
           "plugins",
         ]
@@ -159,10 +170,6 @@ jobs:
             os: ubuntu-latest
             tox_env: "plugins"
 
-          - name: "docs"
-            python: "3.7"
-            os: ubuntu-latest
-            tox_env: "docs"
           - name: "doctesting"
             python: "3.7"
             os: ubuntu-latest
@@ -175,6 +182,12 @@ jobs:
         fetch-depth: 0
         persist-credentials: false
 
+    - name: Download Package
+      uses: actions/download-artifact@v3
+      with:
+        name: Packages
+        path: dist
+
     - name: Set up Python ${{ matrix.python }}
       uses: actions/setup-python@v4
       with:
@@ -188,11 +201,13 @@ jobs:
 
     - name: Test without coverage
       if: "! matrix.use_coverage"
-      run: "tox -e ${{ matrix.tox_env }}"
+      shell: bash
+      run: tox run -e ${{ matrix.tox_env }} --installpkg `find dist/*.tar.gz`
 
     - name: Test with coverage
       if: "matrix.use_coverage"
-      run: "tox -e ${{ matrix.tox_env }}-coverage"
+      shell: bash
+      run: tox run -e ${{ matrix.tox_env }}-coverage --installpkg `find dist/*.tar.gz`
 
     - name: Generate coverage report
       if: "matrix.use_coverage"
@@ -206,10 +221,3 @@ jobs:
         fail_ci_if_error: true
         files: ./coverage.xml
         verbose: true
-
-  check-package:
-    runs-on: ubuntu-latest
-    steps:
-    - uses: actions/checkout@v3
-    - name: Build and Check Package
-      uses: hynek/build-and-inspect-python-package@v1.5
diff --git a/RELEASING.rst b/RELEASING.rst
index b018dc48932..5d49fb5d6d9 100644
--- a/RELEASING.rst
+++ b/RELEASING.rst
@@ -133,14 +133,11 @@ Releasing
 
 Both automatic and manual processes described above follow the same steps from this point onward.
 
-#. After all tests pass and the PR has been approved, tag the release commit
-   in the ``release-MAJOR.MINOR.PATCH`` branch and push it. This will publish to PyPI::
+#. After all tests pass and the PR has been approved, trigger the ``deploy`` job
+   in https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml.
 
-     git fetch upstream
-     git tag MAJOR.MINOR.PATCH upstream/release-MAJOR.MINOR.PATCH
-     git push upstream MAJOR.MINOR.PATCH
-
-   Wait for the deploy to complete, then make sure it is `available on PyPI <https://pypi.org/project/pytest>`_.
+   This job will require approval from ``pytest-dev/core``, after which it will publish to PyPI
+   and tag the repository.
 
 #. Merge the PR. **Make sure it's not squash-merged**, so that the tagged commit ends up in the main branch.
 

From 0319a0d4fd9ef432a52cfeed69d32f86e6aa9b31 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <bruno@soliv.dev>
Date: Sat, 2 Sep 2023 12:38:39 -0300
Subject: [PATCH 20/55] Checkout source code during deploy

We need the checked out repository in order to push the tag.
---
 .github/workflows/deploy.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 34145271c34..4cda08650ad 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -37,6 +37,7 @@ jobs:
     permissions:
       id-token: write
     steps:
+    - uses: actions/checkout@v3
     - name: Download Package
       uses: actions/download-artifact@v3
       with:

From 7f5d9b9df4e5e323fe3a66aeb956557710e5d277 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Sun, 3 Sep 2023 15:01:56 -0300
Subject: [PATCH 21/55] Fix user_properties not saved to XML if fixture errors
 during teardown (#11382)

Move handling of user_properties to `finalize()`.

Previously if a fixture failed during teardown, `pytest_runtest_logreport` would not be called with "teardown", resulting in the user properties not being saved on the JUnit XML file.

Fixes: #11367
(cherry picked from commit 917ce9aa0102c7f0ec8fdac118058c41ffb603e6)

Co-authored-by: Israel Fruchter <israel.fruchter@gmail.com>
---
 AUTHORS                    |  2 ++
 changelog/11367.bugfix.rst |  1 +
 src/_pytest/junitxml.py    |  7 ++++---
 testing/test_junitxml.py   | 30 ++++++++++++++++++++++++++++++
 4 files changed, 37 insertions(+), 3 deletions(-)
 create mode 100644 changelog/11367.bugfix.rst

diff --git a/AUTHORS b/AUTHORS
index ee4ef203f0c..d914f3ba906 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -166,6 +166,8 @@ Ian Bicking
 Ian Lesperance
 Ilya Konstantinov
 Ionuț Turturică
+Isaac Virshup
+Israel Fruchter
 Itxaso Aizpurua
 Iwan Briquemont
 Jaap Broekhuizen
diff --git a/changelog/11367.bugfix.rst b/changelog/11367.bugfix.rst
new file mode 100644
index 00000000000..dda40db0fc9
--- /dev/null
+++ b/changelog/11367.bugfix.rst
@@ -0,0 +1 @@
+Fixed bug where `user_properties` where not being saved in the JUnit XML file if a fixture failed during teardown.
diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py
index 9242d46d9df..ed259e4c41d 100644
--- a/src/_pytest/junitxml.py
+++ b/src/_pytest/junitxml.py
@@ -502,6 +502,10 @@ def finalize(self, report: TestReport) -> None:
         # Local hack to handle xdist report order.
         workernode = getattr(report, "node", None)
         reporter = self.node_reporters.pop((nodeid, workernode))
+
+        for propname, propvalue in report.user_properties:
+            reporter.add_property(propname, str(propvalue))
+
         if reporter is not None:
             reporter.finalize()
 
@@ -599,9 +603,6 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
             reporter = self._opentestcase(report)
             reporter.write_captured_output(report)
 
-            for propname, propvalue in report.user_properties:
-                reporter.add_property(propname, str(propvalue))
-
             self.finalize(report)
             report_wid = getattr(report, "worker_id", None)
             report_ii = getattr(report, "item_index", None)
diff --git a/testing/test_junitxml.py b/testing/test_junitxml.py
index 690830329c0..3f88c21e2b7 100644
--- a/testing/test_junitxml.py
+++ b/testing/test_junitxml.py
@@ -1228,6 +1228,36 @@ def test_record(record_property, other):
     result.stdout.fnmatch_lines(["*= 1 passed in *"])
 
 
+def test_record_property_on_test_and_teardown_failure(
+    pytester: Pytester, run_and_parse: RunAndParse
+) -> None:
+    pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        def other(record_property):
+            record_property("bar", 1)
+            yield
+            assert 0
+
+        def test_record(record_property, other):
+            record_property("foo", "<1")
+            assert 0
+    """
+    )
+    result, dom = run_and_parse()
+    node = dom.find_first_by_tag("testsuite")
+    tnodes = node.find_by_tag("testcase")
+    for tnode in tnodes:
+        psnode = tnode.find_first_by_tag("properties")
+        assert psnode, f"testcase didn't had expected properties:\n{tnode}"
+        pnodes = psnode.find_by_tag("property")
+        pnodes[0].assert_attr(name="bar", value="1")
+        pnodes[1].assert_attr(name="foo", value="<1")
+    result.stdout.fnmatch_lines(["*= 1 failed, 1 error *"])
+
+
 def test_record_property_same_name(
     pytester: Pytester, run_and_parse: RunAndParse
 ) -> None:

From 1de00e9830c57805ec07a0d67095822703b90bb4 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 5 Sep 2023 23:07:48 +0000
Subject: [PATCH 22/55] [7.4.x] Fix import_path for packages (#11395)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 changelog/11306.bugfix.rst |  1 +
 src/_pytest/pathlib.py     |  4 ++++
 testing/test_pathlib.py    | 45 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 50 insertions(+)
 create mode 100644 changelog/11306.bugfix.rst

diff --git a/changelog/11306.bugfix.rst b/changelog/11306.bugfix.rst
new file mode 100644
index 00000000000..02e0957a9c6
--- /dev/null
+++ b/changelog/11306.bugfix.rst
@@ -0,0 +1 @@
+Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index 14fb2e3ae2f..7cf64f03b2c 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -623,6 +623,10 @@ def module_name_from_path(path: Path, root: Path) -> str:
         # Use the parts for the relative path to the root path.
         path_parts = relative_path.parts
 
+    # Module name for packages do not contain the __init__ file.
+    if path_parts[-1] == "__init__":
+        path_parts = path_parts[:-1]
+
     return ".".join(path_parts)
 
 
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 3d574e85658..1ca6414375e 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -18,6 +18,7 @@
 from _pytest.pathlib import get_extended_length_path_str
 from _pytest.pathlib import get_lock_path
 from _pytest.pathlib import import_path
+from _pytest.pathlib import ImportMode
 from _pytest.pathlib import ImportPathMismatchError
 from _pytest.pathlib import insert_missing_modules
 from _pytest.pathlib import maybe_delete_a_numbered_dir
@@ -585,6 +586,10 @@ def test_module_name_from_path(self, tmp_path: Path) -> None:
         result = module_name_from_path(Path("/home/foo/test_foo.py"), Path("/bar"))
         assert result == "home.foo.test_foo"
 
+        # Importing __init__.py files should return the package as module name.
+        result = module_name_from_path(tmp_path / "src/app/__init__.py", tmp_path)
+        assert result == "src.app"
+
     def test_insert_missing_modules(
         self, monkeypatch: MonkeyPatch, tmp_path: Path
     ) -> None:
@@ -615,3 +620,43 @@ def test_parent_contains_child_module_attribute(
         assert sorted(modules) == ["xxx", "xxx.tests", "xxx.tests.foo"]
         assert modules["xxx"].tests is modules["xxx.tests"]
         assert modules["xxx.tests"].foo is modules["xxx.tests.foo"]
+
+    def test_importlib_package(self, monkeypatch: MonkeyPatch, tmp_path: Path):
+        """
+        Importing a package using --importmode=importlib should not import the
+        package's __init__.py file more than once (#11306).
+        """
+        monkeypatch.chdir(tmp_path)
+        monkeypatch.syspath_prepend(tmp_path)
+
+        package_name = "importlib_import_package"
+        tmp_path.joinpath(package_name).mkdir()
+        init = tmp_path.joinpath(f"{package_name}/__init__.py")
+        init.write_text(
+            dedent(
+                """
+                from .singleton import Singleton
+
+                instance = Singleton()
+                """
+            ),
+            encoding="ascii",
+        )
+        singleton = tmp_path.joinpath(f"{package_name}/singleton.py")
+        singleton.write_text(
+            dedent(
+                """
+                class Singleton:
+                    INSTANCES = []
+
+                    def __init__(self) -> None:
+                        self.INSTANCES.append(self)
+                        if len(self.INSTANCES) > 1:
+                            raise RuntimeError("Already initialized")
+                """
+            ),
+            encoding="ascii",
+        )
+
+        mod = import_path(init, root=tmp_path, mode=ImportMode.importlib)
+        assert len(mod.instance.INSTANCES) == 1

From de69883e3a6a923c9eceb28cedf8e9660bd58a51 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 6 Sep 2023 11:02:29 +0000
Subject: [PATCH 23/55] [7.4.x] improve plugin list disclaimer (#11398)

Co-authored-by: Stefaan Lippens <soxofaan@users.noreply.github.com>
---
 AUTHORS                          |  1 +
 changelog/11391.doc.rst          |  1 +
 doc/en/reference/plugin_list.rst | 22 +++++++++++++++++-----
 scripts/update-plugin-list.py    | 22 +++++++++++++++++-----
 4 files changed, 36 insertions(+), 10 deletions(-)
 create mode 100644 changelog/11391.doc.rst

diff --git a/AUTHORS b/AUTHORS
index d914f3ba906..74043fcfb6c 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -341,6 +341,7 @@ Simon Holesch
 Simon Kerr
 Skylar Downes
 Srinivas Reddy Thatiparthy
+Stefaan Lippens
 Stefan Farmbauer
 Stefan Scherfke
 Stefan Zimmermann
diff --git a/changelog/11391.doc.rst b/changelog/11391.doc.rst
new file mode 100644
index 00000000000..fff324af18d
--- /dev/null
+++ b/changelog/11391.doc.rst
@@ -0,0 +1 @@
+Improved disclaimer on pytest plugin reference page to better indicate this is an automated, non-curated listing.
diff --git a/doc/en/reference/plugin_list.rst b/doc/en/reference/plugin_list.rst
index c882130b03e..42b6a9a8f29 100644
--- a/doc/en/reference/plugin_list.rst
+++ b/doc/en/reference/plugin_list.rst
@@ -1,14 +1,26 @@
 
 .. _plugin-list:
 
-Plugin List
-===========
+Pytest Plugin List
+==================
 
-PyPI projects that match "pytest-\*" are considered plugins and are listed
-automatically together with a manually-maintained list in `the source
-code <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
+Below is an automated compilation of ``pytest``` plugins available on `PyPI <https://pypi.org>`_.
+It includes PyPI projects whose names begin with "pytest-" and a handful of manually selected projects.
 Packages classified as inactive are excluded.
 
+For detailed insights into how this list is generated,
+please refer to `the update script <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
+
+.. warning::
+
+   Please be aware that this list is not a curated collection of projects
+   and does not undergo a systematic review process.
+   It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins.
+
+   Do not presume any endorsement from the ``pytest`` project or its developers,
+   and always conduct your own quality assessment before incorporating any of these plugins into your own projects.
+
+
 .. The following conditional uses a different format for this list when
    creating a PDF, because otherwise the table gets far too wide for the
    page.
diff --git a/scripts/update-plugin-list.py b/scripts/update-plugin-list.py
index ea7e7986e15..2efe04165c2 100644
--- a/scripts/update-plugin-list.py
+++ b/scripts/update-plugin-list.py
@@ -13,14 +13,26 @@
 FILE_HEAD = r"""
 .. _plugin-list:
 
-Plugin List
-===========
+Pytest Plugin List
+==================
 
-PyPI projects that match "pytest-\*" are considered plugins and are listed
-automatically together with a manually-maintained list in `the source
-code <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
+Below is an automated compilation of ``pytest``` plugins available on `PyPI <https://pypi.org>`_.
+It includes PyPI projects whose names begin with "pytest-" and a handful of manually selected projects.
 Packages classified as inactive are excluded.
 
+For detailed insights into how this list is generated,
+please refer to `the update script <https://github.com/pytest-dev/pytest/blob/main/scripts/update-plugin-list.py>`_.
+
+.. warning::
+
+   Please be aware that this list is not a curated collection of projects
+   and does not undergo a systematic review process.
+   It serves purely as an informational resource to aid in the discovery of ``pytest`` plugins.
+
+   Do not presume any endorsement from the ``pytest`` project or its developers,
+   and always conduct your own quality assessment before incorporating any of these plugins into your own projects.
+
+
 .. The following conditional uses a different format for this list when
    creating a PDF, because otherwise the table gets far too wide for the
    page.

From 79c2012d4090bbe0cd0910e1616dc19ca75aea86 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 6 Sep 2023 13:50:00 +0000
Subject: [PATCH 24/55] [7.4.x] doc: Remove done training (#11400)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
---
 doc/en/index.rst | 1 -
 1 file changed, 1 deletion(-)

diff --git a/doc/en/index.rst b/doc/en/index.rst
index 69ca1dcfea9..bdee8b5dbd9 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -2,7 +2,6 @@
 
 .. sidebar:: Next Open Trainings
 
-   - `pytest: Professionelles Testen (nicht nur) für Python <https://workshoptage.ch/workshops/2023/pytest-professionelles-testen-nicht-nur-fuer-python-2/>`_, at `Workshoptage 2023 <https://workshoptage.ch/>`_, **September 5th**, `OST <https://www.ost.ch/en>`_ Campus **Rapperswil, Switzerland**
    - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, **March 5th to 7th 2024** (3 day in-depth training), **Leipzig, Germany / Remote**
 
    Also see :doc:`previous talks and blogposts <talks>`.

From 6e49a74089540196ae2b2bf8267a530ad9957f3e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 7 Sep 2023 13:33:12 +0000
Subject: [PATCH 25/55] [7.4.x] Fix doctest collection of
 `functools.cached_property` objects. (#11403)

Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
---
 AUTHORS                    |  1 +
 changelog/11237.bugfix.rst |  1 +
 src/_pytest/doctest.py     | 20 ++++++++++++++++++++
 testing/test_doctest.py    | 21 +++++++++++++++++++++
 4 files changed, 43 insertions(+)
 create mode 100644 changelog/11237.bugfix.rst

diff --git a/AUTHORS b/AUTHORS
index 74043fcfb6c..e8456d92b31 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -374,6 +374,7 @@ Tony Narlock
 Tor Colvin
 Trevor Bekolay
 Tyler Goodlet
+Tyler Smart
 Tzu-ping Chung
 Vasily Kuznetsov
 Victor Maryama
diff --git a/changelog/11237.bugfix.rst b/changelog/11237.bugfix.rst
new file mode 100644
index 00000000000..d054fc18d1c
--- /dev/null
+++ b/changelog/11237.bugfix.rst
@@ -0,0 +1 @@
+Fix doctest collection of `functools.cached_property` objects.
diff --git a/src/_pytest/doctest.py b/src/_pytest/doctest.py
index 455ad62cc9c..ca41a98ea9c 100644
--- a/src/_pytest/doctest.py
+++ b/src/_pytest/doctest.py
@@ -1,5 +1,6 @@
 """Discover and run doctests in modules and test files."""
 import bdb
+import functools
 import inspect
 import os
 import platform
@@ -536,6 +537,25 @@ def _find(
                         tests, obj, name, module, source_lines, globs, seen
                     )
 
+            if sys.version_info < (3, 13):
+
+                def _from_module(self, module, object):
+                    """`cached_property` objects are never considered a part
+                    of the 'current module'. As such they are skipped by doctest.
+                    Here we override `_from_module` to check the underlying
+                    function instead. https://github.com/python/cpython/issues/107995
+                    """
+                    if hasattr(functools, "cached_property") and isinstance(
+                        object, functools.cached_property
+                    ):
+                        object = object.func
+
+                    # Type ignored because this is a private function.
+                    return super()._from_module(module, object)  # type: ignore[misc]
+
+            else:  # pragma: no cover
+                pass
+
         if self.path.name == "conftest.py":
             module = self.config.pluginmanager._importconftest(
                 self.path,
diff --git a/testing/test_doctest.py b/testing/test_doctest.py
index dfe569987ca..665bdb73b5d 100644
--- a/testing/test_doctest.py
+++ b/testing/test_doctest.py
@@ -482,6 +482,27 @@ def test_doctestmodule(self, pytester: Pytester):
         reprec = pytester.inline_run(p, "--doctest-modules")
         reprec.assertoutcome(failed=1)
 
+    @pytest.mark.skipif(
+        sys.version_info[:2] <= (3, 7), reason="Only Python 3.7 or less"
+    )
+    def test_doctest_cached_property(self, pytester: Pytester):
+        p = pytester.makepyfile(
+            """
+            import functools
+
+            class Foo:
+                @functools.cached_property
+                def foo(self):
+                    '''
+                    >>> assert False, "Tacos!"
+                    '''
+                    ...
+        """
+        )
+        result = pytester.runpytest(p, "--doctest-modules")
+        result.assert_outcomes(failed=1)
+        assert "Tacos!" in result.stdout.str()
+
     def test_doctestmodule_external_and_issue116(self, pytester: Pytester):
         p = pytester.mkpydir("hello")
         p.joinpath("__init__.py").write_text(

From 884b911a9cbf3c97689b473ae7f732fc72e06eea Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Thu, 7 Sep 2023 12:49:25 -0300
Subject: [PATCH 26/55] Fix crash when passing a very long cmdline argument
 (#11404)

Fixes #11394

(cherry picked from commit 28ccf476b91be32ffda303f0d7a8b57e475b465b)
---
 changelog/11394.bugfix.rst |  1 +
 src/_pytest/main.py        |  3 ++-
 src/_pytest/pathlib.py     | 11 +++++++++++
 testing/test_main.py       | 31 +++++++++++++++++++++++++++++++
 testing/test_pathlib.py    | 32 ++++++++++++++++++++++++++++++++
 5 files changed, 77 insertions(+), 1 deletion(-)
 create mode 100644 changelog/11394.bugfix.rst

diff --git a/changelog/11394.bugfix.rst b/changelog/11394.bugfix.rst
new file mode 100644
index 00000000000..aa89c81b01f
--- /dev/null
+++ b/changelog/11394.bugfix.rst
@@ -0,0 +1 @@
+Fixed crash when parsing long command line arguments that might be interpreted as files.
diff --git a/src/_pytest/main.py b/src/_pytest/main.py
index 803b95a2033..ea89a63fa1b 100644
--- a/src/_pytest/main.py
+++ b/src/_pytest/main.py
@@ -36,6 +36,7 @@
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import bestrelpath
 from _pytest.pathlib import fnmatch_ex
+from _pytest.pathlib import safe_exists
 from _pytest.pathlib import visit
 from _pytest.reports import CollectReport
 from _pytest.reports import TestReport
@@ -895,7 +896,7 @@ def resolve_collection_argument(
         strpath = search_pypath(strpath)
     fspath = invocation_path / strpath
     fspath = absolutepath(fspath)
-    if not fspath.exists():
+    if not safe_exists(fspath):
         msg = (
             "module or package not found: {arg} (missing __init__.py?)"
             if as_pypath
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index 7cf64f03b2c..b5c2d86452f 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -1,5 +1,6 @@
 import atexit
 import contextlib
+import errno
 import fnmatch
 import importlib.util
 import itertools
@@ -791,3 +792,13 @@ def copytree(source: Path, target: Path) -> None:
             shutil.copyfile(x, newx)
         elif x.is_dir():
             newx.mkdir(exist_ok=True)
+
+
+def safe_exists(p: Path) -> bool:
+    """Like Path.exists(), but account for input arguments that might be too long (#11394)."""
+    try:
+        return p.exists()
+    except OSError as e:
+        if e.errno == errno.ENAMETOOLONG:
+            return False
+        raise
diff --git a/testing/test_main.py b/testing/test_main.py
index 71597626790..3c8998c1a35 100644
--- a/testing/test_main.py
+++ b/testing/test_main.py
@@ -262,3 +262,34 @@ def test(fix):
             "* 1 passed in *",
         ]
     )
+
+
+def test_very_long_cmdline_arg(pytester: Pytester) -> None:
+    """
+    Regression test for #11394.
+
+    Note: we could not manage to actually reproduce the error with this code, we suspect
+    GitHub runners are configured to support very long paths, however decided to leave
+    the test in place in case this ever regresses in the future.
+    """
+    pytester.makeconftest(
+        """
+        import pytest
+
+        def pytest_addoption(parser):
+            parser.addoption("--long-list", dest="long_list", action="store", default="all", help="List of things")
+
+        @pytest.fixture(scope="module")
+        def specified_feeds(request):
+            list_string = request.config.getoption("--long-list")
+            return list_string.split(',')
+        """
+    )
+    pytester.makepyfile(
+        """
+        def test_foo(specified_feeds):
+            assert len(specified_feeds) == 100_000
+        """
+    )
+    result = pytester.runpytest("--long-list", ",".join(["helloworld"] * 100_000))
+    result.stdout.fnmatch_lines("* 1 passed *")
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 1ca6414375e..8a9659aabd9 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -1,3 +1,4 @@
+import errno
 import os.path
 import pickle
 import sys
@@ -24,6 +25,7 @@
 from _pytest.pathlib import maybe_delete_a_numbered_dir
 from _pytest.pathlib import module_name_from_path
 from _pytest.pathlib import resolve_package_path
+from _pytest.pathlib import safe_exists
 from _pytest.pathlib import symlink_or_skip
 from _pytest.pathlib import visit
 from _pytest.tmpdir import TempPathFactory
@@ -660,3 +662,33 @@ def __init__(self) -> None:
 
         mod = import_path(init, root=tmp_path, mode=ImportMode.importlib)
         assert len(mod.instance.INSTANCES) == 1
+
+
+def test_safe_exists(tmp_path: Path) -> None:
+    d = tmp_path.joinpath("some_dir")
+    d.mkdir()
+    assert safe_exists(d) is True
+
+    f = tmp_path.joinpath("some_file")
+    f.touch()
+    assert safe_exists(f) is True
+
+    # Use unittest.mock() as a context manager to have a very narrow
+    # patch lifetime.
+    p = tmp_path.joinpath("some long filename" * 100)
+    with unittest.mock.patch.object(
+        Path,
+        "exists",
+        autospec=True,
+        side_effect=OSError(errno.ENAMETOOLONG, "name too long"),
+    ):
+        assert safe_exists(p) is False
+
+    with unittest.mock.patch.object(
+        Path,
+        "exists",
+        autospec=True,
+        side_effect=OSError(errno.EIO, "another kind of error"),
+    ):
+        with pytest.raises(OSError):
+            _ = safe_exists(p)

From 63b0c6f75f218b400a5a42305f9fa3830448f7e8 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <bruno@soliv.dev>
Date: Thu, 7 Sep 2023 13:15:11 -0300
Subject: [PATCH 27/55] Use _pytest.pathlib.safe_exists in get_dirs_from_args

Related to #11394
---
 src/_pytest/config/__init__.py  | 9 +++------
 src/_pytest/config/findpaths.py | 9 +--------
 src/_pytest/pathlib.py          | 9 ++++-----
 testing/test_pathlib.py         | 5 ++---
 4 files changed, 10 insertions(+), 22 deletions(-)

diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py
index dc2b9f6a160..e3990d175df 100644
--- a/src/_pytest/config/__init__.py
+++ b/src/_pytest/config/__init__.py
@@ -57,6 +57,7 @@
 from _pytest.pathlib import import_path
 from _pytest.pathlib import ImportMode
 from _pytest.pathlib import resolve_package_path
+from _pytest.pathlib import safe_exists
 from _pytest.stash import Stash
 from _pytest.warning_types import PytestConfigWarning
 from _pytest.warning_types import warn_explicit_for
@@ -557,12 +558,8 @@ def _set_initial_conftests(
             anchor = absolutepath(current / path)
 
             # Ensure we do not break if what appears to be an anchor
-            # is in fact a very long option (#10169).
-            try:
-                anchor_exists = anchor.exists()
-            except OSError:  # pragma: no cover
-                anchor_exists = False
-            if anchor_exists:
+            # is in fact a very long option (#10169, #11394).
+            if safe_exists(anchor):
                 self._try_load_conftest(anchor, importmode, rootpath)
                 foundanchor = True
         if not foundanchor:
diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py
index 234b9e12906..02674ffae3b 100644
--- a/src/_pytest/config/findpaths.py
+++ b/src/_pytest/config/findpaths.py
@@ -16,6 +16,7 @@
 from _pytest.outcomes import fail
 from _pytest.pathlib import absolutepath
 from _pytest.pathlib import commonpath
+from _pytest.pathlib import safe_exists
 
 if TYPE_CHECKING:
     from . import Config
@@ -151,14 +152,6 @@ def get_dir_from_path(path: Path) -> Path:
             return path
         return path.parent
 
-    def safe_exists(path: Path) -> bool:
-        # This can throw on paths that contain characters unrepresentable at the OS level,
-        # or with invalid syntax on Windows (https://bugs.python.org/issue35306)
-        try:
-            return path.exists()
-        except OSError:
-            return False
-
     # These look like paths but may not exist
     possible_paths = (
         absolutepath(get_file_part_from_node_id(arg))
diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index b5c2d86452f..5c765c68348 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -1,6 +1,5 @@
 import atexit
 import contextlib
-import errno
 import fnmatch
 import importlib.util
 import itertools
@@ -798,7 +797,7 @@ def safe_exists(p: Path) -> bool:
     """Like Path.exists(), but account for input arguments that might be too long (#11394)."""
     try:
         return p.exists()
-    except OSError as e:
-        if e.errno == errno.ENAMETOOLONG:
-            return False
-        raise
+    except (ValueError, OSError):
+        # ValueError: stat: path too long for Windows
+        # OSError: [WinError 123] The filename, directory name, or volume label syntax is incorrect
+        return False
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 8a9659aabd9..678fd27feac 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -688,7 +688,6 @@ def test_safe_exists(tmp_path: Path) -> None:
         Path,
         "exists",
         autospec=True,
-        side_effect=OSError(errno.EIO, "another kind of error"),
+        side_effect=ValueError("name too long"),
     ):
-        with pytest.raises(OSError):
-            _ = safe_exists(p)
+        assert safe_exists(p) is False

From 45f34dfb8d813b00c7d8060a3ea75f7b4bc5cd7d Mon Sep 17 00:00:00 2001
From: pytest bot <pytestbot@gmail.com>
Date: Thu, 7 Sep 2023 17:21:49 +0000
Subject: [PATCH 28/55] Prepare release version 7.4.2

---
 changelog/11237.bugfix.rst        |  1 -
 changelog/11306.bugfix.rst        |  1 -
 changelog/11367.bugfix.rst        |  1 -
 changelog/11391.doc.rst           |  1 -
 changelog/11394.bugfix.rst        |  1 -
 doc/en/announce/index.rst         |  1 +
 doc/en/announce/release-7.4.2.rst | 18 ++++++++++++++++++
 doc/en/builtin.rst                |  2 +-
 doc/en/changelog.rst              | 25 +++++++++++++++++++++++++
 doc/en/example/reportingdemo.rst  | 20 ++++++++++----------
 doc/en/getting-started.rst        |  2 +-
 11 files changed, 56 insertions(+), 17 deletions(-)
 delete mode 100644 changelog/11237.bugfix.rst
 delete mode 100644 changelog/11306.bugfix.rst
 delete mode 100644 changelog/11367.bugfix.rst
 delete mode 100644 changelog/11391.doc.rst
 delete mode 100644 changelog/11394.bugfix.rst
 create mode 100644 doc/en/announce/release-7.4.2.rst

diff --git a/changelog/11237.bugfix.rst b/changelog/11237.bugfix.rst
deleted file mode 100644
index d054fc18d1c..00000000000
--- a/changelog/11237.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix doctest collection of `functools.cached_property` objects.
diff --git a/changelog/11306.bugfix.rst b/changelog/11306.bugfix.rst
deleted file mode 100644
index 02e0957a9c6..00000000000
--- a/changelog/11306.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
diff --git a/changelog/11367.bugfix.rst b/changelog/11367.bugfix.rst
deleted file mode 100644
index dda40db0fc9..00000000000
--- a/changelog/11367.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed bug where `user_properties` where not being saved in the JUnit XML file if a fixture failed during teardown.
diff --git a/changelog/11391.doc.rst b/changelog/11391.doc.rst
deleted file mode 100644
index fff324af18d..00000000000
--- a/changelog/11391.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Improved disclaimer on pytest plugin reference page to better indicate this is an automated, non-curated listing.
diff --git a/changelog/11394.bugfix.rst b/changelog/11394.bugfix.rst
deleted file mode 100644
index aa89c81b01f..00000000000
--- a/changelog/11394.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed crash when parsing long command line arguments that might be interpreted as files.
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index 85dfa0894fe..39fdfc13776 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,7 @@ Release announcements
    :maxdepth: 2
 
 
+   release-7.4.2
    release-7.4.1
    release-7.4.0
    release-7.3.2
diff --git a/doc/en/announce/release-7.4.2.rst b/doc/en/announce/release-7.4.2.rst
new file mode 100644
index 00000000000..22191e7b4f9
--- /dev/null
+++ b/doc/en/announce/release-7.4.2.rst
@@ -0,0 +1,18 @@
+pytest-7.4.2
+=======================================
+
+pytest 7.4.2 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst
index 0d673d0426e..405289444a8 100644
--- a/doc/en/builtin.rst
+++ b/doc/en/builtin.rst
@@ -105,7 +105,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:757
         Fixture that returns a :py:class:`dict` that will be injected into the
         namespace of doctests.
 
diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index be7e7fabaef..ecfeeb662b6 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -28,6 +28,31 @@ with advance notice in the **Deprecations** section of releases.
 
 .. towncrier release notes start
 
+pytest 7.4.2 (2023-09-07)
+=========================
+
+Bug Fixes
+---------
+
+- `#11237 <https://github.com/pytest-dev/pytest/issues/11237>`_: Fix doctest collection of `functools.cached_property` objects.
+
+
+- `#11306 <https://github.com/pytest-dev/pytest/issues/11306>`_: Fixed bug using ``--importmode=importlib`` which would cause package ``__init__.py`` files to be imported more than once in some cases.
+
+
+- `#11367 <https://github.com/pytest-dev/pytest/issues/11367>`_: Fixed bug where `user_properties` where not being saved in the JUnit XML file if a fixture failed during teardown.
+
+
+- `#11394 <https://github.com/pytest-dev/pytest/issues/11394>`_: Fixed crash when parsing long command line arguments that might be interpreted as files.
+
+
+
+Improved Documentation
+----------------------
+
+- `#11391 <https://github.com/pytest-dev/pytest/issues/11391>`_: Improved disclaimer on pytest plugin reference page to better indicate this is an automated, non-curated listing.
+
+
 pytest 7.4.1 (2023-09-02)
 =========================
 
diff --git a/doc/en/example/reportingdemo.rst b/doc/en/example/reportingdemo.rst
index d4d3d3ce202..cb59c4b42e1 100644
--- a/doc/en/example/reportingdemo.rst
+++ b/doc/en/example/reportingdemo.rst
@@ -554,13 +554,13 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     E       AssertionError: assert False
     E        +  where False = <built-in method startswith of str object at 0xdeadbeef0027>('456')
     E        +    where <built-in method startswith of str object at 0xdeadbeef0027> = '123'.startswith
-    E        +      where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0006>()
-    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef0029>()
+    E        +      where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef0029>()
+    E        +    and   '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef002a>()
 
     failure_demo.py:235: AssertionError
     _____________________ TestMoreErrors.test_global_func ______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002a>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
 
         def test_global_func(self):
     >       assert isinstance(globf(42), float)
@@ -571,18 +571,18 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:238: AssertionError
     _______________________ TestMoreErrors.test_instance _______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
 
         def test_instance(self):
             self.x = 6 * 7
     >       assert self.x != 42
     E       assert 42 != 42
-    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002b>.x
+    E        +  where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>.x
 
     failure_demo.py:242: AssertionError
     _______________________ TestMoreErrors.test_compare ________________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002c>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
 
         def test_compare(self):
     >       assert globf(10) < 5
@@ -592,7 +592,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:245: AssertionError
     _____________________ TestMoreErrors.test_try_finally ______________________
 
-    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002d>
+    self = <failure_demo.TestMoreErrors object at 0xdeadbeef002e>
 
         def test_try_finally(self):
             x = 1
@@ -603,7 +603,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:250: AssertionError
     ___________________ TestCustomAssertMsg.test_single_line ___________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002e>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
 
         def test_single_line(self):
             class A:
@@ -618,7 +618,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:261: AssertionError
     ____________________ TestCustomAssertMsg.test_multiline ____________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef002f>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
 
         def test_multiline(self):
             class A:
@@ -637,7 +637,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
     failure_demo.py:268: AssertionError
     ___________________ TestCustomAssertMsg.test_custom_repr ___________________
 
-    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0030>
+    self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef0031>
 
         def test_custom_repr(self):
             class JSON:
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index 31cfa68bf38..e426e0c5072 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -22,7 +22,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 7.4.1
+    pytest 7.4.2
 
 .. _`simpletest`:
 

From c39bdf6190a80d522885808339cc6e3a0920ebb8 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Fri, 8 Sep 2023 08:42:55 -0300
Subject: [PATCH 29/55] Adjustments to the release process (#11410) (#11415)

As discussed in #11408:

* Improve documentation for the release process.
* Fix the description for the PRs created by the `prepare release pr` workflow.
* Fix pushing tag in the `deploy` workflow.

(cherry picked from commit e5c81fa41aa437261009d2dcbed5f05bb2b86647)
---
 .github/workflows/deploy.yml  |  2 ++
 RELEASING.rst                 |  3 ++-
 scripts/prepare-release-pr.py | 12 +++++++++---
 3 files changed, 13 insertions(+), 4 deletions(-)

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 4cda08650ad..7340e13664c 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -36,8 +36,10 @@ jobs:
     timeout-minutes: 30
     permissions:
       id-token: write
+      contents: write
     steps:
     - uses: actions/checkout@v3
+
     - name: Download Package
       uses: actions/download-artifact@v3
       with:
diff --git a/RELEASING.rst b/RELEASING.rst
index 5d49fb5d6d9..08004a84c00 100644
--- a/RELEASING.rst
+++ b/RELEASING.rst
@@ -134,7 +134,8 @@ Releasing
 Both automatic and manual processes described above follow the same steps from this point onward.
 
 #. After all tests pass and the PR has been approved, trigger the ``deploy`` job
-   in https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml.
+   in https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml, using the ``release-MAJOR.MINOR.PATCH`` branch
+   as source.
 
    This job will require approval from ``pytest-dev/core``, after which it will publish to PyPI
    and tag the repository.
diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py
index 7a80de7edaa..a0e5e4d7f37 100644
--- a/scripts/prepare-release-pr.py
+++ b/scripts/prepare-release-pr.py
@@ -31,10 +31,16 @@ class InvalidFeatureRelease(Exception):
 SLUG = "pytest-dev/pytest"
 
 PR_BODY = """\
-Created automatically from manual trigger.
+Created by the [prepare release pr](https://github.com/pytest-dev/pytest/actions/workflows/prepare-release-pr.yml)
+workflow.
 
-Once all builds pass and it has been **approved** by one or more maintainers, the build
-can be released by pushing a tag `{version}` to this repository.
+Once all builds pass and it has been **approved** by one or more maintainers,
+start the [deploy](https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml) workflow, using these parameters:
+
+* `Use workflow from`: `release-{version}`.
+* `Release version`: `{version}`.
+
+After the `deploy` workflow has been approved by a core maintainer, the package will be uploaded to PyPI automatically.
 """
 
 

From 5341b9cd67dad64d1d96dd0851861ff5f7f874dc Mon Sep 17 00:00:00 2001
From: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Date: Sat, 9 Sep 2023 14:09:31 +0200
Subject: [PATCH 30/55] Fix assert rewriting with assignment expressions
 (#11414)

Fixes #11239

(cherry picked from commit 7259e8db9844f6f973c1d0c0ce46cc68c8248abb)
---
 AUTHORS                          |  1 +
 changelog/11239.bugfix.rst       |  1 +
 src/_pytest/assertion/rewrite.py | 54 +++++++++++++++++++++++---------
 testing/test_assertrewrite.py    | 21 +++++++++++++
 4 files changed, 63 insertions(+), 14 deletions(-)
 create mode 100644 changelog/11239.bugfix.rst

diff --git a/AUTHORS b/AUTHORS
index e8456d92b31..6d860575f37 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -231,6 +231,7 @@ Maho
 Maik Figura
 Mandeep Bhutani
 Manuel Krebber
+Marc Mueller
 Marc Schlaich
 Marcelo Duarte Trevisani
 Marcin Bachry
diff --git a/changelog/11239.bugfix.rst b/changelog/11239.bugfix.rst
new file mode 100644
index 00000000000..a486224cdda
--- /dev/null
+++ b/changelog/11239.bugfix.rst
@@ -0,0 +1 @@
+Fixed ``:=`` in asserts impacting unrelated test cases.
diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py
index ab83fee32b2..fd23552973e 100644
--- a/src/_pytest/assertion/rewrite.py
+++ b/src/_pytest/assertion/rewrite.py
@@ -13,6 +13,7 @@
 import sys
 import tokenize
 import types
+from collections import defaultdict
 from pathlib import Path
 from pathlib import PurePath
 from typing import Callable
@@ -56,6 +57,10 @@
     astNum = ast.Num
 
 
+class Sentinel:
+    pass
+
+
 assertstate_key = StashKey["AssertionState"]()
 
 # pytest caches rewritten pycs in pycache dirs
@@ -63,6 +68,9 @@
 PYC_EXT = ".py" + (__debug__ and "c" or "o")
 PYC_TAIL = "." + PYTEST_TAG + PYC_EXT
 
+# Special marker that denotes we have just left a scope definition
+_SCOPE_END_MARKER = Sentinel()
+
 
 class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader):
     """PEP302/PEP451 import hook which rewrites asserts."""
@@ -645,6 +653,8 @@ class AssertionRewriter(ast.NodeVisitor):
        .push_format_context() and .pop_format_context() which allows
        to build another %-formatted string while already building one.
 
+    :scope: A tuple containing the current scope used for variables_overwrite.
+
     :variables_overwrite: A dict filled with references to variables
        that change value within an assert. This happens when a variable is
        reassigned with the walrus operator
@@ -666,7 +676,10 @@ def __init__(
         else:
             self.enable_assertion_pass_hook = False
         self.source = source
-        self.variables_overwrite: Dict[str, str] = {}
+        self.scope: tuple[ast.AST, ...] = ()
+        self.variables_overwrite: defaultdict[
+            tuple[ast.AST, ...], Dict[str, str]
+        ] = defaultdict(dict)
 
     def run(self, mod: ast.Module) -> None:
         """Find all assert statements in *mod* and rewrite them."""
@@ -732,9 +745,17 @@ def run(self, mod: ast.Module) -> None:
         mod.body[pos:pos] = imports
 
         # Collect asserts.
-        nodes: List[ast.AST] = [mod]
+        self.scope = (mod,)
+        nodes: List[Union[ast.AST, Sentinel]] = [mod]
         while nodes:
             node = nodes.pop()
+            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
+                self.scope = tuple((*self.scope, node))
+                nodes.append(_SCOPE_END_MARKER)
+            if node == _SCOPE_END_MARKER:
+                self.scope = self.scope[:-1]
+                continue
+            assert isinstance(node, ast.AST)
             for name, field in ast.iter_fields(node):
                 if isinstance(field, list):
                     new: List[ast.AST] = []
@@ -1005,7 +1026,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]:
                     ]
                 ):
                     pytest_temp = self.variable()
-                    self.variables_overwrite[
+                    self.variables_overwrite[self.scope][
                         v.left.target.id
                     ] = v.left  # type:ignore[assignment]
                     v.left.target.id = pytest_temp
@@ -1048,17 +1069,20 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]:
         new_args = []
         new_kwargs = []
         for arg in call.args:
-            if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite:
-                arg = self.variables_overwrite[arg.id]  # type:ignore[assignment]
+            if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite.get(
+                self.scope, {}
+            ):
+                arg = self.variables_overwrite[self.scope][
+                    arg.id
+                ]  # type:ignore[assignment]
             res, expl = self.visit(arg)
             arg_expls.append(expl)
             new_args.append(res)
         for keyword in call.keywords:
-            if (
-                isinstance(keyword.value, ast.Name)
-                and keyword.value.id in self.variables_overwrite
-            ):
-                keyword.value = self.variables_overwrite[
+            if isinstance(
+                keyword.value, ast.Name
+            ) and keyword.value.id in self.variables_overwrite.get(self.scope, {}):
+                keyword.value = self.variables_overwrite[self.scope][
                     keyword.value.id
                 ]  # type:ignore[assignment]
             res, expl = self.visit(keyword.value)
@@ -1094,12 +1118,14 @@ def visit_Attribute(self, attr: ast.Attribute) -> Tuple[ast.Name, 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(comp.left, ast.Name) and comp.left.id in self.variables_overwrite:
-            comp.left = self.variables_overwrite[
+        if isinstance(
+            comp.left, ast.Name
+        ) and comp.left.id in self.variables_overwrite.get(self.scope, {}):
+            comp.left = self.variables_overwrite[self.scope][
                 comp.left.id
             ]  # type:ignore[assignment]
         if isinstance(comp.left, namedExpr):
-            self.variables_overwrite[
+            self.variables_overwrite[self.scope][
                 comp.left.target.id
             ] = comp.left  # type:ignore[assignment]
         left_res, left_expl = self.visit(comp.left)
@@ -1119,7 +1145,7 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]:
                 and next_operand.target.id == left_res.id
             ):
                 next_operand.target.id = self.variable()
-                self.variables_overwrite[
+                self.variables_overwrite[self.scope][
                     left_res.id
                 ] = next_operand  # type:ignore[assignment]
             next_res, next_expl = self.visit(next_operand)
diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index 778f843e6cf..63353438c95 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -1531,6 +1531,27 @@ def test_gt():
         result.stdout.fnmatch_lines(["*assert 4 > 5", "*where 5 = add_one(4)"])
 
 
+class TestIssue11239:
+    def test_assertion_walrus_different_test_cases(self, pytester: Pytester) -> None:
+        """Regression for (#11239)
+
+        Walrus operator rewriting would leak to separate test cases if they used the same variables.
+        """
+        pytester.makepyfile(
+            """
+            def test_1():
+                state = {"x": 2}.get("x")
+                assert state is not None
+
+            def test_2():
+                db = {"x": 2}
+                assert (state := db.get("x")) is not None
+        """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0
+
+
 @pytest.mark.skipif(
     sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems"
 )

From 721a0881fb99c8f749701d1301cf19a948a9554f Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <bruno@soliv.dev>
Date: Sat, 9 Sep 2023 09:38:10 -0300
Subject: [PATCH 31/55] Skip test_assertion_walrus_different_test_cases on
 Python 3.7

---
 testing/test_assertrewrite.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index 63353438c95..fbf2854953f 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -1532,6 +1532,7 @@ def test_gt():
 
 
 class TestIssue11239:
+    @pytest.mark.skipif(sys.version_info[:2] <= (3, 7), reason="Only Python 3.8+")
     def test_assertion_walrus_different_test_cases(self, pytester: Pytester) -> None:
         """Regression for (#11239)
 

From d849a3ed64c6da63a0e3713892a7bfefdd56acaf Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sat, 9 Sep 2023 13:02:31 +0000
Subject: [PATCH 32/55] [7.4.x] fix: closes #11343's [attr-defined] type errors
 (#11421)

Co-authored-by: Warren Markham <rabbitsinwarrens@gmail.com>
---
 src/_pytest/compat.py    | 25 +++++++++++++++++--------
 testing/test_parseopt.py |  3 ++-
 2 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/src/_pytest/compat.py b/src/_pytest/compat.py
index 352211de8aa..1d0add7363e 100644
--- a/src/_pytest/compat.py
+++ b/src/_pytest/compat.py
@@ -380,15 +380,24 @@ def __get__(self, instance, owner=None):
 
 
 def get_user_id() -> int | None:
-    """Return the current user id, or None if we cannot get it reliably on the current platform."""
-    # win32 does not have a getuid() function.
-    # On Emscripten, getuid() is a stub that always returns 0.
-    if sys.platform in ("win32", "emscripten"):
+    """Return the current process's real user id or None if it could not be
+    determined.
+
+    :return: The user id or None if it could not be determined.
+    """
+    # mypy follows the version and platform checking expectation of PEP 484:
+    # https://mypy.readthedocs.io/en/stable/common_issues.html?highlight=platform#python-version-and-system-platform-checks
+    # Containment checks are too complex for mypy v1.5.0 and cause failure.
+    if sys.platform == "win32" or sys.platform == "emscripten":
+        # win32 does not have a getuid() function.
+        # Emscripten has a return 0 stub.
         return None
-    # getuid shouldn't fail, but cpython defines such a case.
-    # Let's hope for the best.
-    uid = os.getuid()
-    return uid if uid != -1 else None
+    else:
+        # On other platforms, a return value of -1 is assumed to indicate that
+        # the current process's real user id could not be determined.
+        ERROR = -1
+        uid = os.getuid()
+        return uid if uid != ERROR else None
 
 
 # Perform exhaustiveness checking.
diff --git a/testing/test_parseopt.py b/testing/test_parseopt.py
index 1899abe153f..b6df035aacf 100644
--- a/testing/test_parseopt.py
+++ b/testing/test_parseopt.py
@@ -291,7 +291,8 @@ def test_multiple_metavar_help(self, parser: parseopt.Parser) -> None:
 
 def test_argcomplete(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:
     try:
-        encoding = locale.getencoding()  # New in Python 3.11, ignores utf-8 mode
+        # New in Python 3.11, ignores utf-8 mode
+        encoding = locale.getencoding()  # type: ignore[attr-defined]
     except AttributeError:
         encoding = locale.getpreferredencoding(False)
     try:

From 1944dc06d39404ae9869b544dc2e2b482bf472e2 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 10 Sep 2023 13:27:53 +0000
Subject: [PATCH 33/55] [7.4.x] Fix --import-mode=importlib when root contains
 `__init__.py` file (#11426)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 src/_pytest/pathlib.py  |  5 +++--
 testing/test_pathlib.py | 21 +++++++++++++++++++++
 2 files changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py
index 5c765c68348..c2f8535f5f5 100644
--- a/src/_pytest/pathlib.py
+++ b/src/_pytest/pathlib.py
@@ -623,8 +623,9 @@ def module_name_from_path(path: Path, root: Path) -> str:
         # Use the parts for the relative path to the root path.
         path_parts = relative_path.parts
 
-    # Module name for packages do not contain the __init__ file.
-    if path_parts[-1] == "__init__":
+    # Module name for packages do not contain the __init__ file, unless
+    # the `__init__.py` file is at the root.
+    if len(path_parts) >= 2 and path_parts[-1] == "__init__":
         path_parts = path_parts[:-1]
 
     return ".".join(path_parts)
diff --git a/testing/test_pathlib.py b/testing/test_pathlib.py
index 678fd27feac..5ae0bcdde09 100644
--- a/testing/test_pathlib.py
+++ b/testing/test_pathlib.py
@@ -28,6 +28,7 @@
 from _pytest.pathlib import safe_exists
 from _pytest.pathlib import symlink_or_skip
 from _pytest.pathlib import visit
+from _pytest.pytester import Pytester
 from _pytest.tmpdir import TempPathFactory
 
 
@@ -592,6 +593,10 @@ def test_module_name_from_path(self, tmp_path: Path) -> None:
         result = module_name_from_path(tmp_path / "src/app/__init__.py", tmp_path)
         assert result == "src.app"
 
+        # Unless __init__.py file is at the root, in which case we cannot have an empty module name.
+        result = module_name_from_path(tmp_path / "__init__.py", tmp_path)
+        assert result == "__init__"
+
     def test_insert_missing_modules(
         self, monkeypatch: MonkeyPatch, tmp_path: Path
     ) -> None:
@@ -663,6 +668,22 @@ def __init__(self) -> None:
         mod = import_path(init, root=tmp_path, mode=ImportMode.importlib)
         assert len(mod.instance.INSTANCES) == 1
 
+    def test_importlib_root_is_package(self, pytester: Pytester) -> None:
+        """
+        Regression for importing a `__init__`.py file that is at the root
+        (#11417).
+        """
+        pytester.makepyfile(__init__="")
+        pytester.makepyfile(
+            """
+            def test_my_test():
+                assert True
+            """
+        )
+
+        result = pytester.runpytest("--import-mode=importlib")
+        result.stdout.fnmatch_lines("* 1 passed *")
+
 
 def test_safe_exists(tmp_path: Path) -> None:
     d = tmp_path.joinpath("some_dir")

From f8bb8572fed8627946bfc82819d24b138d587257 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Mon, 11 Sep 2023 09:48:22 -0300
Subject: [PATCH 34/55] Force terminal width when running tests (#11425)
 (#11432)

Related to #11423

(cherry picked from commit 241f2a890e3fb3f83eac807de0b4ad00510c7f65)
---
 testing/conftest.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/testing/conftest.py b/testing/conftest.py
index 8e77fcae5ab..2a3ce5203d9 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -21,6 +21,15 @@ def restore_tracing():
             sys.settrace(orig_trace)
 
 
+@pytest.fixture(autouse=True)
+def set_column_width(monkeypatch: pytest.MonkeyPatch) -> None:
+    """
+    Force terminal width to 80: some tests check the formatting of --help, which is sensible
+    to terminal width.
+    """
+    monkeypatch.setenv("COLUMNS", "80")
+
+
 @pytest.hookimpl(hookwrapper=True, tryfirst=True)
 def pytest_collection_modifyitems(items):
     """Prefer faster tests.

From 21fe071d797612468fa18dd0ae4d6dbf49846b6d Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 20 Sep 2023 12:41:01 +0000
Subject: [PATCH 35/55] [7.4.x] fix for ValueError raised in faulthandler
 teardown code (#11455)

Co-authored-by: Simon Blanchard <bnomis@gmail.com>
---
 AUTHORS                     | 1 +
 changelog/11439.bugfix.rst  | 1 +
 src/_pytest/faulthandler.py | 3 +--
 3 files changed, 3 insertions(+), 2 deletions(-)
 create mode 100644 changelog/11439.bugfix.rst

diff --git a/AUTHORS b/AUTHORS
index 6d860575f37..be1e7863860 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -337,6 +337,7 @@ Serhii Mozghovyi
 Seth Junot
 Shantanu Jain
 Shubham Adep
+Simon Blanchard
 Simon Gomizelj
 Simon Holesch
 Simon Kerr
diff --git a/changelog/11439.bugfix.rst b/changelog/11439.bugfix.rst
new file mode 100644
index 00000000000..b5104b1bcab
--- /dev/null
+++ b/changelog/11439.bugfix.rst
@@ -0,0 +1 @@
+Handle an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py
index af879aa44cf..36040bffffc 100644
--- a/src/_pytest/faulthandler.py
+++ b/src/_pytest/faulthandler.py
@@ -1,4 +1,3 @@
-import io
 import os
 import sys
 from typing import Generator
@@ -51,7 +50,7 @@ def get_stderr_fileno() -> int:
         if fileno == -1:
             raise AttributeError()
         return fileno
-    except (AttributeError, io.UnsupportedOperation):
+    except (AttributeError, ValueError):
         # pytest-xdist monkeypatches sys.stderr with an object that is not an actual file.
         # https://docs.python.org/3/library/faulthandler.html#issue-with-file-descriptors
         # This is potentially dangerous, but the best we can do.

From a5178273183ddbda0ef4e4c6aa2b92aab086776b Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 23 Oct 2023 13:23:18 +0000
Subject: [PATCH 36/55] [7.4.x] Configure ReadTheDocs to fail on warnings
 (#11540)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 .readthedocs.yml               | 4 ++++
 doc/en/reference/customize.rst | 2 +-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/.readthedocs.yml b/.readthedocs.yml
index b506c5f4039..266d4e07aea 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -9,6 +9,10 @@ python:
        path: .
      - requirements: doc/en/requirements.txt
 
+sphinx:
+  configuration: doc/en/conf.py
+  fail_on_warning: true
+
 build:
   os: ubuntu-20.04
   tools:
diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst
index b794d646b8e..24c0ed21752 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 :doc:`distutils <python:distutils/configfile>`, and can also be used to hold pytest configuration
+``setup.cfg`` files are general purpose configuration files, used originally by ``distutils`` (now deprecated) and `setuptools <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`__, and can also be used to hold pytest configuration
 if they have a ``[tool:pytest]`` section.
 
 .. code-block:: ini

From 5dc77253d439038ac64c55a5a48692ac3a53db2e Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 23 Oct 2023 14:28:04 +0000
Subject: [PATCH 37/55] [7.4.x] Ensure logging tests always cleanup after
 themselves (#11541)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 testing/logging/test_fixture.py | 56 +++++++++++++++++++--------------
 1 file changed, 33 insertions(+), 23 deletions(-)

diff --git a/testing/logging/test_fixture.py b/testing/logging/test_fixture.py
index 8eaa2de96a8..753cf5fcd03 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
 import logging
+from typing import Iterator
 
 import pytest
 from _pytest.logging import caplog_records_key
@@ -9,8 +11,8 @@
 sublogger = logging.getLogger(__name__ + ".baz")
 
 
-@pytest.fixture
-def cleanup_disabled_logging():
+@pytest.fixture(autouse=True)
+def cleanup_disabled_logging() -> Iterator[None]:
     """Simple fixture that ensures that a test doesn't disable logging.
 
     This is necessary because ``logging.disable()`` is global, so a test disabling logging
@@ -27,7 +29,7 @@ def test_fixture_help(pytester: Pytester) -> None:
     result.stdout.fnmatch_lines(["*caplog*"])
 
 
-def test_change_level(caplog):
+def test_change_level(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.debug("handler DEBUG level")
     logger.info("handler INFO level")
@@ -42,7 +44,7 @@ def test_change_level(caplog):
     assert "CRITICAL" in caplog.text
 
 
-def test_change_level_logging_disabled(caplog, cleanup_disabled_logging):
+def test_change_level_logging_disabled(caplog: pytest.LogCaptureFixture) -> None:
     logging.disable(logging.CRITICAL)
     assert logging.root.manager.disable == logging.CRITICAL
     caplog.set_level(logging.WARNING)
@@ -85,9 +87,7 @@ def test2(caplog):
     result.stdout.no_fnmatch_line("*log from test2*")
 
 
-def test_change_disabled_level_undo(
-    pytester: Pytester, cleanup_disabled_logging
-) -> None:
+def test_change_disabled_level_undo(pytester: Pytester) -> None:
     """Ensure that '_force_enable_logging' in 'set_level' is undone after the end of the test.
 
     Tests the logging output themselves (affected by disabled logging level).
@@ -144,7 +144,7 @@ def test3(caplog):
     result.assert_outcomes(passed=3)
 
 
-def test_with_statement(caplog):
+def test_with_statement(caplog: pytest.LogCaptureFixture) -> None:
     with caplog.at_level(logging.INFO):
         logger.debug("handler DEBUG level")
         logger.info("handler INFO level")
@@ -159,7 +159,7 @@ def test_with_statement(caplog):
     assert "CRITICAL" in caplog.text
 
 
-def test_with_statement_logging_disabled(caplog, cleanup_disabled_logging):
+def test_with_statement_logging_disabled(caplog: pytest.LogCaptureFixture) -> None:
     logging.disable(logging.CRITICAL)
     assert logging.root.manager.disable == logging.CRITICAL
     with caplog.at_level(logging.WARNING):
@@ -198,8 +198,8 @@ def test_with_statement_logging_disabled(caplog, cleanup_disabled_logging):
     ],
 )
 def test_force_enable_logging_level_string(
-    caplog, cleanup_disabled_logging, level_str, expected_disable_level
-):
+    caplog: pytest.LogCaptureFixture, level_str: str, expected_disable_level: int
+) -> None:
     """Test _force_enable_logging using a level string.
 
     ``expected_disable_level`` is one level below ``level_str`` because the disabled log level
@@ -218,7 +218,7 @@ def test_force_enable_logging_level_string(
     assert test_logger.manager.disable == expected_disable_level
 
 
-def test_log_access(caplog):
+def test_log_access(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("boo %s", "arg")
     assert caplog.records[0].levelname == "INFO"
@@ -226,7 +226,7 @@ def test_log_access(caplog):
     assert "boo arg" in caplog.text
 
 
-def test_messages(caplog):
+def test_messages(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("boo %s", "arg")
     logger.info("bar %s\nbaz %s", "arg1", "arg2")
@@ -247,14 +247,14 @@ def test_messages(caplog):
     assert "Exception" not in caplog.messages[-1]
 
 
-def test_record_tuples(caplog):
+def test_record_tuples(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("boo %s", "arg")
 
     assert caplog.record_tuples == [(__name__, logging.INFO, "boo arg")]
 
 
-def test_unicode(caplog):
+def test_unicode(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("bū")
     assert caplog.records[0].levelname == "INFO"
@@ -262,7 +262,7 @@ def test_unicode(caplog):
     assert "bū" in caplog.text
 
 
-def test_clear(caplog):
+def test_clear(caplog: pytest.LogCaptureFixture) -> None:
     caplog.set_level(logging.INFO)
     logger.info("bū")
     assert len(caplog.records)
@@ -273,7 +273,9 @@ def test_clear(caplog):
 
 
 @pytest.fixture
-def logging_during_setup_and_teardown(caplog):
+def logging_during_setup_and_teardown(
+    caplog: pytest.LogCaptureFixture,
+) -> Iterator[None]:
     caplog.set_level("INFO")
     logger.info("a_setup_log")
     yield
@@ -281,7 +283,9 @@ def logging_during_setup_and_teardown(caplog):
     assert [x.message for x in caplog.get_records("teardown")] == ["a_teardown_log"]
 
 
-def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardown):
+def test_caplog_captures_for_all_stages(
+    caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None
+) -> None:
     assert not caplog.records
     assert not caplog.get_records("call")
     logger.info("a_call_log")
@@ -290,25 +294,31 @@ def test_caplog_captures_for_all_stages(caplog, logging_during_setup_and_teardow
     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!
-    assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"}
+    caplog_records = caplog._item.stash[caplog_records_key]
+    assert set(caplog_records) == {"setup", "call"}
 
 
-def test_clear_for_call_stage(caplog, logging_during_setup_and_teardown):
+def test_clear_for_call_stage(
+    caplog: pytest.LogCaptureFixture, logging_during_setup_and_teardown: None
+) -> None:
     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"]
-    assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"}
+    caplog_records = caplog._item.stash[caplog_records_key]
+    assert set(caplog_records) == {"setup", "call"}
 
     caplog.clear()
 
     assert caplog.get_records("call") == []
     assert [x.message for x in caplog.get_records("setup")] == ["a_setup_log"]
-    assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"}
+    caplog_records = caplog._item.stash[caplog_records_key]
+    assert set(caplog_records) == {"setup", "call"}
 
     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"]
-    assert set(caplog._item.stash[caplog_records_key]) == {"setup", "call"}
+    caplog_records = caplog._item.stash[caplog_records_key]
+    assert set(caplog_records) == {"setup", "call"}
 
 
 def test_ini_controls_global_log_level(pytester: Pytester) -> None:

From 44ad1c9811d2ebf540e601ea66b9bebf8ea82969 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 24 Oct 2023 15:04:13 +0000
Subject: [PATCH 38/55] [7.4.x] fix #10447 - consider marks in reverse mro
 order to give base classes priority (#11545)

Co-authored-by: Ronny Pfannschmidt <opensource@ronnypfannschmidt.de>
---
 changelog/10447.bugfix.rst     |  2 ++
 src/_pytest/mark/structures.py |  4 +++-
 testing/test_mark.py           | 37 +++++++++++++++++++++++++++++++++-
 3 files changed, 41 insertions(+), 2 deletions(-)
 create mode 100644 changelog/10447.bugfix.rst

diff --git a/changelog/10447.bugfix.rst b/changelog/10447.bugfix.rst
new file mode 100644
index 00000000000..fff94b28fb5
--- /dev/null
+++ b/changelog/10447.bugfix.rst
@@ -0,0 +1,2 @@
+markers are now considered in the reverse mro order to ensure base  class markers are considered first
+this resolves a regression.
diff --git a/src/_pytest/mark/structures.py b/src/_pytest/mark/structures.py
index 8dbff1dc93a..55620f0429e 100644
--- a/src/_pytest/mark/structures.py
+++ b/src/_pytest/mark/structures.py
@@ -373,7 +373,9 @@ def get_unpacked_marks(
         if not consider_mro:
             mark_lists = [obj.__dict__.get("pytestmark", [])]
         else:
-            mark_lists = [x.__dict__.get("pytestmark", []) for x in obj.__mro__]
+            mark_lists = [
+                x.__dict__.get("pytestmark", []) for x in reversed(obj.__mro__)
+            ]
         mark_list = []
         for item in mark_lists:
             if isinstance(item, list):
diff --git a/testing/test_mark.py b/testing/test_mark.py
index e2d1a40c38a..2767260df8c 100644
--- a/testing/test_mark.py
+++ b/testing/test_mark.py
@@ -1130,6 +1130,41 @@ class C(A, B):
 
     all_marks = get_unpacked_marks(C)
 
-    assert all_marks == [xfail("c").mark, xfail("a").mark, xfail("b").mark]
+    assert all_marks == [xfail("b").mark, xfail("a").mark, xfail("c").mark]
 
     assert get_unpacked_marks(C, consider_mro=False) == [xfail("c").mark]
+
+
+# @pytest.mark.issue("https://github.com/pytest-dev/pytest/issues/10447")
+def test_mark_fixture_order_mro(pytester: Pytester):
+    """This ensures we walk marks of the mro starting with the base classes
+    the action at a distance fixtures are taken as minimal example from a real project
+
+    """
+    foo = pytester.makepyfile(
+        """
+        import pytest
+
+        @pytest.fixture
+        def add_attr1(request):
+            request.instance.attr1 = object()
+
+
+        @pytest.fixture
+        def add_attr2(request):
+            request.instance.attr2 = request.instance.attr1
+
+
+        @pytest.mark.usefixtures('add_attr1')
+        class Parent:
+            pass
+
+
+        @pytest.mark.usefixtures('add_attr2')
+        class TestThings(Parent):
+            def test_attrs(self):
+                assert self.attr1 == self.attr2
+        """
+    )
+    result = pytester.runpytest(foo)
+    result.assert_outcomes(passed=1)

From a0714aa0076f38e6fb8c7321e8bb4f5f33d1792d Mon Sep 17 00:00:00 2001
From: pytest bot <pytestbot@gmail.com>
Date: Tue, 24 Oct 2023 18:43:16 +0000
Subject: [PATCH 39/55] Prepare release version 7.4.3

---
 changelog/10447.bugfix.rst        |  2 --
 changelog/11239.bugfix.rst        |  1 -
 changelog/11439.bugfix.rst        |  1 -
 doc/en/announce/index.rst         |  1 +
 doc/en/announce/release-7.4.3.rst | 19 +++++++++++++++++++
 doc/en/changelog.rst              | 16 ++++++++++++++++
 doc/en/getting-started.rst        |  2 +-
 7 files changed, 37 insertions(+), 5 deletions(-)
 delete mode 100644 changelog/10447.bugfix.rst
 delete mode 100644 changelog/11239.bugfix.rst
 delete mode 100644 changelog/11439.bugfix.rst
 create mode 100644 doc/en/announce/release-7.4.3.rst

diff --git a/changelog/10447.bugfix.rst b/changelog/10447.bugfix.rst
deleted file mode 100644
index fff94b28fb5..00000000000
--- a/changelog/10447.bugfix.rst
+++ /dev/null
@@ -1,2 +0,0 @@
-markers are now considered in the reverse mro order to ensure base  class markers are considered first
-this resolves a regression.
diff --git a/changelog/11239.bugfix.rst b/changelog/11239.bugfix.rst
deleted file mode 100644
index a486224cdda..00000000000
--- a/changelog/11239.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed ``:=`` in asserts impacting unrelated test cases.
diff --git a/changelog/11439.bugfix.rst b/changelog/11439.bugfix.rst
deleted file mode 100644
index b5104b1bcab..00000000000
--- a/changelog/11439.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Handle an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index 39fdfc13776..854666f6725 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,7 @@ Release announcements
    :maxdepth: 2
 
 
+   release-7.4.3
    release-7.4.2
    release-7.4.1
    release-7.4.0
diff --git a/doc/en/announce/release-7.4.3.rst b/doc/en/announce/release-7.4.3.rst
new file mode 100644
index 00000000000..0f319c1e7f0
--- /dev/null
+++ b/doc/en/announce/release-7.4.3.rst
@@ -0,0 +1,19 @@
+pytest-7.4.3
+=======================================
+
+pytest 7.4.3 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Marc Mueller
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index ecfeeb662b6..f9c6130c24f 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -28,6 +28,22 @@ with advance notice in the **Deprecations** section of releases.
 
 .. towncrier release notes start
 
+pytest 7.4.3 (2023-10-24)
+=========================
+
+Bug Fixes
+---------
+
+- `#10447 <https://github.com/pytest-dev/pytest/issues/10447>`_: markers are now considered in the reverse mro order to ensure base  class markers are considered first
+  this resolves a regression.
+
+
+- `#11239 <https://github.com/pytest-dev/pytest/issues/11239>`_: Fixed ``:=`` in asserts impacting unrelated test cases.
+
+
+- `#11439 <https://github.com/pytest-dev/pytest/issues/11439>`_: Handle an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
+
+
 pytest 7.4.2 (2023-09-07)
 =========================
 
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index e426e0c5072..ba6facd4cd9 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -22,7 +22,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 7.4.2
+    pytest 7.4.3
 
 .. _`simpletest`:
 

From 23906106968eb95afbd61adfbc7bbb795fc9aaa9 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Tue, 24 Oct 2023 15:45:08 -0300
Subject: [PATCH 40/55] Tweak changelog.rst

---
 doc/en/changelog.rst | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index f9c6130c24f..121d1708da7 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -34,14 +34,13 @@ pytest 7.4.3 (2023-10-24)
 Bug Fixes
 ---------
 
-- `#10447 <https://github.com/pytest-dev/pytest/issues/10447>`_: markers are now considered in the reverse mro order to ensure base  class markers are considered first
-  this resolves a regression.
+- `#10447 <https://github.com/pytest-dev/pytest/issues/10447>`_: Markers are now considered in the reverse mro order to ensure base  class markers are considered first -- this resolves a regression.
 
 
 - `#11239 <https://github.com/pytest-dev/pytest/issues/11239>`_: Fixed ``:=`` in asserts impacting unrelated test cases.
 
 
-- `#11439 <https://github.com/pytest-dev/pytest/issues/11439>`_: Handle an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
+- `#11439 <https://github.com/pytest-dev/pytest/issues/11439>`_: Handled an edge case where :data:`sys.stderr` might already be closed when :ref:`faulthandler` is tearing down.
 
 
 pytest 7.4.2 (2023-09-07)

From f8070ffb9b0a0f5618190d9311cf2a99e7b18a52 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 25 Oct 2023 13:37:56 +0000
Subject: [PATCH 41/55] [7.4.x] Add deploy instructions using the command-line
 (#11553)

Co-authored-by: Bruno Oliveira <nicoddemus@gmail.com>
---
 scripts/prepare-release-pr.py | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/scripts/prepare-release-pr.py b/scripts/prepare-release-pr.py
index a0e5e4d7f37..8ffa6696466 100644
--- a/scripts/prepare-release-pr.py
+++ b/scripts/prepare-release-pr.py
@@ -31,16 +31,22 @@ class InvalidFeatureRelease(Exception):
 SLUG = "pytest-dev/pytest"
 
 PR_BODY = """\
-Created by the [prepare release pr](https://github.com/pytest-dev/pytest/actions/workflows/prepare-release-pr.yml)
-workflow.
+Created by the [prepare release pr]\
+(https://github.com/pytest-dev/pytest/actions/workflows/prepare-release-pr.yml) workflow.
 
-Once all builds pass and it has been **approved** by one or more maintainers,
-start the [deploy](https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml) workflow, using these parameters:
+Once all builds pass and it has been **approved** by one or more maintainers, start the \
+[deploy](https://github.com/pytest-dev/pytest/actions/workflows/deploy.yml) workflow, using these parameters:
 
 * `Use workflow from`: `release-{version}`.
 * `Release version`: `{version}`.
 
-After the `deploy` workflow has been approved by a core maintainer, the package will be uploaded to PyPI automatically.
+Or execute on the command line:
+
+```console
+gh workflow run deploy.yml -r release-{version} -f version={version}
+```
+
+After the workflow has been approved by a core maintainer, the package will be uploaded to PyPI automatically.
 """
 
 

From a32feda04c706e16411ac0a80a8adfcd575a35ff Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Wed, 25 Oct 2023 13:28:22 -0300
Subject: [PATCH 42/55] Fix tag name generated by deploy workflow (#11550)
 (#11554)

Traditionally pytest publishes tags in the form `X.Y.Z`, however the deploy workflow (copied from somewhere else) published tags in the form `vX.Y.Z`.

This is the root cause of #11548, because it tried to publish the release notes for tag `X.Y.Z` (which did not exist).

Fix #11548

(cherry picked from commit c1728948ac7bcd807a40f9aaae63f2f09f5df5b7)
---
 .github/workflows/deploy.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
index 7340e13664c..09c14e405b1 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -53,8 +53,8 @@ jobs:
       run: |
         git config user.name "pytest bot"
         git config user.email "pytestbot@gmail.com"
-        git tag --annotate --message=v${{ github.event.inputs.version }} v${{ github.event.inputs.version }} ${{ github.sha }}
-        git push origin v${{ github.event.inputs.version }}
+        git tag --annotate --message=v${{ github.event.inputs.version }} ${{ github.event.inputs.version }} ${{ github.sha }}
+        git push origin ${{ github.event.inputs.version }}
 
   release-notes:
 

From ec5bd27cc759228cb57ece3cb549b527ee98dc99 Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Fri, 27 Oct 2023 10:36:51 -0300
Subject: [PATCH 43/55] Update build-and-inspect-python-package action (#11561)
 (#11562)

This should fix the action for Python 3.12.

Ref: hynek/build-and-inspect-python-package#72
(cherry picked from commit 247436819a49a4c21c0926ed0a12bfe7e8166237)
---
 .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 09c14e405b1..9243007b697 100644
--- a/.github/workflows/deploy.yml
+++ b/.github/workflows/deploy.yml
@@ -26,7 +26,7 @@ jobs:
         persist-credentials: false
 
     - name: Build and Check Package
-      uses: hynek/build-and-inspect-python-package@v1.5
+      uses: hynek/build-and-inspect-python-package@v1.5.3
 
   deploy:
     if: github.repository == 'pytest-dev/pytest'
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index a3c2d9ed5a1..de0bfea5234 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@v1.5
+      uses: hynek/build-and-inspect-python-package@v1.5.3
 
   build:
     needs: [package]

From bd068705b1f4ba0db07079feabb869cb2815df7a Mon Sep 17 00:00:00 2001
From: Ran Benita <ran@unusedvar.com>
Date: Mon, 30 Oct 2023 09:01:54 +0200
Subject: [PATCH 44/55] [7.4.x] doc/reference: fix sidebar TOC depth

---
 doc/en/reference/reference.rst | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index 6e11d385d12..ff61e3b49d0 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -1,3 +1,5 @@
+:tocdepth: 3
+
 .. _`api-reference`:
 
 API Reference

From a40dacf6577ae990740e10572582538dfaf357b6 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 14 Nov 2023 15:08:28 +0000
Subject: [PATCH 45/55] [7.4.x] XFAIL
 TestLocalPath.test_make_numbered_dir_multiprocess_safe (#11616)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: Miro Hrončok <miro@hroncok.cz>
---
 testing/_py/test_local.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/testing/_py/test_local.py b/testing/_py/test_local.py
index 348682b5396..fc20bd63b86 100644
--- a/testing/_py/test_local.py
+++ b/testing/_py/test_local.py
@@ -868,6 +868,9 @@ def test_fspath_protocol_other_class(self, fake_fspath_obj):
             py_path.strpath, str_path
         )
 
+    @pytest.mark.xfail(
+        reason="#11603", raises=(error.EEXIST, error.ENOENT), strict=False
+    )
     def test_make_numbered_dir_multiprocess_safe(self, tmpdir):
         # https://github.com/pytest-dev/py/issues/30
         with multiprocessing.Pool() as pool:

From 13024efd7afdbae80ce70d27295d9bbe62670cb8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Wed, 22 Nov 2023 20:25:29 +0000
Subject: [PATCH 46/55] [7.4.x] Fix for operation on closed file in
 faulthandler teardown (#11631)

Co-authored-by: Simon Blanchard <bnomis@gmail.com>
---
 changelog/11572.bugfix.rst  |  1 +
 src/_pytest/faulthandler.py | 20 ++++++++++++++------
 2 files changed, 15 insertions(+), 6 deletions(-)
 create mode 100644 changelog/11572.bugfix.rst

diff --git a/changelog/11572.bugfix.rst b/changelog/11572.bugfix.rst
new file mode 100644
index 00000000000..7a235a071fe
--- /dev/null
+++ b/changelog/11572.bugfix.rst
@@ -0,0 +1 @@
+Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down.
diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py
index 36040bffffc..d8c7e9fd3b6 100644
--- a/src/_pytest/faulthandler.py
+++ b/src/_pytest/faulthandler.py
@@ -9,8 +9,8 @@
 from _pytest.stash import StashKey
 
 
+fault_handler_original_stderr_fd_key = StashKey[int]()
 fault_handler_stderr_fd_key = StashKey[int]()
-fault_handler_originally_enabled_key = StashKey[bool]()
 
 
 def pytest_addoption(parser: Parser) -> None:
@@ -24,8 +24,15 @@ def pytest_addoption(parser: Parser) -> None:
 def pytest_configure(config: Config) -> None:
     import faulthandler
 
-    config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno())
-    config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled()
+    # at teardown we want to restore the original faulthandler fileno
+    # but faulthandler has no api to return the original fileno
+    # so here we stash the stderr fileno to be used at teardown
+    # sys.stderr and sys.__stderr__ may be closed or patched during the session
+    # so we can't rely on their values being good at that point (#11572).
+    stderr_fileno = get_stderr_fileno()
+    if faulthandler.is_enabled():
+        config.stash[fault_handler_original_stderr_fd_key] = stderr_fileno
+    config.stash[fault_handler_stderr_fd_key] = os.dup(stderr_fileno)
     faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key])
 
 
@@ -37,9 +44,10 @@ def pytest_unconfigure(config: Config) -> None:
     if fault_handler_stderr_fd_key in config.stash:
         os.close(config.stash[fault_handler_stderr_fd_key])
         del config.stash[fault_handler_stderr_fd_key]
-    if config.stash.get(fault_handler_originally_enabled_key, False):
-        # Re-enable the faulthandler if it was originally enabled.
-        faulthandler.enable(file=get_stderr_fileno())
+    # Re-enable the faulthandler if it was originally enabled.
+    if fault_handler_original_stderr_fd_key in config.stash:
+        faulthandler.enable(config.stash[fault_handler_original_stderr_fd_key])
+        del config.stash[fault_handler_original_stderr_fd_key]
 
 
 def get_stderr_fileno() -> int:

From 5582bfcddf78929f7979c5023b167b333e1c2dd9 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Thu, 14 Dec 2023 08:19:40 -0300
Subject: [PATCH 47/55] [7.4.x] Improves clarity in Sphinx documentation for
 function signature. (#11702)

Co-authored-by: Arthur Richard <arthur.richard2299@gmail.com>
---
 AUTHORS                        | 1 +
 doc/en/reference/reference.rst | 2 +-
 src/_pytest/outcomes.py        | 2 +-
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/AUTHORS b/AUTHORS
index be1e7863860..cb73916bcaa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -47,6 +47,7 @@ Ariel Pillemer
 Armin Rigo
 Aron Coyle
 Aron Curzon
+Arthur Richard
 Ashish Kurmi
 Aviral Verma
 Aviv Palivoda
diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst
index ff61e3b49d0..c80bdb18eb5 100644
--- a/doc/en/reference/reference.rst
+++ b/doc/en/reference/reference.rst
@@ -79,7 +79,7 @@ pytest.xfail
 pytest.exit
 ~~~~~~~~~~~
 
-.. autofunction:: pytest.exit(reason, [returncode=False, msg=None])
+.. autofunction:: pytest.exit(reason, [returncode=None, msg=None])
 
 pytest.main
 ~~~~~~~~~~~
diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py
index 1be97dda4ea..53c3e1511cb 100644
--- a/src/_pytest/outcomes.py
+++ b/src/_pytest/outcomes.py
@@ -123,7 +123,7 @@ def exit(
         only because `msg` is deprecated.
 
     :param returncode:
-        Return code to be used when exiting pytest.
+        Return code to be used when exiting pytest. None means the same as ``0`` (no error), same as :func:`sys.exit`.
 
     :param msg:
         Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead.

From d06c05bd23ea6af8e07fd944e56c58b64375b724 Mon Sep 17 00:00:00 2001
From: Ran Benita <ran@unusedvar.com>
Date: Sun, 31 Dec 2023 10:14:23 +0200
Subject: [PATCH 48/55] [7.4.x] nodes: fix tracebacks from collection errors
 are not getting pruned

---
 changelog/11710.bugfix.rst |  1 +
 src/_pytest/nodes.py       |  2 +-
 testing/test_collection.py | 23 +++++++++++++++++++++++
 3 files changed, 25 insertions(+), 1 deletion(-)
 create mode 100644 changelog/11710.bugfix.rst

diff --git a/changelog/11710.bugfix.rst b/changelog/11710.bugfix.rst
new file mode 100644
index 00000000000..4bbf9fa2e7d
--- /dev/null
+++ b/changelog/11710.bugfix.rst
@@ -0,0 +1 @@
+Fixed tracebacks from collection errors not getting pruned.
diff --git a/src/_pytest/nodes.py b/src/_pytest/nodes.py
index 667a02b77af..a5313cb7656 100644
--- a/src/_pytest/nodes.py
+++ b/src/_pytest/nodes.py
@@ -567,7 +567,7 @@ def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
             ntraceback = traceback.cut(path=self.path)
             if ntraceback == traceback:
                 ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
-            return excinfo.traceback.filter(excinfo)
+            return ntraceback.filter(excinfo)
         return excinfo.traceback
 
 
diff --git a/testing/test_collection.py b/testing/test_collection.py
index 8b0a1ab3650..5d4b0a75853 100644
--- a/testing/test_collection.py
+++ b/testing/test_collection.py
@@ -345,6 +345,29 @@ def pytest_make_collect_report():
         result = pytester.runpytest(p)
         result.stdout.fnmatch_lines(["*ERROR collecting*", "*header1*"])
 
+    def test_collection_error_traceback_is_clean(self, pytester: Pytester) -> None:
+        """When a collection error occurs, the report traceback doesn't contain
+        internal pytest stack entries.
+
+        Issue #11710.
+        """
+        pytester.makepyfile(
+            """
+            raise Exception("LOUSY")
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(
+            [
+                "*ERROR collecting*",
+                "test_*.py:1: in <module>",
+                '    raise Exception("LOUSY")',
+                "E   Exception: LOUSY",
+                "*= short test summary info =*",
+            ],
+            consecutive=True,
+        )
+
 
 class TestCustomConftests:
     def test_ignore_collect_path(self, pytester: Pytester) -> None:

From b1f3387d42571090ee4a35ec1945765b7f2ffae8 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 31 Dec 2023 11:50:38 +0200
Subject: [PATCH 49/55] [7.4.x] #11091: documentation should use hypthonated
 properties (#11750)

---
 AUTHORS                    | 1 +
 changelog/11091.doc.rst    | 1 +
 changelog/README.rst       | 2 +-
 doc/en/deprecations.rst    | 2 +-
 doc/en/example/markers.rst | 2 +-
 doc/en/example/simple.rst  | 2 +-
 doc/en/how-to/output.rst   | 2 +-
 src/_pytest/junitxml.py    | 2 +-
 src/_pytest/pytester.py    | 2 +-
 testing/python/metafunc.py | 2 +-
 10 files changed, 10 insertions(+), 8 deletions(-)
 create mode 100644 changelog/11091.doc.rst

diff --git a/AUTHORS b/AUTHORS
index cb73916bcaa..f633ef9e995 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -324,6 +324,7 @@ Ronny Pfannschmidt
 Ross Lawley
 Ruaridh Williamson
 Russel Winder
+Ryan Puddephatt
 Ryan Wooden
 Saiprasad Kale
 Samuel Colvin
diff --git a/changelog/11091.doc.rst b/changelog/11091.doc.rst
new file mode 100644
index 00000000000..b5e2af36061
--- /dev/null
+++ b/changelog/11091.doc.rst
@@ -0,0 +1 @@
+Updated documentation to refer to hyphenated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``.
diff --git a/changelog/README.rst b/changelog/README.rst
index 6d026f57ef3..88956ef28d8 100644
--- a/changelog/README.rst
+++ b/changelog/README.rst
@@ -14,7 +14,7 @@ Each file should be named like ``<ISSUE>.<TYPE>.rst``, where
 ``<ISSUE>`` is an issue number, and ``<TYPE>`` is one of:
 
 * ``feature``: new user facing features, like new command-line options and new behavior.
-* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junitxml``, improved colors in terminal, etc).
+* ``improvement``: improvement of existing functionality, usually without requiring user intervention (for example, new fields being written in ``--junit-xml``, improved colors in terminal, etc).
 * ``bugfix``: fixes a bug.
 * ``doc``: documentation improvement, like rewording an entire session or adding missing docs.
 * ``deprecation``: feature deprecation.
diff --git a/doc/en/deprecations.rst b/doc/en/deprecations.rst
index 4f7830a2791..3bb93d81ef6 100644
--- a/doc/en/deprecations.rst
+++ b/doc/en/deprecations.rst
@@ -596,7 +596,7 @@ By using ``legacy`` you will keep using the legacy/xunit1 format when upgrading
 pytest 6.0, where the default format will be ``xunit2``.
 
 In order to let users know about the transition, pytest will issue a warning in case
-the ``--junitxml`` option is given in the command line but ``junit_family`` is not explicitly
+the ``--junit-xml`` option is given in the command line but ``junit_family`` is not explicitly
 configured in ``pytest.ini``.
 
 Services known to support the ``xunit2`` format:
diff --git a/doc/en/example/markers.rst b/doc/en/example/markers.rst
index 55fd1f576cf..6cdf4eb42d8 100644
--- a/doc/en/example/markers.rst
+++ b/doc/en/example/markers.rst
@@ -136,7 +136,7 @@ Or select multiple nodes:
 
     Node IDs for failing tests are displayed in the test summary info
     when running pytest with the ``-rf`` option.  You can also
-    construct Node IDs from the output of ``pytest --collectonly``.
+    construct Node IDs from the output of ``pytest --collect-only``.
 
 Using ``-k expr`` to select tests based on their name
 -------------------------------------------------------
diff --git a/doc/en/example/simple.rst b/doc/en/example/simple.rst
index 32e5188b741..ba2920daf6f 100644
--- a/doc/en/example/simple.rst
+++ b/doc/en/example/simple.rst
@@ -1088,4 +1088,4 @@ application with standard ``pytest`` command-line options:
 
 .. code-block:: bash
 
-    ./app_main --pytest --verbose --tb=long --junitxml=results.xml test-suite/
+    ./app_main --pytest --verbose --tb=long --junit=xml=results.xml test-suite/
diff --git a/doc/en/how-to/output.rst b/doc/en/how-to/output.rst
index e8e9af0c70b..cf72a2d291c 100644
--- a/doc/en/how-to/output.rst
+++ b/doc/en/how-to/output.rst
@@ -478,7 +478,7 @@ integration servers, use this invocation:
 
 .. code-block:: bash
 
-    pytest --junitxml=path
+    pytest --junit-xml=path
 
 to create an XML file at ``path``.
 
diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py
index ed259e4c41d..9ee35b84e84 100644
--- a/src/_pytest/junitxml.py
+++ b/src/_pytest/junitxml.py
@@ -369,7 +369,7 @@ def test_foo(record_testsuite_property):
     __tracebackhide__ = True
 
     def record_func(name: str, value: object) -> None:
-        """No-op function in case --junitxml was not passed in the command-line."""
+        """No-op function in case --junit-xml was not passed in the command-line."""
         __tracebackhide__ = True
         _check_record_param_type("name", name)
 
diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py
index cdfc2c04ae1..0771065e065 100644
--- a/src/_pytest/pytester.py
+++ b/src/_pytest/pytester.py
@@ -1074,7 +1074,7 @@ def inline_runsource(self, source: str, *cmdlineargs) -> HookRecorder:
         return self.inline_run(*values)
 
     def inline_genitems(self, *args) -> Tuple[List[Item], HookRecorder]:
-        """Run ``pytest.main(['--collectonly'])`` in-process.
+        """Run ``pytest.main(['--collect-only'])`` in-process.
 
         Runs the :py:func:`pytest.main` function to run all of pytest inside
         the test process itself like :py:meth:`inline_run`, but returns a
diff --git a/testing/python/metafunc.py b/testing/python/metafunc.py
index a9e9b526934..dfb2dffb55e 100644
--- a/testing/python/metafunc.py
+++ b/testing/python/metafunc.py
@@ -1493,7 +1493,7 @@ def test_foo(x):
                 pass
         """
         )
-        result = pytester.runpytest("--collectonly")
+        result = pytester.runpytest("--collect-only")
         result.stdout.fnmatch_lines(
             [
                 "collected 0 items / 1 error",

From a0f58fa9e7f9b09b212ed491464be5df9b80fc0b Mon Sep 17 00:00:00 2001
From: Zac Hatfield-Dodds <zac.hatfield.dodds@gmail.com>
Date: Tue, 4 Jul 2023 10:47:13 -0700
Subject: [PATCH 50/55] Merge pull request #11143 from
 tushar-deepsource/patch-1

(cherry picked from commit 084d756ae6a03bba7a257ebae3c83095b228988f)

[ran: adapted to 7.4.x, fixed changelog issue number]
---
 AUTHORS                          |  1 +
 changelog/11140.bugfix.rst       |  1 +
 src/_pytest/assertion/rewrite.py | 14 ++++++++++----
 testing/test_assertrewrite.py    | 14 ++++++++++++++
 4 files changed, 26 insertions(+), 4 deletions(-)
 create mode 100644 changelog/11140.bugfix.rst

diff --git a/AUTHORS b/AUTHORS
index cb73916bcaa..1eeb3869adf 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -376,6 +376,7 @@ Tomer Keren
 Tony Narlock
 Tor Colvin
 Trevor Bekolay
+Tushar Sadhwani
 Tyler Goodlet
 Tyler Smart
 Tzu-ping Chung
diff --git a/changelog/11140.bugfix.rst b/changelog/11140.bugfix.rst
new file mode 100644
index 00000000000..cdf0b37c7bf
--- /dev/null
+++ b/changelog/11140.bugfix.rst
@@ -0,0 +1 @@
+Fix non-string constants at the top of file being detected as docstrings on Python>=3.8.
diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py
index fd23552973e..d1974bb3b4a 100644
--- a/src/_pytest/assertion/rewrite.py
+++ b/src/_pytest/assertion/rewrite.py
@@ -604,6 +604,13 @@ def _write_and_reset() -> None:
     return ret
 
 
+def _get_ast_constant_value(value: astStr) -> object:
+    if sys.version_info >= (3, 8):
+        return value.value
+    else:
+        return value.s
+
+
 class AssertionRewriter(ast.NodeVisitor):
     """Assertion rewriting implementation.
 
@@ -700,11 +707,10 @@ def run(self, mod: ast.Module) -> None:
                 expect_docstring
                 and isinstance(item, ast.Expr)
                 and isinstance(item.value, astStr)
+                and isinstance(_get_ast_constant_value(item.value), str)
             ):
-                if sys.version_info >= (3, 8):
-                    doc = item.value.value
-                else:
-                    doc = item.value.s
+                doc = _get_ast_constant_value(item.value)
+                assert isinstance(doc, str)
                 if self.is_rewrite_disabled(doc):
                     return
                 expect_docstring = False
diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index fbf2854953f..2f59ace54fc 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -2077,3 +2077,17 @@ def test_max_increased_verbosity(self, pytester: Pytester) -> None:
         self.create_test_file(pytester, DEFAULT_REPR_MAX_SIZE * 10)
         result = pytester.runpytest("-vv")
         result.stdout.no_fnmatch_line("*xxx...xxx*")
+
+
+class TestIssue11140:
+    def test_constant_not_picked_as_module_docstring(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """\
+            0
+
+            def test_foo():
+                pass
+            """
+        )
+        result = pytester.runpytest()
+        assert result.ret == 0

From 531d76daa4a871df5b2a46cae132851c29abf027 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Sun, 31 Dec 2023 12:11:02 +0200
Subject: [PATCH 51/55] [7.4.x] Improve reporting from __iter__ exceptions
 (#11749)

---
 changelog/7966.bugfix.rst     |  1 +
 src/_pytest/assertion/util.py |  2 +-
 testing/test_assertrewrite.py | 19 +++++++++++++++++++
 3 files changed, 21 insertions(+), 1 deletion(-)
 create mode 100644 changelog/7966.bugfix.rst

diff --git a/changelog/7966.bugfix.rst b/changelog/7966.bugfix.rst
new file mode 100644
index 00000000000..849bdb06e97
--- /dev/null
+++ b/changelog/7966.bugfix.rst
@@ -0,0 +1 @@
+Removed unhelpful error message from assertion rewrite mechanism when exceptions are raised in ``__iter__`` methods. Now they are treated un-iterable instead.
diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py
index fc5dfdbd5ba..39ca5403e04 100644
--- a/src/_pytest/assertion/util.py
+++ b/src/_pytest/assertion/util.py
@@ -132,7 +132,7 @@ def isiterable(obj: Any) -> bool:
     try:
         iter(obj)
         return not istext(obj)
-    except TypeError:
+    except Exception:
         return False
 
 
diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py
index fbf2854953f..f64d7733cdc 100644
--- a/testing/test_assertrewrite.py
+++ b/testing/test_assertrewrite.py
@@ -686,6 +686,25 @@ def myany(x) -> bool:
         assert msg is not None
         assert "<MY42 object> < 0" in msg
 
+    def test_assert_handling_raise_in__iter__(self, pytester: Pytester) -> None:
+        pytester.makepyfile(
+            """\
+            class A:
+                def __iter__(self):
+                    raise ValueError()
+
+                def __eq__(self, o: object) -> bool:
+                    return self is o
+
+                def __repr__(self):
+                    return "<A object>"
+
+            assert A() == A()
+            """
+        )
+        result = pytester.runpytest()
+        result.stdout.fnmatch_lines(["*E*assert <A object> == <A object>"])
+
     def test_formatchar(self) -> None:
         def f() -> None:
             assert "%test" == "test"  # type: ignore[comparison-overlap]

From 33f694f4b30c5c502f21f81cb8ab907b12ad2f65 Mon Sep 17 00:00:00 2001
From: pytest bot <pytestbot@gmail.com>
Date: Sun, 31 Dec 2023 10:17:27 +0000
Subject: [PATCH 52/55] Prepare release version 7.4.4

---
 changelog/11091.doc.rst           |  1 -
 changelog/11140.bugfix.rst        |  1 -
 changelog/11572.bugfix.rst        |  1 -
 changelog/11710.bugfix.rst        |  1 -
 changelog/7966.bugfix.rst         |  1 -
 doc/en/announce/index.rst         |  1 +
 doc/en/announce/release-7.4.4.rst | 20 ++++++++++++++++++++
 doc/en/changelog.rst              | 25 +++++++++++++++++++++++++
 doc/en/getting-started.rst        |  2 +-
 9 files changed, 47 insertions(+), 6 deletions(-)
 delete mode 100644 changelog/11091.doc.rst
 delete mode 100644 changelog/11140.bugfix.rst
 delete mode 100644 changelog/11572.bugfix.rst
 delete mode 100644 changelog/11710.bugfix.rst
 delete mode 100644 changelog/7966.bugfix.rst
 create mode 100644 doc/en/announce/release-7.4.4.rst

diff --git a/changelog/11091.doc.rst b/changelog/11091.doc.rst
deleted file mode 100644
index b5e2af36061..00000000000
--- a/changelog/11091.doc.rst
+++ /dev/null
@@ -1 +0,0 @@
-Updated documentation to refer to hyphenated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``.
diff --git a/changelog/11140.bugfix.rst b/changelog/11140.bugfix.rst
deleted file mode 100644
index cdf0b37c7bf..00000000000
--- a/changelog/11140.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fix non-string constants at the top of file being detected as docstrings on Python>=3.8.
diff --git a/changelog/11572.bugfix.rst b/changelog/11572.bugfix.rst
deleted file mode 100644
index 7a235a071fe..00000000000
--- a/changelog/11572.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down.
diff --git a/changelog/11710.bugfix.rst b/changelog/11710.bugfix.rst
deleted file mode 100644
index 4bbf9fa2e7d..00000000000
--- a/changelog/11710.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Fixed tracebacks from collection errors not getting pruned.
diff --git a/changelog/7966.bugfix.rst b/changelog/7966.bugfix.rst
deleted file mode 100644
index 849bdb06e97..00000000000
--- a/changelog/7966.bugfix.rst
+++ /dev/null
@@ -1 +0,0 @@
-Removed unhelpful error message from assertion rewrite mechanism when exceptions are raised in ``__iter__`` methods. Now they are treated un-iterable instead.
diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst
index 854666f6725..35fd2c814e2 100644
--- a/doc/en/announce/index.rst
+++ b/doc/en/announce/index.rst
@@ -6,6 +6,7 @@ Release announcements
    :maxdepth: 2
 
 
+   release-7.4.4
    release-7.4.3
    release-7.4.2
    release-7.4.1
diff --git a/doc/en/announce/release-7.4.4.rst b/doc/en/announce/release-7.4.4.rst
new file mode 100644
index 00000000000..c9633678d2e
--- /dev/null
+++ b/doc/en/announce/release-7.4.4.rst
@@ -0,0 +1,20 @@
+pytest-7.4.4
+=======================================
+
+pytest 7.4.4 has just been released to PyPI.
+
+This is a bug-fix release, being a drop-in replacement. To upgrade::
+
+  pip install --upgrade pytest
+
+The full changelog is available at https://docs.pytest.org/en/stable/changelog.html.
+
+Thanks to all of the contributors to this release:
+
+* Bruno Oliveira
+* Ran Benita
+* Zac Hatfield-Dodds
+
+
+Happy testing,
+The pytest Development Team
diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst
index 121d1708da7..6973a08c9c6 100644
--- a/doc/en/changelog.rst
+++ b/doc/en/changelog.rst
@@ -28,6 +28,31 @@ with advance notice in the **Deprecations** section of releases.
 
 .. towncrier release notes start
 
+pytest 7.4.4 (2023-12-31)
+=========================
+
+Bug Fixes
+---------
+
+- `#11140 <https://github.com/pytest-dev/pytest/issues/11140>`_: Fix non-string constants at the top of file being detected as docstrings on Python>=3.8.
+
+
+- `#11572 <https://github.com/pytest-dev/pytest/issues/11572>`_: Handle an edge case where :data:`sys.stderr` and :data:`sys.__stderr__` might already be closed when :ref:`faulthandler` is tearing down.
+
+
+- `#11710 <https://github.com/pytest-dev/pytest/issues/11710>`_: Fixed tracebacks from collection errors not getting pruned.
+
+
+- `#7966 <https://github.com/pytest-dev/pytest/issues/7966>`_: Removed unhelpful error message from assertion rewrite mechanism when exceptions are raised in ``__iter__`` methods. Now they are treated un-iterable instead.
+
+
+
+Improved Documentation
+----------------------
+
+- `#11091 <https://github.com/pytest-dev/pytest/issues/11091>`_: Updated documentation to refer to hyphenated options: replaced ``--junitxml`` with ``--junit-xml`` and ``--collectonly`` with ``--collect-only``.
+
+
 pytest 7.4.3 (2023-10-24)
 =========================
 
diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst
index ba6facd4cd9..cd9ef9a66d3 100644
--- a/doc/en/getting-started.rst
+++ b/doc/en/getting-started.rst
@@ -22,7 +22,7 @@ Install ``pytest``
 .. code-block:: bash
 
     $ pytest --version
-    pytest 7.4.3
+    pytest 7.4.4
 
 .. _`simpletest`:
 

From cf72d1a40e2ab81f777c0f30aefdd3eee2030104 Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Mon, 15 Jan 2024 20:18:31 +0000
Subject: [PATCH 53/55] [7.4.x] doc: Update training dates and add pytest
 sprint (#11820)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
---
 doc/en/index.rst | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/doc/en/index.rst b/doc/en/index.rst
index bdee8b5dbd9..b9ad8556f77 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -1,8 +1,12 @@
 :orphan:
 
-.. sidebar:: Next Open Trainings
+.. sidebar:: Next Open Trainings and Events
 
-   - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_, **March 5th to 7th 2024** (3 day in-depth training), **Leipzig, Germany / Remote**
+   - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_ (3 day in-depth training):
+      * **March 5th to 7th 2024**, Leipzig, Germany / Remote
+      * **June 11th to 13th 2024**, Remote
+      * **March 4th to 6th 2025**, Leipzig, Germany / Remote
+   - `pytest development sprint <https://github.com/pytest-dev/pytest/discussions/11655>`_, June 2024 (`date poll <https://nuudel.digitalcourage.de/2tEsEpRcwMNcAXVO>`_)
 
    Also see :doc:`previous talks and blogposts <talks>`.
 

From 549190651e384faede15fcd159a50a953a950e3c Mon Sep 17 00:00:00 2001
From: "github-actions[bot]"
 <41898282+github-actions[bot]@users.noreply.github.com>
Date: Tue, 16 Jan 2024 14:07:26 +0000
Subject: [PATCH 54/55] [7.4.x] doc: Remove sold out training (#11824)

Co-authored-by: Florian Bruhin <me@the-compiler.org>
---
 doc/en/index.rst | 1 -
 1 file changed, 1 deletion(-)

diff --git a/doc/en/index.rst b/doc/en/index.rst
index b9ad8556f77..046c012f95b 100644
--- a/doc/en/index.rst
+++ b/doc/en/index.rst
@@ -3,7 +3,6 @@
 .. sidebar:: Next Open Trainings and Events
 
    - `Professional Testing with Python <https://python-academy.com/courses/python_course_testing.html>`_, via `Python Academy <https://www.python-academy.com/>`_ (3 day in-depth training):
-      * **March 5th to 7th 2024**, Leipzig, Germany / Remote
       * **June 11th to 13th 2024**, Remote
       * **March 4th to 6th 2025**, Leipzig, Germany / Remote
    - `pytest development sprint <https://github.com/pytest-dev/pytest/discussions/11655>`_, June 2024 (`date poll <https://nuudel.digitalcourage.de/2tEsEpRcwMNcAXVO>`_)

From 40d58e0ce7a9a086cfaf0c38de50df1e93097d87 Mon Sep 17 00:00:00 2001
From: James Frost <james.frost@metoffice.gov.uk>
Date: Thu, 30 May 2024 12:08:19 +0100
Subject: [PATCH 55/55] Add html_baseurl to sphinx conf.py (#12364) (#12391)

This is used to set the <link rel="canonical" href="X"> 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 950672930f3..3c4009603a2 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -176,6 +176,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 32f508219a6..27dbfe1ebfd 100644
--- a/doc/en/conf.py
+++ b/doc/en/conf.py
@@ -273,6 +273,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 --------------------------------------------------