diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 54c74863f84..ecfa3071f58 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,25 +28,29 @@ jobs: fetch-depth: 0 persist-credentials: false + - name: Build and Check Package + uses: hynek/build-and-inspect-python-package@v1.5 + + - name: Download Package + uses: actions/download-artifact@v3 + with: + name: Packages + path: dist + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.pypi_token }} + - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: "3.7" - - name: Install dependencies + - name: Install tox run: | python -m pip install --upgrade pip - pip install --upgrade build tox - - - name: Build package - run: | - python -m build - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@master - with: - user: __token__ - password: ${{ secrets.pypi_token }} + pip install --upgrade tox - name: Publish GitHub release notes env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 524260961b4..cd1ffdbf9d4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,6 +18,11 @@ on: env: PYTEST_ADDOPTS: "--color=yes" +# Cancel running jobs for the same workflow and branch. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + # Set permissions at the job level. permissions: {} @@ -189,3 +194,10 @@ 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/.pre-commit-config.yaml b/.pre-commit-config.yaml index de612d96988..df0c6e338b6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -2,7 +2,7 @@ default_language_version: python: "3.10" repos: - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 22.12.0 hooks: - id: black args: [--safe, --quiet] @@ -44,7 +44,7 @@ repos: - id: reorder-python-imports args: ['--application-directories=.:src', --py37-plus] - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py37-plus] @@ -52,7 +52,7 @@ repos: rev: v2.1.0 hooks: - id: setup-cfg-fmt - args: ["--max-py-version=3.10", "--include-version-classifiers"] + args: ["--max-py-version=3.11", "--include-version-classifiers"] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.9.0 hooks: diff --git a/.readthedocs.yml b/.readthedocs.yml index bc44d38b4c7..b506c5f4039 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -2,9 +2,12 @@ version: 2 python: install: - - requirements: doc/en/requirements.txt - - method: pip - path: . + # Install pytest first, then doc/en/requirements.txt. + # This order is important to honor any pins in doc/en/requirements.txt + # when the pinned library is also a dependency of pytest. + - method: pip + path: . + - requirements: doc/en/requirements.txt build: os: ubuntu-20.04 diff --git a/AUTHORS b/AUTHORS index 7da1f8a0c57..697cca65be5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -88,6 +88,7 @@ Daniel Grana Daniel Hahler Daniel Nuri Daniel Sánchez Castelló +Daniel Valenzuela Zenteno Daniel Wandschneider Daniele Procida Danielle Jenkins @@ -285,6 +286,7 @@ Prashant Sharma Pulkit Goyal Punyashloka Biswal Quentin Pradet +q0w Ralf Schmitt Ram Rachum Ralph Giles @@ -310,6 +312,7 @@ Samuel Searles-Bryant Samuele Pedroni Sanket Duthade Sankt Petersbug +Saravanan Padmanaban Segev Finer Serhii Mozghovyi Seth Junot @@ -341,6 +344,7 @@ Thomas Grainger Thomas Hisch Tim Hoffmann Tim Strazny +TJ Bruno Tobias Diez Tom Dalton Tom Viner @@ -372,6 +376,8 @@ Xixi Zhao Xuan Luong Xuecong Liao Yoav Caspi +Yuliang Shao +Yusuke Kadowaki Yuval Shimon Zac Hatfield-Dodds Zachary Kneupper diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index 021cbd1af39..fa57ca5c8af 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,8 @@ Release announcements :maxdepth: 2 + release-7.2.2 + release-7.2.1 release-7.2.0 release-7.1.3 release-7.1.2 diff --git a/doc/en/announce/release-7.2.1.rst b/doc/en/announce/release-7.2.1.rst new file mode 100644 index 00000000000..80ac7aff07f --- /dev/null +++ b/doc/en/announce/release-7.2.1.rst @@ -0,0 +1,25 @@ +pytest-7.2.1 +======================================= + +pytest 7.2.1 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Anthony Sottile +* Bruno Oliveira +* Daniel Valenzuela +* Kadino +* Prerak Patel +* Ronny Pfannschmidt +* Santiago Castro +* s-padmanaban + + +Happy testing, +The pytest Development Team diff --git a/doc/en/announce/release-7.2.2.rst b/doc/en/announce/release-7.2.2.rst new file mode 100644 index 00000000000..b34a6ff5c1e --- /dev/null +++ b/doc/en/announce/release-7.2.2.rst @@ -0,0 +1,25 @@ +pytest-7.2.2 +======================================= + +pytest 7.2.2 has just been released to PyPI. + +This is a bug-fix release, being a drop-in replacement. To upgrade:: + + pip install --upgrade pytest + +The full changelog is available at https://docs.pytest.org/en/stable/changelog.html. + +Thanks to all of the contributors to this release: + +* Bruno Oliveira +* Garvit Shubham +* Mahesh Vashishtha +* Ramsey +* Ronny Pfannschmidt +* Teejay +* q0w +* vin01 + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 1f55ea156fc..453be4ad320 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:509 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:1351 + pytestconfig [session scope] -- .../_pytest/fixtures.py:1356 Session-scoped fixture that returns the session's :class:`pytest.Config` object. diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 53ddafc52e7..020e6289e48 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,63 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.2.2 (2023-03-03) +========================= + +Bug Fixes +--------- + +- `#10533 `_: Fixed :func:`pytest.approx` handling of dictionaries containing one or more values of `0.0`. + + +- `#10592 `_: Fixed crash if `--cache-show` and `--help` are passed at the same time. + + +- `#10597 `_: Fixed bug where a fixture method named ``teardown`` would be called as part of ``nose`` teardown stage. + + +- `#10626 `_: Fixed crash if ``--fixtures`` and ``--help`` are passed at the same time. + + +- `#10660 `_: Fixed :py:func:`pytest.raises` to return a 'ContextManager' so that type-checkers could narrow + :code:`pytest.raises(...) if ... else nullcontext()` down to 'ContextManager' rather than 'object'. + + + +Improved Documentation +---------------------- + +- `#10690 `_: Added `CI` and `BUILD_NUMBER` environment variables to the documentation. + + +- `#10721 `_: Fixed entry-points declaration in the documentation example using Hatch. + + +- `#10753 `_: Changed wording of the module level skip to be very explicit + about not collecting tests and not executing the rest of the module. + + +pytest 7.2.1 (2023-01-13) +========================= + +Bug Fixes +--------- + +- `#10452 `_: Fix 'importlib.abc.TraversableResources' deprecation warning in Python 3.12. + + +- `#10457 `_: If a test is skipped from inside a fixture, the test summary now shows the test location instead of the fixture location. + + +- `#10506 `_: Fix bug where sometimes pytest would use the file system root directory as :ref:`rootdir ` on Windows. + + +- `#10607 `_: Fix a race condition when creating junitxml reports, which could occur when multiple instances of pytest execute in parallel. + + +- `#10641 `_: Fix a race condition when creating or updating the stepwise plugin's cache, which could occur when multiple xdist worker nodes try to simultaneously update the stepwise plugin's cache. + + pytest 7.2.0 (2022-10-23) ========================= @@ -57,6 +114,7 @@ Deprecations .. _`with-setup-nose`: https://nose.readthedocs.io/en/latest/testing_tools.html?highlight=with_setup#nose.tools.with_setup +- `#7337 `_: A deprecation warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. The plan is to make returning non-`None` from tests an error in the future. Features @@ -79,10 +137,7 @@ Improvements - `#10381 `_: The ``--no-showlocals`` flag has been added. This can be passed directly to tests to override ``--showlocals`` declared through ``addopts``. -- `#3426 `_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expresed instead. - - -- `#7337 `_: A warning is now emitted if a test function returns something other than `None`. This prevents a common mistake among beginners that expect that returning a `bool` (for example `return foo(a, b) == result`) would cause a test to pass or fail, instead of using `assert`. +- `#3426 `_: Assertion failures with strings in NFC and NFD forms that normalize to the same string now have a dedicated error message detailing the issue, and their utf-8 representation is expressed instead. - `#8508 `_: Introduce multiline display for warning matching via :py:func:`pytest.warns` and @@ -95,7 +150,7 @@ Improvements - `#9741 `_: On Python 3.11, use the standard library's :mod:`tomllib` to parse TOML. - :mod:`tomli`` is no longer a dependency on Python 3.11. + :mod:`tomli` is no longer a dependency on Python 3.11. - `#9742 `_: Display assertion message without escaped newline characters with ``-vv``. @@ -110,7 +165,7 @@ Improvements - `#9883 `_: Normalize the help description of all command-line options. -- `#9920 `_: Display full crash messages in ``short test summary info``, when runng in a CI environment. +- `#9920 `_: Display full crash messages in ``short test summary info``, when running in a CI environment. - `#9987 `_: Added support for hidden configuration file by allowing ``.pytest.ini`` as an alternative to ``pytest.ini``. @@ -156,9 +211,6 @@ Improved Documentation Trivial/Internal Changes ------------------------ -- `#10196 `_: :class:`~pytest.PytestReturnNotNoneWarning` is now a subclass of :class:`~pytest.PytestRemovedIn8Warning`: the plan is to make returning non-``None`` from tests an error in the future. - - - `#10313 `_: Made ``_pytest.doctest.DoctestItem`` export ``pytest.DoctestItem`` for type check and runtime purposes. Made `_pytest.doctest` use internal APIs to avoid circular imports. @@ -173,7 +225,7 @@ Trivial/Internal Changes - `#9984 `_: Improve the error message when we attempt to access a fixture that has been torn down. Add an additional sentence to the docstring explaining when it's not a good - idea to call getfixturevalue. + idea to call ``getfixturevalue``. pytest 7.1.3 (2022-08-31) diff --git a/doc/en/example/fixtures/test_fixtures_order_dependencies.py b/doc/en/example/fixtures/test_fixtures_order_dependencies.py index b3512c2a64d..e76e3f93c62 100644 --- a/doc/en/example/fixtures/test_fixtures_order_dependencies.py +++ b/doc/en/example/fixtures/test_fixtures_order_dependencies.py @@ -17,7 +17,7 @@ def b(a, order): @pytest.fixture -def c(a, b, order): +def c(b, order): order.append("c") diff --git a/doc/en/example/parametrize.rst b/doc/en/example/parametrize.rst index f8147683975..df2859b14e0 100644 --- a/doc/en/example/parametrize.rst +++ b/doc/en/example/parametrize.rst @@ -504,9 +504,9 @@ Running it results in some skips if we don't have all the python interpreters in . $ pytest -rs -q multipython.py sssssssssssssssssssssssssss [100%] ========================= short test summary info ========================== - SKIPPED [9] multipython.py:29: 'python3.5' not found - SKIPPED [9] multipython.py:29: 'python3.6' not found - SKIPPED [9] multipython.py:29: 'python3.7' not found + SKIPPED [9] multipython.py:69: 'python3.5' not found + SKIPPED [9] multipython.py:69: 'python3.6' not found + SKIPPED [9] multipython.py:69: 'python3.7' not found 27 skipped in 0.12s Indirect parametrization of optional implementations/imports @@ -574,7 +574,7 @@ If you run this with reporting for skips enabled: test_module.py .s [100%] ========================= short test summary info ========================== - SKIPPED [1] conftest.py:12: could not import 'opt2': No module named 'opt2' + SKIPPED [1] test_module.py:3: could not import 'opt2': No module named 'opt2' ======================= 1 passed, 1 skipped in 0.12s ======================= You'll see that we don't have an ``opt2`` module and thus the second test run diff --git a/doc/en/explanation/goodpractices.rst b/doc/en/explanation/goodpractices.rst index 6b3af861a0d..6b32875d3d1 100644 --- a/doc/en/explanation/goodpractices.rst +++ b/doc/en/explanation/goodpractices.rst @@ -270,8 +270,8 @@ tox Once you are done with your work and want to make sure that your actual package passes all tests you may want to look into :doc:`tox `, the -virtualenv test automation tool and its :doc:`pytest support `. -tox helps you to setup virtualenv environments with pre-defined +virtualenv test automation tool. +``tox`` helps you to setup virtualenv environments with pre-defined dependencies and then executing a pre-configured test command with options. It will run tests against the installed package and not against your source code checkout, helping to detect packaging diff --git a/doc/en/explanation/pythonpath.rst b/doc/en/explanation/pythonpath.rst index e1d240b498a..5b533f47fdc 100644 --- a/doc/en/explanation/pythonpath.rst +++ b/doc/en/explanation/pythonpath.rst @@ -16,7 +16,7 @@ import process can be controlled through the ``--import-mode`` command-line flag these values: * ``prepend`` (default): the directory path containing each module will be inserted into the *beginning* - of :py:data:`sys.path` if not already there, and then imported with the :func:`__import__ <__import__>` builtin. + of :py:data:`sys.path` if not already there, and then imported with the :func:`importlib.import_module ` function. This requires test module names to be unique when the test directory tree is not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing. @@ -24,7 +24,7 @@ these values: This is the classic mechanism, dating back from the time Python 2 was still supported. * ``append``: the directory containing each module is appended to the end of :py:data:`sys.path` if not already - there, and imported with ``__import__``. + there, and imported with :func:`importlib.import_module `. This better allows to run test modules against installed versions of a package even if the package under test has the same import root. For example: @@ -43,7 +43,7 @@ these values: Same as ``prepend``, requires test module names to be unique when the test directory tree is not arranged in packages, because the modules will put in :py:data:`sys.modules` after importing. -* ``importlib``: new in pytest-6.0, this mode uses :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`. +* ``importlib``: new in pytest-6.0, this mode uses more fine control mechanisms provided by :mod:`importlib` to import test modules. This gives full control over the import process, and doesn't require changing :py:data:`sys.path`. For this reason this doesn't require test module names to be unique. diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index 971982c2ce8..b36c2e3ccae 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.2.0 + pytest 7.2.2 .. _`simpletest`: diff --git a/doc/en/how-to/capture-warnings.rst b/doc/en/how-to/capture-warnings.rst index 91565002ccc..0390230b8ea 100644 --- a/doc/en/how-to/capture-warnings.rst +++ b/doc/en/how-to/capture-warnings.rst @@ -109,6 +109,18 @@ When a warning matches more than one option in the list, the action for the last is performed. +.. note:: + + The ``-W`` flag and the ``filterwarnings`` ini option use warning filters that are + similar in structure, but each configuration option interprets its filter + differently. For example, *message* in ``filterwarnings`` is a string containing a + regular expression that the start of the warning message must match, + case-insensitively, while *message* in ``-W`` is a literal string that the start of + the warning message must contain (case-insensitively), ignoring any whitespace at + the start or end of message. Consult the `warning filter`_ documentation for more + details. + + .. _`filterwarnings`: ``@pytest.mark.filterwarnings`` @@ -270,20 +282,34 @@ which works in a similar manner to :ref:`raises ` (except that warnings.warn("my warning", UserWarning) The test will fail if the warning in question is not raised. Use the keyword -argument ``match`` to assert that the warning matches a text or regex:: +argument ``match`` to assert that the warning matches a text or regex. +To match a literal string that may contain regular expression metacharacters like ``(`` or ``.``, the pattern can +first be escaped with ``re.escape``. - >>> with warns(UserWarning, match='must be 0 or None'): +Some examples: + +.. code-block:: pycon + + + >>> with warns(UserWarning, match="must be 0 or None"): ... warnings.warn("value must be 0 or None", UserWarning) + ... - >>> with warns(UserWarning, match=r'must be \d+$'): + >>> with warns(UserWarning, match=r"must be \d+$"): ... warnings.warn("value must be 42", UserWarning) + ... - >>> with warns(UserWarning, match=r'must be \d+$'): + >>> with warns(UserWarning, match=r"must be \d+$"): ... warnings.warn("this is not here", UserWarning) + ... Traceback (most recent call last): ... Failed: DID NOT WARN. No warnings of type ...UserWarning... were emitted... + >>> with warns(UserWarning, match=re.escape("issue with foo() func")): + ... warnings.warn("issue with foo() func") + ... + You can also call :func:`pytest.warns` on a function or code string: .. code-block:: python diff --git a/doc/en/how-to/writing_plugins.rst b/doc/en/how-to/writing_plugins.rst index f15b69c2317..4eee1b2e120 100644 --- a/doc/en/how-to/writing_plugins.rst +++ b/doc/en/how-to/writing_plugins.rst @@ -167,13 +167,8 @@ it in your ``pyproject.toml`` file. "Framework :: Pytest", ] - [tool.setuptools] - packages = ["myproject"] - - [project.entry_points] - pytest11 = [ - "myproject = myproject.pluginmodule", - ] + [project.entry-points.pytest11] + myproject = "myproject.pluginmodule" If a package is installed this way, ``pytest`` will load ``myproject.pluginmodule`` as a plugin which can define diff --git a/doc/en/index.rst b/doc/en/index.rst index 60fbf99aed6..c277cc30721 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,8 +2,7 @@ .. sidebar:: Next Open Trainings - - Professionelles Testen für Python mit pytest, part of `enterPy `__ (German), `October 28th `__ (sold out) and `November 4th `__, online - - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote and Leipzig, Germany + - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote Also see :doc:`previous talks and blogposts `. diff --git a/doc/en/reference/fixtures.rst b/doc/en/reference/fixtures.rst index d25979ab95d..01f825222ea 100644 --- a/doc/en/reference/fixtures.rst +++ b/doc/en/reference/fixtures.rst @@ -335,7 +335,7 @@ For example: .. literalinclude:: /example/fixtures/test_fixtures_order_dependencies.py -If we map out what depends on what, we get something that look like this: +If we map out what depends on what, we get something that looks like this: .. image:: /example/fixtures/test_fixtures_order_dependencies.* :align: center diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index b7bd4d1199a..bbad27e09cd 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1047,6 +1047,14 @@ Environment Variables Environment variables that can be used to change pytest's behavior. +.. envvar:: CI + +When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to ``BUILD_NUMBER`` variable. + +.. envvar:: BUILD_NUMBER + +When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to CI variable. + .. envvar:: PYTEST_ADDOPTS This contains a command-line (parsed by the py:mod:`shlex` module) that will be **prepended** to the command line given diff --git a/doc/en/requirements.txt b/doc/en/requirements.txt index a0d54cd4c1d..b6059723cd5 100644 --- a/doc/en/requirements.txt +++ b/doc/en/requirements.txt @@ -1,7 +1,11 @@ pallets-sphinx-themes pluggy>=1.0 -pygments-pytest>=2.2.0 +pygments-pytest>=2.3.0 sphinx-removed-in>=0.2.0 sphinx>=5,<6 sphinxcontrib-trio sphinxcontrib-svg2pdfconverter +# Pin packaging because it no longer handles 'latest' version, which +# is the version that is assigned to the docs. +# See https://github.com/pytest-dev/pytest/pull/10578#issuecomment-1348249045. +packaging <22 diff --git a/pyproject.toml b/pyproject.toml index fc9a119f6f0..a4139a5c051 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,3 +114,8 @@ template = "changelog/_template.rst" [tool.black] target-version = ['py37'] + +# check-wheel-contents is executed by the build-and-inspect-python-package action. +[tool.check-wheel-contents] +# W009: Wheel contains multiple toplevel library entries +ignore = "W009" diff --git a/setup.cfg b/setup.cfg index 39ade4dff4c..ceb02877af5 100644 --- a/setup.cfg +++ b/setup.cfg @@ -21,6 +21,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Software Development :: Libraries Topic :: Software Development :: Testing Topic :: Utilities diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 63f9dd8f27b..cb64a33c078 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -275,7 +275,12 @@ def get_data(self, pathname: Union[str, bytes]) -> bytes: if sys.version_info >= (3, 10): - def get_resource_reader(self, name: str) -> importlib.abc.TraversableResources: # type: ignore + if sys.version_info >= (3, 12): + from importlib.resources.abc import TraversableResources + else: + from importlib.abc import TraversableResources + + def get_resource_reader(self, name: str) -> TraversableResources: # type: ignore if sys.version_info < (3, 11): from importlib.readers import FileReader else: diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 777c1b0b05a..c236dd4176b 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -32,7 +32,6 @@ from _pytest.python import Package from _pytest.reports import TestReport - README_CONTENT = """\ # pytest cache directory # @@ -492,7 +491,7 @@ def pytest_addoption(parser: Parser) -> None: def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]: - if config.option.cacheshow: + if config.option.cacheshow and not config.option.help: from _pytest.main import wrap_session return wrap_session(config, cacheshow) diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 25f156f8b20..bd2611df6b1 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -998,6 +998,8 @@ def __init__( self.hook.pytest_addoption.call_historic( kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) ) + self.args_source = Config.ArgsSource.ARGS + self.args: List[str] = [] if TYPE_CHECKING: from _pytest.cacheprovider import Cache @@ -1337,8 +1339,8 @@ def _get_unknown_ini_keys(self) -> List[str]: def parse(self, args: List[str], addopts: bool = True) -> None: # Parse given cmdline arguments into this config object. - assert not hasattr( - self, "args" + assert ( + self.args == [] ), "can only parse cmdline args at most once per Config object" self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager) diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 43c2367793e..234b9e12906 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -203,8 +203,7 @@ def determine_setup( else: cwd = Path.cwd() rootdir = get_common_ancestor([cwd, ancestor]) - is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" - if is_fs_root: + if is_fs_root(rootdir): rootdir = ancestor if rootdir_cmd_arg: rootdir = absolutepath(os.path.expandvars(rootdir_cmd_arg)) @@ -216,3 +215,11 @@ def determine_setup( ) assert rootdir is not None return rootdir, inipath, inicfg or {} + + +def is_fs_root(p: Path) -> bool: + r""" + Return True if the given path is pointing to the root of the + file system ("/" on Unix and "C:\\" on Windows for example). + """ + return os.path.splitdrive(str(p))[1] == os.sep diff --git a/src/_pytest/fixtures.py b/src/_pytest/fixtures.py index d79895c262b..7ef261b969a 100644 --- a/src/_pytest/fixtures.py +++ b/src/_pytest/fixtures.py @@ -58,6 +58,7 @@ from _pytest.mark import ParameterSet from _pytest.mark.structures import MarkDecorator from _pytest.outcomes import fail +from _pytest.outcomes import skip from _pytest.outcomes import TEST_OUTCOME from _pytest.pathlib import absolutepath from _pytest.pathlib import bestrelpath @@ -1129,6 +1130,10 @@ def pytest_fixture_setup( except TEST_OUTCOME: exc_info = sys.exc_info() assert exc_info[0] is not None + if isinstance( + exc_info[1], skip.Exception + ) and not fixturefunc.__name__.startswith("xunit_setup"): + exc_info[1]._use_item_location = True # type: ignore[attr-defined] fixturedef.cached_result = (None, my_cache_key, exc_info) raise fixturedef.cached_result = (result, my_cache_key, None) diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index 7a5170f328b..9242d46d9df 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -645,8 +645,8 @@ def pytest_sessionstart(self) -> None: def pytest_sessionfinish(self) -> None: dirname = os.path.dirname(os.path.abspath(self.logfile)) - if not os.path.isdir(dirname): - os.makedirs(dirname) + # exist_ok avoids filesystem race conditions between checking path existence and requesting creation + os.makedirs(dirname, exist_ok=True) with open(self.logfile, "w", encoding="utf-8") as logfile: suite_stop_time = timing.time() diff --git a/src/_pytest/outcomes.py b/src/_pytest/outcomes.py index e46b663dd50..3efd1de7fbb 100644 --- a/src/_pytest/outcomes.py +++ b/src/_pytest/outcomes.py @@ -157,8 +157,12 @@ def skip( The message to show the user as reason for the skip. :param allow_module_level: - Allows this function to be called at module level, skipping the rest - of the module. Defaults to False. + Allows this function to be called at module level. + Raising the skip exception at module level will stop + the execution of the module and prevent the collection of all tests in the module, + even those defined before the `skip` call. + + Defaults to False. :param msg: Same as ``reason``, but deprecated. Will be removed in a future version, use ``reason`` instead. diff --git a/src/_pytest/pathlib.py b/src/_pytest/pathlib.py index c5a411b5963..3086fa78250 100644 --- a/src/_pytest/pathlib.py +++ b/src/_pytest/pathlib.py @@ -464,14 +464,14 @@ def import_path( * `mode == ImportMode.prepend`: the directory containing the module (or package, taking `__init__.py` files into account) will be put at the *start* of `sys.path` before - being imported with `__import__. + being imported with `importlib.import_module`. * `mode == ImportMode.append`: same as `prepend`, but the directory will be appended to the end of `sys.path`, if not already in `sys.path`. * `mode == ImportMode.importlib`: uses more fine control mechanisms provided by `importlib` - to import the module, which avoids having to use `__import__` and muck with `sys.path` - at all. It effectively allows having same-named test modules in different places. + to import the module, which avoids having to muck with `sys.path` at all. It effectively + allows having same-named test modules in different places. :param root: Used as an anchor when mode == ImportMode.importlib to obtain diff --git a/src/_pytest/python.py b/src/_pytest/python.py index 1e30d42ce9c..75d64fbdf67 100644 --- a/src/_pytest/python.py +++ b/src/_pytest/python.py @@ -848,7 +848,7 @@ def _inject_setup_class_fixture(self) -> None: other fixtures (#517). """ setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",)) - teardown_class = getattr(self.obj, "teardown_class", None) + teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",)) if setup_class is None and teardown_class is None: return @@ -885,12 +885,12 @@ def _inject_setup_method_fixture(self) -> None: emit_nose_setup_warning = True setup_method = _get_first_non_fixture_func(self.obj, (setup_name,)) teardown_name = "teardown_method" - teardown_method = getattr(self.obj, teardown_name, None) + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) emit_nose_teardown_warning = False if teardown_method is None and has_nose: teardown_name = "teardown" emit_nose_teardown_warning = True - teardown_method = getattr(self.obj, teardown_name, None) + teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,)) if setup_method is None and teardown_method is None: return diff --git a/src/_pytest/python_api.py b/src/_pytest/python_api.py index 515d437f0d8..7afc2d96570 100644 --- a/src/_pytest/python_api.py +++ b/src/_pytest/python_api.py @@ -8,7 +8,7 @@ from typing import Any from typing import Callable from typing import cast -from typing import Generic +from typing import ContextManager from typing import List from typing import Mapping from typing import Optional @@ -269,10 +269,16 @@ def _repr_compare(self, other_side: Mapping[object, float]) -> List[str]: max_abs_diff = max( max_abs_diff, abs(approx_value.expected - other_value) ) - max_rel_diff = max( - max_rel_diff, - abs((approx_value.expected - other_value) / approx_value.expected), - ) + if approx_value.expected == 0.0: + max_rel_diff = math.inf + else: + max_rel_diff = max( + max_rel_diff, + abs( + (approx_value.expected - other_value) + / approx_value.expected + ), + ) different_ids.append(approx_key) message_data = [ @@ -957,7 +963,7 @@ def raises( # noqa: F811 @final -class RaisesContext(Generic[E]): +class RaisesContext(ContextManager[_pytest._code.ExceptionInfo[E]]): def __init__( self, expected_exception: Union[Type[E], Tuple[Type[E], ...]], diff --git a/src/_pytest/stepwise.py b/src/_pytest/stepwise.py index 84f1a6ce8fe..74ad9dbd4dd 100644 --- a/src/_pytest/stepwise.py +++ b/src/_pytest/stepwise.py @@ -48,6 +48,10 @@ def pytest_configure(config: Config) -> None: def pytest_sessionfinish(session: Session) -> None: if not session.config.getoption("stepwise"): assert session.config.cache is not None + if hasattr(session.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return # Clear the list of failing tests if the plugin is not active. session.config.cache.set(STEPWISE_CACHE_DIR, []) @@ -119,4 +123,8 @@ def pytest_report_collectionfinish(self) -> Optional[str]: return None def pytest_sessionfinish(self) -> None: + if hasattr(self.config, "workerinput"): + # Do not update cache if this process is a xdist worker to prevent + # race conditions (#10641). + return self.cache.set(STEPWISE_CACHE_DIR, self.lastfailed) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index c7139b538b2..73d0cc785c2 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -694,7 +694,14 @@ def test_cmdline_python_namespace_package( # mixed module and filenames: monkeypatch.chdir("world") - result = pytester.runpytest("--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world") + + # pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages. + # While we could change the test to use implicit namespace packages, seems better + # to still ensure the old declaration via declare_namespace still works. + ignore_w = r"-Wignore:Deprecated call to `pkg_resources.declare_namespace" + result = pytester.runpytest( + "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w + ) assert result.ret == 0 result.stdout.fnmatch_lines( [ diff --git a/testing/conftest.py b/testing/conftest.py index 107aad86b25..8a9816799d9 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -157,6 +157,7 @@ class ColorMapping: "number": "\x1b[94m", "str": "\x1b[33m", "print": "\x1b[96m", + "endline": "\x1b[90m\x1b[39;49;00m", } RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} diff --git a/testing/io/test_terminalwriter.py b/testing/io/test_terminalwriter.py index 6fe718b5341..b5a04a99f18 100644 --- a/testing/io/test_terminalwriter.py +++ b/testing/io/test_terminalwriter.py @@ -254,7 +254,7 @@ def test_combining(self) -> None: pytest.param( True, True, - "{kw}assert{hl-reset} {number}0{hl-reset}\n", + "{kw}assert{hl-reset} {number}0{hl-reset}{endline}\n", id="with markup and code_highlight", ), pytest.param( diff --git a/testing/python/approx.py b/testing/python/approx.py index 6acb466ffb1..631e52b56ac 100644 --- a/testing/python/approx.py +++ b/testing/python/approx.py @@ -630,6 +630,19 @@ def test_dict_nonnumeric(self): def test_dict_vs_other(self): assert 1 != approx({"a": 0}) + def test_dict_for_div_by_zero(self, assert_approx_raises_regex): + assert_approx_raises_regex( + {"foo": 42.0}, + {"foo": 0.0}, + [ + r" comparison failed. Mismatched elements: 1 / 1:", + rf" Max absolute difference: {SOME_FLOAT}", + r" Max relative difference: inf", + r" Index \| Obtained\s+\| Expected ", + rf" foo | {SOME_FLOAT} \| {SOME_FLOAT} ± {SOME_FLOAT}", + ], + ) + def test_numpy_array(self): np = pytest.importorskip("numpy") diff --git a/testing/python/fixtures.py b/testing/python/fixtures.py index 3ce5cb34ddd..d996f80bb93 100644 --- a/testing/python/fixtures.py +++ b/testing/python/fixtures.py @@ -3338,6 +3338,10 @@ def test_funcarg_compat(self, pytester: Pytester) -> None: config = pytester.parseconfigure("--funcargs") assert config.option.showfixtures + def test_show_help(self, pytester: Pytester) -> None: + result = pytester.runpytest("--fixtures", "--help") + assert not result.ret + def test_show_fixtures(self, pytester: Pytester) -> None: result = pytester.runpytest("--fixtures") result.stdout.fnmatch_lines( diff --git a/testing/test_assertion.py b/testing/test_assertion.py index d8844f2e41d..75745922108 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -1664,15 +1664,7 @@ def test_raising_repr(): """ ) result = pytester.runpytest() - if sys.version_info >= (3, 11): - # python 3.11 has native support for un-str-able exceptions - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) - else: - result.stdout.fnmatch_lines( - ["E AssertionError: "] - ) + result.stdout.fnmatch_lines(["E AssertionError: "]) def test_issue_1944(pytester: Pytester) -> None: diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 2baa3c8f189..c381a8448ce 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -1249,3 +1249,8 @@ def test_cachedir_tag(pytester: Pytester) -> None: cache.set("foo", "bar") cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG") assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT + + +def test_clioption_with_cacheshow_and_help(pytester: Pytester) -> None: + result = pytester.runpytest("--cache-show", "--help") + assert result.ret == 0 diff --git a/testing/test_findpaths.py b/testing/test_findpaths.py index 3a2917261a2..8287de603ed 100644 --- a/testing/test_findpaths.py +++ b/testing/test_findpaths.py @@ -1,3 +1,4 @@ +import os from pathlib import Path from textwrap import dedent @@ -5,6 +6,7 @@ from _pytest.config import UsageError from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_dirs_from_args +from _pytest.config.findpaths import is_fs_root from _pytest.config.findpaths import load_config_dict_from_file @@ -133,3 +135,18 @@ def test_get_dirs_from_args(tmp_path): assert get_dirs_from_args( [str(fn), str(tmp_path / "does_not_exist"), str(d), option, xdist_rsync_option] ) == [fn.parent, d] + + +@pytest.mark.parametrize( + "path, expected", + [ + pytest.param( + f"e:{os.sep}", True, marks=pytest.mark.skipif("sys.platform != 'win32'") + ), + (f"{os.sep}", True), + (f"e:{os.sep}projects", False), + (f"{os.sep}projects", False), + ], +) +def test_is_fs_root(path: Path, expected: bool) -> None: + assert is_fs_root(Path(path)) is expected diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 49635f95e79..b46ec05bbd2 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -425,6 +425,9 @@ class A: assert A.x == 1 +@pytest.mark.filterwarnings( + "ignore:Deprecated call to `pkg_resources.declare_namespace" +) def test_syspath_prepend_with_namespace_packages( pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: diff --git a/testing/test_nose.py b/testing/test_nose.py index 92d6b95fd87..e838e79ddd5 100644 --- a/testing/test_nose.py +++ b/testing/test_nose.py @@ -496,3 +496,24 @@ def test_it(): ) result = pytester.runpytest(p, "-p", "nose") assert result.ret == 0 + + +@pytest.mark.parametrize("fixture_name", ("teardown", "teardown_class")) +def test_teardown_fixture_not_called_directly(fixture_name, pytester: Pytester) -> None: + """Regression test for #10597.""" + p = pytester.makepyfile( + f""" + import pytest + + class TestHello: + + @pytest.fixture + def {fixture_name}(self): + yield + + def test_hello(self, {fixture_name}): + assert True + """ + ) + result = pytester.runpytest(p, "-p", "nose") + assert result.ret == 0 diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 6415480ef4f..892ed85476b 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -1439,6 +1439,27 @@ def test_pass(): ) +def test_skip_from_fixture(pytester: Pytester) -> None: + pytester.makepyfile( + **{ + "tests/test_1.py": """ + import pytest + def test_pass(arg): + pass + @pytest.fixture + def arg(): + condition = True + if condition: + pytest.skip("Fixture conditional skip") + """, + } + ) + result = pytester.runpytest("-rs", "tests/test_1.py", "--rootdir=tests") + result.stdout.fnmatch_lines( + ["SKIPPED [[]1[]] tests/test_1.py:2: Fixture conditional skip"] + ) + + def test_skip_using_reason_works_ok(pytester: Pytester) -> None: p = pytester.makepyfile( """ diff --git a/testing/test_stepwise.py b/testing/test_stepwise.py index 20781d42cce..2094abc4e50 100644 --- a/testing/test_stepwise.py +++ b/testing/test_stepwise.py @@ -1,6 +1,10 @@ +from pathlib import Path + import pytest +from _pytest.cacheprovider import Cache from _pytest.monkeypatch import MonkeyPatch from _pytest.pytester import Pytester +from _pytest.stepwise import STEPWISE_CACHE_DIR @pytest.fixture @@ -278,3 +282,76 @@ def test_three(): def test_sw_skip_help(pytester: Pytester) -> None: result = pytester.runpytest("-h") result.stdout.fnmatch_lines("*Implicitly enables --stepwise.") + + +def test_stepwise_xdist_dont_store_lastfailed(pytester: Pytester) -> None: + pytester.makefile( + ext=".ini", + pytest=f"[pytest]\ncache_dir = {pytester.path}\n", + ) + + pytester.makepyfile( + conftest=""" +import pytest + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config) -> None: + config.workerinput = True +""" + ) + pytester.makepyfile( + test_one=""" +def test_one(): + assert False +""" + ) + result = pytester.runpytest("--stepwise") + assert result.ret == pytest.ExitCode.INTERRUPTED + + stepwise_cache_file = ( + pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR + ) + assert not Path(stepwise_cache_file).exists() + + +def test_disabled_stepwise_xdist_dont_clear_cache(pytester: Pytester) -> None: + pytester.makefile( + ext=".ini", + pytest=f"[pytest]\ncache_dir = {pytester.path}\n", + ) + + stepwise_cache_file = ( + pytester.path / Cache._CACHE_PREFIX_VALUES / STEPWISE_CACHE_DIR + ) + stepwise_cache_dir = stepwise_cache_file.parent + stepwise_cache_dir.mkdir(exist_ok=True, parents=True) + + stepwise_cache_file_relative = f"{Cache._CACHE_PREFIX_VALUES}/{STEPWISE_CACHE_DIR}" + + expected_value = '"test_one.py::test_one"' + content = {f"{stepwise_cache_file_relative}": expected_value} + + pytester.makefile(ext="", **content) + + pytester.makepyfile( + conftest=""" +import pytest + +@pytest.hookimpl(tryfirst=True) +def pytest_configure(config) -> None: + config.workerinput = True +""" + ) + pytester.makepyfile( + test_one=""" +def test_one(): + assert True +""" + ) + result = pytester.runpytest() + assert result.ret == 0 + + assert Path(stepwise_cache_file).exists() + with stepwise_cache_file.open() as file_handle: + observed_value = file_handle.readlines() + assert [expected_value] == observed_value diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 9de9a85f093..453f183236f 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1265,14 +1265,14 @@ def test_this(): "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "", - " {kw}def{hl-reset} {function}test_this{hl-reset}():", - "> fail()", + " {kw}def{hl-reset} {function}test_this{hl-reset}():{endline}", + "> fail(){endline}", "", "{bold}{red}test_color_yes.py{reset}:5: ", "_ _ * _ _*", "", - " {kw}def{hl-reset} {function}fail{hl-reset}():", - "> {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}def{hl-reset} {function}fail{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "", "{bold}{red}test_color_yes.py{reset}:2: AssertionError", @@ -1292,9 +1292,9 @@ def test_this(): "=*= FAILURES =*=", "{red}{bold}_*_ test_this _*_{reset}", "{bold}{red}test_color_yes.py{reset}:5: in test_this", - " fail()", + " fail(){endline}", "{bold}{red}test_color_yes.py{reset}:2: in fail", - " {kw}assert{hl-reset} {number}0{hl-reset}", + " {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", "{red}=*= {red}{bold}1 failed{reset}{red} in *s{reset}{red} =*={reset}", ] @@ -2472,8 +2472,8 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) @@ -2494,9 +2494,9 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", " {print}print{hl-reset}({str}'''{hl-reset}{str}{hl-reset}", - "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}", + "> {str} {hl-reset}{str}'''{hl-reset}); {kw}assert{hl-reset} {number}0{hl-reset}{endline}", "{bold}{red}E assert 0{reset}", ] ) @@ -2517,8 +2517,8 @@ def test_foo(): result.stdout.fnmatch_lines( color_mapping.format_for_fnmatch( [ - " {kw}def{hl-reset} {function}test_foo{hl-reset}():", - "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + " {kw}def{hl-reset} {function}test_foo{hl-reset}():{endline}", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}{endline}", "{bold}{red}E assert 1 == 10{reset}", ] ) diff --git a/testing/typing_checks.py b/testing/typing_checks.py index 0a6b5ad2841..d15b3988bb5 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -3,6 +3,11 @@ This file is not executed, it is only checked by mypy to ensure that none of the code triggers any mypy errors. """ +import contextlib +from typing import Optional + +from typing_extensions import assert_type + import pytest @@ -22,3 +27,9 @@ def check_fixture_ids_callable() -> None: @pytest.mark.parametrize("func", [str, int], ids=lambda x: str(x.__name__)) def check_parametrize_ids_callable(func) -> None: pass + + +def check_raises_is_a_context_manager(val: bool) -> None: + with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo: + pass + assert_type(excinfo, Optional[pytest.ExceptionInfo[RuntimeError]]) diff --git a/tox.ini b/tox.ini index f04242c5c43..663ada60caf 100644 --- a/tox.ini +++ b/tox.ini @@ -9,6 +9,7 @@ envlist = py39 py310 py311 + py312 pypy3 py37-{pexpect,xdist,unittestextras,numpy,pluggymain,pylib} doctesting @@ -29,7 +30,11 @@ commands = doctesting: {env:_PYTEST_TOX_COVERAGE_RUN:} pytest --doctest-modules --pyargs _pytest coverage: coverage combine coverage: coverage report -m -passenv = USER USERNAME COVERAGE_* PYTEST_ADDOPTS TERM SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + COVERAGE_* + PYTEST_ADDOPTS + TERM + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST setenv = _PYTEST_TOX_DEFAULT_POSARGS={env:_PYTEST_TOX_POSARGS_DOCTESTING:} {env:_PYTEST_TOX_POSARGS_LSOF:} {env:_PYTEST_TOX_POSARGS_XDIST:} @@ -92,13 +97,14 @@ commands = [testenv:regen] changedir = doc/en basepython = python3 -passenv = SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST +passenv = + SETUPTOOLS_SCM_PRETEND_VERSION_FOR_PYTEST deps = dataclasses PyYAML regendoc>=0.8.1 sphinx -whitelist_externals = +allowlist_externals = make commands = make regen @@ -160,7 +166,10 @@ commands = python scripts/prepare-release-pr.py {posargs} description = create GitHub release after deployment basepython = python3 usedevelop = True -passenv = GH_RELEASE_NOTES_TOKEN GITHUB_REF GITHUB_REPOSITORY +passenv = + GH_RELEASE_NOTES_TOKEN + GITHUB_REF + GITHUB_REPOSITORY deps = github3.py pypandoc