Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-pytest for openSUSE:Factory checked in at 2023-09-17 19:28:40 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-pytest (Old) and /work/SRC/openSUSE:Factory/.python-pytest.new.1766 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-pytest" Sun Sep 17 19:28:40 2023 rev:79 rq:1111052 version:7.4.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-pytest/python-pytest.changes 2023-07-27 16:50:59.285875150 +0200 +++ /work/SRC/openSUSE:Factory/.python-pytest.new.1766/python-pytest.changes 2023-09-17 19:28:41.636288656 +0200 @@ -1,0 +2,15 @@ +Thu Sep 7 08:50:26 UTC 2023 - Dirk Müller <dmuel...@suse.com> + +- update to 7.4.1: + * Fixed bug where fake intermediate + modules generated by ``--import-mode=importlib`` would not + include the child modules as attributes of the parent modules. + * Fixed error assertion handling in + :func:`pytest.approx` when ``None`` is an expected or + received value when comparing dictionaries. + * 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. + +------------------------------------------------------------------- Old: ---- pytest-7.4.0.tar.gz New: ---- pytest-7.4.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-pytest.spec ++++++ --- /var/tmp/diff_new_pack.KnkbY3/_old 2023-09-17 19:28:44.272382608 +0200 +++ /var/tmp/diff_new_pack.KnkbY3/_new 2023-09-17 19:28:44.276382750 +0200 @@ -33,7 +33,7 @@ %{?sle15_python_module_pythons} Name: python-pytest%{psuffix} -Version: 7.4.0 +Version: 7.4.1 Release: 0 Summary: Simple powerful testing with Python License: MIT ++++++ pytest-7.4.0.tar.gz -> pytest-7.4.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/.github/workflows/deploy.yml new/pytest-7.4.1/.github/workflows/deploy.yml --- old/pytest-7.4.0/.github/workflows/deploy.yml 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/.github/workflows/deploy.yml 2023-09-02 16:59:58.000000000 +0200 @@ -1,26 +1,23 @@ 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: - - deploy: - if: github.repository == 'pytest-dev/pytest' - + package: runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: write + env: + SETUPTOOLS_SCM_PRETEND_VERSION: ${{ github.event.inputs.version }} + timeout-minutes: 10 steps: - uses: actions/checkout@v3 @@ -31,6 +28,15 @@ - name: Build and Check Package uses: hynek/build-and-inspect-python-package@v1.5 + deploy: + if: github.repository == 'pytest-dev/pytest' + needs: [package] + runs-on: ubuntu-latest + environment: deploy + timeout-minutes: 30 + permissions: + id-token: write + steps: - name: Download Package uses: actions/download-artifact@v3 with: @@ -38,14 +44,35 @@ path: dist - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@v1.8.5 + + - name: Push tag + run: | + git config user.name "pytest bot" + git config user.email "pytest...@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 + # 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: - password: ${{ secrets.pypi_token }} + 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/.github/workflows/test.yml new/pytest-7.4.1/.github/workflows/test.yml --- old/pytest-7.4.0/.github/workflows/test.yml 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/.github/workflows/test.yml 2023-09-02 16:59:58.000000000 +0200 @@ -27,7 +27,19 @@ 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: @@ -38,17 +50,17 @@ 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", @@ -60,7 +72,6 @@ "macos-py310", "macos-py312", - "docs", "doctesting", "plugins", ] @@ -70,15 +81,15 @@ 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 +112,6 @@ 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 +120,10 @@ 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 @@ -159,10 +170,6 @@ 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 @@ 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 @@ - 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/CONTRIBUTING.rst new/pytest-7.4.1/CONTRIBUTING.rst --- old/pytest-7.4.0/CONTRIBUTING.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/CONTRIBUTING.rst 2023-09-02 16:59:58.000000000 +0200 @@ -50,7 +50,7 @@ -------- 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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/PKG-INFO new/pytest-7.4.1/PKG-INFO --- old/pytest-7.4.0/PKG-INFO 2023-06-23 13:17:10.120871300 +0200 +++ new/pytest-7.4.1/PKG-INFO 2023-09-02 17:00:18.888728000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pytest -Version: 7.4.0 +Version: 7.4.1 Summary: pytest: simple powerful testing with Python Home-page: https://docs.pytest.org/en/latest/ Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/RELEASING.rst new/pytest-7.4.1/RELEASING.rst --- old/pytest-7.4.0/RELEASING.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/RELEASING.rst 2023-09-02 16:59:58.000000000 +0200 @@ -133,14 +133,11 @@ 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. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/announce/index.rst new/pytest-7.4.1/doc/en/announce/index.rst --- old/pytest-7.4.0/doc/en/announce/index.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/announce/index.rst 2023-09-02 16:59:58.000000000 +0200 @@ -6,6 +6,7 @@ :maxdepth: 2 + release-7.4.1 release-7.4.0 release-7.3.2 release-7.3.1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/announce/release-7.4.1.rst new/pytest-7.4.1/doc/en/announce/release-7.4.1.rst --- old/pytest-7.4.0/doc/en/announce/release-7.4.1.rst 1970-01-01 01:00:00.000000000 +0100 +++ new/pytest-7.4.1/doc/en/announce/release-7.4.1.rst 2023-09-02 16:59:58.000000000 +0200 @@ -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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/builtin.rst new/pytest-7.4.1/doc/en/builtin.rst --- old/pytest-7.4.0/doc/en/builtin.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/builtin.rst 2023-09-02 16:59:58.000000000 +0200 @@ -22,7 +22,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/changelog.rst new/pytest-7.4.1/doc/en/changelog.rst --- old/pytest-7.4.0/doc/en/changelog.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/changelog.rst 2023-09-02 16:59:58.000000000 +0200 @@ -28,6 +28,23 @@ .. 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/example/nonpython/conftest.py new/pytest-7.4.1/doc/en/example/nonpython/conftest.py --- old/pytest-7.4.0/doc/en/example/nonpython/conftest.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/example/nonpython/conftest.py 2023-09-02 16:59:58.000000000 +0200 @@ -12,7 +12,7 @@ # 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/example/reportingdemo.rst new/pytest-7.4.1/doc/en/example/reportingdemo.rst --- old/pytest-7.4.0/doc/en/example/reportingdemo.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/example/reportingdemo.rst 2023-09-02 16:59:58.000000000 +0200 @@ -554,13 +554,13 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/example/simple.rst new/pytest-7.4.1/doc/en/example/simple.rst --- old/pytest-7.4.0/doc/en/example/simple.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/example/simple.rst 2023-09-02 16:59:58.000000000 +0200 @@ -817,7 +817,7 @@ # 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/getting-started.rst new/pytest-7.4.1/doc/en/getting-started.rst --- old/pytest-7.4.0/doc/en/getting-started.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/getting-started.rst 2023-09-02 16:59:58.000000000 +0200 @@ -22,7 +22,7 @@ .. code-block:: bash $ pytest --version - pytest 7.4.0 + pytest 7.4.1 .. _`simpletest`: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/how-to/assert.rst new/pytest-7.4.1/doc/en/how-to/assert.rst --- old/pytest-7.4.0/doc/en/how-to/assert.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/how-to/assert.rst 2023-09-02 16:59:58.000000000 +0200 @@ -54,14 +54,13 @@ 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. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/how-to/cache.rst new/pytest-7.4.1/doc/en/how-to/cache.rst --- old/pytest-7.4.0/doc/en/how-to/cache.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/how-to/cache.rst 2023-09-02 16:59:58.000000000 +0200 @@ -176,14 +176,21 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/how-to/fixtures.rst new/pytest-7.4.1/doc/en/how-to/fixtures.rst --- old/pytest-7.4.0/doc/en/how-to/fixtures.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/how-to/fixtures.rst 2023-09-02 16:59:58.000000000 +0200 @@ -1698,7 +1698,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/how-to/tmp_path.rst new/pytest-7.4.1/doc/en/how-to/tmp_path.rst --- old/pytest-7.4.0/doc/en/how-to/tmp_path.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/how-to/tmp_path.rst 2023-09-02 16:59:58.000000000 +0200 @@ -24,8 +24,8 @@ 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 @@ -51,8 +51,8 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/how-to/unittest.rst new/pytest-7.4.1/doc/en/how-to/unittest.rst --- old/pytest-7.4.0/doc/en/how-to/unittest.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/how-to/unittest.rst 2023-09-02 16:59:58.000000000 +0200 @@ -207,10 +207,10 @@ @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 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/how-to/usage.rst new/pytest-7.4.1/doc/en/how-to/usage.rst --- old/pytest-7.4.0/doc/en/how-to/usage.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/how-to/usage.rst 2023-09-02 16:59:58.000000000 +0200 @@ -173,7 +173,8 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/index.rst new/pytest-7.4.1/doc/en/index.rst --- old/pytest-7.4.0/doc/en/index.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/index.rst 2023-09-02 16:59:58.000000000 +0200 @@ -2,8 +2,8 @@ .. 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: 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>`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/doc/en/reference/reference.rst new/pytest-7.4.1/doc/en/reference/reference.rst --- old/pytest-7.4.0/doc/en/reference/reference.rst 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/doc/en/reference/reference.rst 2023-09-02 16:59:58.000000000 +0200 @@ -82,6 +82,8 @@ pytest.main ~~~~~~~~~~~ +**Tutorial**: :ref:`pytest.main-usage` + .. autofunction:: pytest.main pytest.param @@ -783,26 +785,18 @@ .. autofunction:: pytest_leave_pdb -Objects -------- +Collection tree objects +----------------------- -Full reference to objects accessible from :ref:`fixtures <fixture>` or :ref:`hooks <hook-reference>`. +These are the collector and item classes (collectively called "nodes") which +make up the collection tree. +Node +~~~~ -CallInfo -~~~~~~~~ - -.. autoclass:: pytest.CallInfo() +.. autoclass:: _pytest.nodes.Node() :members: - -Class -~~~~~ - -.. autoclass:: pytest.Class() - :members: - :show-inheritance: - Collector ~~~~~~~~~ @@ -810,52 +804,52 @@ :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 +867,52 @@ :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 +943,6 @@ .. autoclass:: pytest.Metafunc() :members: -Module -~~~~~~ - -.. autoclass:: pytest.Module() - :members: - :show-inheritance: - -Node -~~~~ - -.. autoclass:: _pytest.nodes.Node() - :members: - Parser ~~~~~~ @@ -941,13 +964,6 @@ :inherited-members: :show-inheritance: -Session -~~~~~~~ - -.. autoclass:: pytest.Session() - :members: - :show-inheritance: - TestReport ~~~~~~~~~~ @@ -962,10 +978,10 @@ .. 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 ~~~~~ @@ -1871,8 +1887,12 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/_version.py new/pytest-7.4.1/src/_pytest/_version.py --- old/pytest-7.4.0/src/_pytest/_version.py 2023-06-23 13:17:09.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/_version.py 2023-09-02 17:00:18.000000000 +0200 @@ -1,4 +1,4 @@ # file generated by setuptools_scm # don't change, don't track in version control -__version__ = version = '7.4.0' -__version_tuple__ = version_tuple = (7, 4, 0) +__version__ = version = '7.4.1' +__version_tuple__ = version_tuple = (7, 4, 1) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/cacheprovider.py new/pytest-7.4.1/src/_pytest/cacheprovider.py --- old/pytest-7.4.0/src/_pytest/cacheprovider.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/cacheprovider.py 2023-09-02 16:59:58.000000000 +0200 @@ -505,7 +505,11 @@ 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.", ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/config/__init__.py new/pytest-7.4.1/src/_pytest/config/__init__.py --- old/pytest-7.4.0/src/_pytest/config/__init__.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/config/__init__.py 2023-09-02 16:59:58.000000000 +0200 @@ -137,7 +137,9 @@ ) -> 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. @@ -442,10 +444,10 @@ # 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: @@ -454,9 +456,9 @@ 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") ) @@ -465,7 +467,7 @@ 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"), @@ -1063,9 +1065,10 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/helpconfig.py new/pytest-7.4.1/src/_pytest/helpconfig.py --- old/pytest-7.4.0/src/_pytest/helpconfig.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/helpconfig.py 2023-09-02 16:59:58.000000000 +0200 @@ -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 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/logging.py new/pytest-7.4.1/src/_pytest/logging.py --- old/pytest-7.4.0/src/_pytest/logging.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/logging.py 2023-09-02 16:59:58.000000000 +0200 @@ -660,6 +660,8 @@ ) 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/main.py new/pytest-7.4.1/src/_pytest/main.py --- old/pytest-7.4.0/src/_pytest/main.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/main.py 2023-09-02 16:59:58.000000000 +0200 @@ -462,6 +462,11 @@ @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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/nodes.py new/pytest-7.4.1/src/_pytest/nodes.py --- old/pytest-7.4.0/src/_pytest/nodes.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/nodes.py 2023-09-02 16:59:58.000000000 +0200 @@ -157,10 +157,11 @@ 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 @@ 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 @@ class FSCollector(Collector): + """Base class for filesystem collectors.""" + def __init__( self, fspath: Optional[LEGACY_PATH] = None, @@ -660,7 +665,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/pathlib.py new/pytest-7.4.1/src/_pytest/pathlib.py --- old/pytest-7.4.0/src/_pytest/pathlib.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/pathlib.py 2023-09-02 16:59:58.000000000 +0200 @@ -523,6 +523,8 @@ 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)]) @@ -633,6 +635,9 @@ 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: @@ -642,13 +647,22 @@ # 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/pytester.py new/pytest-7.4.1/src/_pytest/pytester.py --- old/pytest-7.4.0/src/_pytest/pytester.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/pytester.py 2023-09-02 16:59:58.000000000 +0200 @@ -752,7 +752,7 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/python.py new/pytest-7.4.1/src/_pytest/python.py --- old/pytest-7.4.0/src/_pytest/python.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/python.py 2023-09-02 16:59:58.000000000 +0200 @@ -522,7 +522,7 @@ 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 @@ 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 @@ 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): @@ -1149,7 +1152,7 @@ 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 @@ -1240,8 +1243,9 @@ 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. :: @@ -1673,7 +1677,7 @@ 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 +1834,8 @@ 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") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/_pytest/python_api.py new/pytest-7.4.1/src/_pytest/python_api.py --- old/pytest-7.4.0/src/_pytest/python_api.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/src/_pytest/python_api.py 2023-09-02 16:59:58.000000000 +0200 @@ -266,19 +266,20 @@ 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/pytest.egg-info/PKG-INFO new/pytest-7.4.1/src/pytest.egg-info/PKG-INFO --- old/pytest-7.4.0/src/pytest.egg-info/PKG-INFO 2023-06-23 13:17:09.000000000 +0200 +++ new/pytest-7.4.1/src/pytest.egg-info/PKG-INFO 2023-09-02 17:00:18.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: pytest -Version: 7.4.0 +Version: 7.4.1 Summary: pytest: simple powerful testing with Python Home-page: https://docs.pytest.org/en/latest/ Author: Holger Krekel, Bruno Oliveira, Ronny Pfannschmidt, Floris Bruynooghe, Brianna Laugher, Florian Bruhin and others diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/src/pytest.egg-info/SOURCES.txt new/pytest-7.4.1/src/pytest.egg-info/SOURCES.txt --- old/pytest-7.4.0/src/pytest.egg-info/SOURCES.txt 2023-06-23 13:17:10.000000000 +0200 +++ new/pytest-7.4.1/src/pytest.egg-info/SOURCES.txt 2023-09-02 17:00:18.000000000 +0200 @@ -232,6 +232,7 @@ doc/en/announce/release-7.3.1.rst doc/en/announce/release-7.3.2.rst doc/en/announce/release-7.4.0.rst +doc/en/announce/release-7.4.1.rst doc/en/announce/sprint2016.rst doc/en/example/attic.rst doc/en/example/conftest.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/testing/acceptance_test.py new/pytest-7.4.1/testing/acceptance_test.py --- old/pytest-7.4.0/testing/acceptance_test.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/testing/acceptance_test.py 2023-09-02 16:59:58.000000000 +0200 @@ -1317,3 +1317,38 @@ ) 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 -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/testing/python/approx.py new/pytest-7.4.1/testing/python/approx.py --- old/pytest-7.4.0/testing/python/approx.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/testing/python/approx.py 2023-09-02 16:59:58.000000000 +0200 @@ -123,6 +123,23 @@ ) 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], [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/testing/test_pathlib.py new/pytest-7.4.1/testing/test_pathlib.py --- old/pytest-7.4.0/testing/test_pathlib.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/testing/test_pathlib.py 2023-09-02 16:59:58.000000000 +0200 @@ -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 @@ 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 @@ # 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 ) @@ -592,3 +603,15 @@ 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"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/pytest-7.4.0/testing/test_pluginmanager.py new/pytest-7.4.1/testing/test_pluginmanager.py --- old/pytest-7.4.0/testing/test_pluginmanager.py 2023-06-23 13:16:47.000000000 +0200 +++ new/pytest-7.4.1/testing/test_pluginmanager.py 2023-09-02 16:59:58.000000000 +0200 @@ -242,8 +242,12 @@ 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 @@ 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 @@ 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(