From 9a413e4cb88d50d6f674fa9f4fe40c1ae1c5e718 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 22 Apr 2023 21:30:51 +0200 Subject: [PATCH 01/16] [7.3.x] Add 2024 pytest training (#10934) Co-authored-by: Florian Bruhin --- doc/en/index.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index 6f3115b19c2..bd4a6486783 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -1,11 +1,10 @@ :orphan: -.. - .. sidebar:: Next Open Trainings +.. sidebar:: Next Open Trainings - - `Professional Testing with Python `_, via `Python Academy `_, March 7th to 9th 2023 (3 day in-depth training), Remote + - `Professional Testing with Python `_, via `Python Academy `_, March 5th to 7th 2024 (3 day in-depth training), Remote - Also see :doc:`previous talks and blogposts `. + Also see :doc:`previous talks and blogposts `. .. _features: From 6193bf73ca85ddafb8b6495982332ad52ec2539a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 10:55:24 +0200 Subject: [PATCH 02/16] [7.3.x] Fix documentation typo (#10943) Co-authored-by: Bryan Ricker <978899+bricker@users.noreply.github.com> --- doc/en/reference/reference.rst | 2 +- src/_pytest/logging.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 963e666ade3..8b42bf12965 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1996,7 +1996,7 @@ All the command-line flags can be obtained by running ``pytest --help``:: Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer. --log-disable=LOGGER_DISABLE - Disable a logger by name. Can be passed multipe + Disable a logger by name. Can be passed multiple times. [pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg|pyproject.toml file found: diff --git a/src/_pytest/logging.py b/src/_pytest/logging.py index 4e3d12475d1..2480a5edd29 100644 --- a/src/_pytest/logging.py +++ b/src/_pytest/logging.py @@ -302,7 +302,7 @@ def add_option_ini(option, dest, default=None, type=None, **kwargs): action="append", default=[], dest="logger_disable", - help="Disable a logger by name. Can be passed multipe times.", + help="Disable a logger by name. Can be passed multiple times.", ) From f72c4cf35b0d3e6677bba82fc8eab4134553c7d1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 25 Apr 2023 11:40:52 +0200 Subject: [PATCH 03/16] [7.3.x] Filter new pkg_resources deprecations (#10944) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Miro Hrončok --- testing/acceptance_test.py | 8 ++++++-- testing/test_monkeypatch.py | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 6b421dde641..7616f1419d6 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -695,11 +695,15 @@ def test_cmdline_python_namespace_package( monkeypatch.chdir("world") # pgk_resources.declare_namespace has been deprecated in favor of implicit namespace packages. + # pgk_resources has been deprecated entirely. # 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" + ignore_w = ( + r"-Wignore:Deprecated call to `pkg_resources.declare_namespace", + r"-Wignore:pkg_resources is deprecated", + ) result = pytester.runpytest( - "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", ignore_w + "--pyargs", "-v", "ns_pkg.hello", "ns_pkg/world", *ignore_w ) assert result.ret == 0 result.stdout.fnmatch_lines( diff --git a/testing/test_monkeypatch.py b/testing/test_monkeypatch.py index 3d09ef4263a..500e244531a 100644 --- a/testing/test_monkeypatch.py +++ b/testing/test_monkeypatch.py @@ -425,9 +425,7 @@ class A: assert A.x == 1 -@pytest.mark.filterwarnings( - "ignore:Deprecated call to `pkg_resources.declare_namespace" -) +@pytest.mark.filterwarnings(r"ignore:.*\bpkg_resources\b:DeprecationWarning") def test_syspath_prepend_with_namespace_packages( pytester: Pytester, monkeypatch: MonkeyPatch ) -> None: From c403dc538b9cc155f2e4365f2e94b80f59e105b5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 26 Apr 2023 07:00:06 +0000 Subject: [PATCH 04/16] [7.3.x] doc: Fix 2024 training location (#10948) Co-authored-by: Florian Bruhin --- doc/en/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/en/index.rst b/doc/en/index.rst index bd4a6486783..87213878890 100644 --- a/doc/en/index.rst +++ b/doc/en/index.rst @@ -2,7 +2,7 @@ .. sidebar:: Next Open Trainings - - `Professional Testing with Python `_, via `Python Academy `_, March 5th to 7th 2024 (3 day in-depth training), Remote + - `Professional Testing with Python `_, via `Python Academy `_, March 5th to 7th 2024 (3 day in-depth training), Leipzig/Remote Also see :doc:`previous talks and blogposts `. From 2ae187e78dc6b41f2b6b6094e5348337c687ebd3 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 29 Apr 2023 11:37:22 +0300 Subject: [PATCH 05/16] [7.3.x] Fix couple of EncodingWarnings --- src/_pytest/_py/path.py | 2 +- src/_pytest/faulthandler.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/_pytest/_py/path.py b/src/_pytest/_py/path.py index fb64830f814..73a070d19a8 100644 --- a/src/_pytest/_py/path.py +++ b/src/_pytest/_py/path.py @@ -953,7 +953,7 @@ def ensure(self, *args, **kwargs): else: p.dirpath()._ensuredirs() if not p.check(file=1): - p.open("w").close() + p.open("wb").close() return p @overload diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index b9c925582ca..ed29346babd 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -27,7 +27,9 @@ def pytest_configure(config: Config) -> None: import faulthandler stderr_fd_copy = os.dup(get_stderr_fileno()) - config.stash[fault_handler_stderr_key] = open(stderr_fd_copy, "w") + config.stash[fault_handler_stderr_key] = open( + stderr_fd_copy, "w", encoding=sys.stderr.encoding + ) config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() faulthandler.enable(file=config.stash[fault_handler_stderr_key]) From 94d69222615fd77efc22a963027c015dfc16ebfa Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 10 May 2023 10:15:20 +0300 Subject: [PATCH 06/16] [7.3.x] fix reference to non-existent module --- src/_pytest/hookspec.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index 143ec190c2d..95b4265ea2f 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -21,7 +21,7 @@ from typing_extensions import Literal from _pytest._code.code import ExceptionRepr - from _pytest.code import ExceptionInfo + from _pytest._code.code import ExceptionInfo from _pytest.config import Config from _pytest.config import ExitCode from _pytest.config import PytestPluginManager From 920512435a7f54e31f91500515662028ea5fbd3d Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 10 May 2023 14:17:54 +0300 Subject: [PATCH 07/16] [7.3.x] faulthandler: avoid accessing sys.stderr.encoding --- src/_pytest/faulthandler.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/src/_pytest/faulthandler.py b/src/_pytest/faulthandler.py index ed29346babd..af879aa44cf 100644 --- a/src/_pytest/faulthandler.py +++ b/src/_pytest/faulthandler.py @@ -2,7 +2,6 @@ import os import sys from typing import Generator -from typing import TextIO import pytest from _pytest.config import Config @@ -11,7 +10,7 @@ from _pytest.stash import StashKey -fault_handler_stderr_key = StashKey[TextIO]() +fault_handler_stderr_fd_key = StashKey[int]() fault_handler_originally_enabled_key = StashKey[bool]() @@ -26,12 +25,9 @@ def pytest_addoption(parser: Parser) -> None: def pytest_configure(config: Config) -> None: import faulthandler - stderr_fd_copy = os.dup(get_stderr_fileno()) - config.stash[fault_handler_stderr_key] = open( - stderr_fd_copy, "w", encoding=sys.stderr.encoding - ) + config.stash[fault_handler_stderr_fd_key] = os.dup(get_stderr_fileno()) config.stash[fault_handler_originally_enabled_key] = faulthandler.is_enabled() - faulthandler.enable(file=config.stash[fault_handler_stderr_key]) + faulthandler.enable(file=config.stash[fault_handler_stderr_fd_key]) def pytest_unconfigure(config: Config) -> None: @@ -39,9 +35,9 @@ def pytest_unconfigure(config: Config) -> None: faulthandler.disable() # Close the dup file installed during pytest_configure. - if fault_handler_stderr_key in config.stash: - config.stash[fault_handler_stderr_key].close() - del config.stash[fault_handler_stderr_key] + if fault_handler_stderr_fd_key in config.stash: + os.close(config.stash[fault_handler_stderr_fd_key]) + del config.stash[fault_handler_stderr_fd_key] if config.stash.get(fault_handler_originally_enabled_key, False): # Re-enable the faulthandler if it was originally enabled. faulthandler.enable(file=get_stderr_fileno()) @@ -69,10 +65,10 @@ def get_timeout_config_value(config: Config) -> float: @pytest.hookimpl(hookwrapper=True, trylast=True) def pytest_runtest_protocol(item: Item) -> Generator[None, None, None]: timeout = get_timeout_config_value(item.config) - stderr = item.config.stash[fault_handler_stderr_key] - if timeout > 0 and stderr is not None: + if timeout > 0: import faulthandler + stderr = item.config.stash[fault_handler_stderr_fd_key] faulthandler.dump_traceback_later(timeout, file=stderr) try: yield From 8082d27d11522db7569878777da9507e9200087f Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 12 May 2023 10:42:27 -0300 Subject: [PATCH 08/16] Merge pull request #10988 from nicoddemus/initial-testpaths-10987 (#10995) Consider testpaths for initial conftests (cherry picked from commit 76d15231f5749ca26a180df02ef2e5c138aaefc8) --- changelog/10169.bugfix.rst | 1 + changelog/10987.bugfix.rst | 1 + doc/en/reference/reference.rst | 20 +++++++++++----- src/_pytest/config/__init__.py | 20 ++++++++++++---- testing/test_collection.py | 42 ++++++++++++++++++++++++++++++++++ testing/test_conftest.py | 2 +- 6 files changed, 75 insertions(+), 11 deletions(-) create mode 100644 changelog/10169.bugfix.rst create mode 100644 changelog/10987.bugfix.rst diff --git a/changelog/10169.bugfix.rst b/changelog/10169.bugfix.rst new file mode 100644 index 00000000000..cbf3516a93d --- /dev/null +++ b/changelog/10169.bugfix.rst @@ -0,0 +1 @@ +Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems. diff --git a/changelog/10987.bugfix.rst b/changelog/10987.bugfix.rst new file mode 100644 index 00000000000..2aafff5f512 --- /dev/null +++ b/changelog/10987.bugfix.rst @@ -0,0 +1 @@ +:confval:`testpaths` is now honored to load root ``conftests``. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 8b42bf12965..4bda48386a1 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1713,13 +1713,12 @@ passed multiple times. The expected format is ``name=value``. For example:: .. confval:: testpaths - - Sets list of directories that should be searched for tests when no specific directories, files or test ids are given in the command line when executing pytest from the :ref:`rootdir ` directory. File system paths may use shell-style wildcards, including the recursive ``**`` pattern. + Useful when all project tests are in a known location to speed up test collection and to avoid picking up undesired tests by accident. @@ -1728,8 +1727,17 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] testpaths = testing doc - This tells pytest to only look for tests in ``testing`` and ``doc`` - directories when executing from the root directory. + This configuration means that executing: + + .. code-block:: console + + pytest + + has the same practical effects as executing: + + .. code-block:: console + + pytest testing doc .. confval:: tmp_path_retention_count @@ -1744,7 +1752,7 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] tmp_path_retention_count = 3 - Default: 3 + Default: ``3`` .. confval:: tmp_path_retention_policy @@ -1763,7 +1771,7 @@ passed multiple times. The expected format is ``name=value``. For example:: [pytest] tmp_path_retention_policy = "all" - Default: all + Default: ``all`` .. confval:: usefixtures diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 720f3953153..74905ff4c8e 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -526,7 +526,10 @@ def pytest_configure(self, config: "Config") -> None: # Internal API for local conftest plugin handling. # def _set_initial_conftests( - self, namespace: argparse.Namespace, rootpath: Path + self, + namespace: argparse.Namespace, + rootpath: Path, + testpaths_ini: Sequence[str], ) -> None: """Load initial conftest files given a preparsed "namespace". @@ -543,7 +546,7 @@ def _set_initial_conftests( ) self._noconftest = namespace.noconftest self._using_pyargs = namespace.pyargs - testpaths = namespace.file_or_dir + testpaths = namespace.file_or_dir + testpaths_ini foundanchor = False for testpath in testpaths: path = str(testpath) @@ -552,7 +555,14 @@ def _set_initial_conftests( if i != -1: path = path[:i] anchor = absolutepath(current / path) - if anchor.exists(): # we found some file object + + # Ensure we do not break if what appears to be an anchor + # is in fact a very long option (#10169). + try: + anchor_exists = anchor.exists() + except OSError: # pragma: no cover + anchor_exists = False + if anchor_exists: self._try_load_conftest(anchor, namespace.importmode, rootpath) foundanchor = True if not foundanchor: @@ -1131,7 +1141,9 @@ def _processopt(self, opt: "Argument") -> None: @hookimpl(trylast=True) def pytest_load_initial_conftests(self, early_config: "Config") -> None: self.pluginmanager._set_initial_conftests( - early_config.known_args_namespace, rootpath=early_config.rootpath + early_config.known_args_namespace, + rootpath=early_config.rootpath, + testpaths_ini=self.getini("testpaths"), ) def _initini(self, args: Sequence[str]) -> None: diff --git a/testing/test_collection.py b/testing/test_collection.py index d907244d551..bbcb358b6ff 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1247,6 +1247,48 @@ def test_collect_pyargs_with_testpaths( result.stdout.fnmatch_lines(["*1 passed in*"]) +def test_initial_conftests_with_testpaths(pytester: Pytester) -> None: + """The testpaths ini option should load conftests in those paths as 'initial' (#10987).""" + p = pytester.mkdir("some_path") + p.joinpath("conftest.py").write_text( + textwrap.dedent( + """ + def pytest_sessionstart(session): + raise Exception("pytest_sessionstart hook successfully run") + """ + ) + ) + pytester.makeini( + """ + [pytest] + testpaths = some_path + """ + ) + result = pytester.runpytest() + result.stdout.fnmatch_lines( + "INTERNALERROR* Exception: pytest_sessionstart hook successfully run" + ) + + +def test_large_option_breaks_initial_conftests(pytester: Pytester) -> None: + """Long option values do not break initial conftests handling (#10169).""" + option_value = "x" * 1024 * 1000 + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addoption("--xx", default=None) + """ + ) + pytester.makepyfile( + f""" + def test_foo(request): + assert request.config.getoption("xx") == {option_value!r} + """ + ) + result = pytester.runpytest(f"--xx={option_value}") + assert result.ret == 0 + + def test_collect_symlink_file_arg(pytester: Pytester) -> None: """Collect a direct symlink works even if it does not match python_files (#4325).""" real = pytester.makepyfile( diff --git a/testing/test_conftest.py b/testing/test_conftest.py index d2bf860c6fe..d6abca5368f 100644 --- a/testing/test_conftest.py +++ b/testing/test_conftest.py @@ -35,7 +35,7 @@ def __init__(self) -> None: self.importmode = "prepend" namespace = cast(argparse.Namespace, Namespace()) - conftest._set_initial_conftests(namespace, rootpath=Path(args[0])) + conftest._set_initial_conftests(namespace, rootpath=Path(args[0]), testpaths_ini=[]) @pytest.mark.usefixtures("_sys_snapshot") From bcada5138adc6e9443467d12206e7f8d08c4d362 Mon Sep 17 00:00:00 2001 From: "Adam J. Stewart" Date: Sun, 14 May 2023 14:17:00 -0500 Subject: [PATCH 09/16] [7.3.x] monkeypatch: add support for TypedDict --- AUTHORS | 1 + changelog/10999.bugfix.rst | 1 + src/_pytest/monkeypatch.py | 19 ++++++++++++------- testing/typing_checks.py | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 changelog/10999.bugfix.rst diff --git a/AUTHORS b/AUTHORS index a4c7f856884..2c1f02fcbcd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,6 +8,7 @@ Abdeali JK Abdelrahman Elbehery Abhijeet Kasurde Adam Johnson +Adam Stewart Adam Uhlir Ahn Ki-Wook Akiomi Kamakura diff --git a/changelog/10999.bugfix.rst b/changelog/10999.bugfix.rst new file mode 100644 index 00000000000..08c68da01bf --- /dev/null +++ b/changelog/10999.bugfix.rst @@ -0,0 +1 @@ +The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments. diff --git a/src/_pytest/monkeypatch.py b/src/_pytest/monkeypatch.py index c6e29ac7642..9e51ff33538 100644 --- a/src/_pytest/monkeypatch.py +++ b/src/_pytest/monkeypatch.py @@ -7,6 +7,7 @@ from typing import Any from typing import Generator from typing import List +from typing import Mapping from typing import MutableMapping from typing import Optional from typing import overload @@ -129,7 +130,7 @@ class MonkeyPatch: def __init__(self) -> None: self._setattr: List[Tuple[object, str, object]] = [] - self._setitem: List[Tuple[MutableMapping[Any, Any], object, object]] = [] + self._setitem: List[Tuple[Mapping[Any, Any], object, object]] = [] self._cwd: Optional[str] = None self._savesyspath: Optional[List[str]] = None @@ -290,12 +291,13 @@ def delattr( self._setattr.append((target, name, oldval)) delattr(target, name) - def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None: + def setitem(self, dic: Mapping[K, V], name: K, value: V) -> None: """Set dictionary entry ``name`` to value.""" self._setitem.append((dic, name, dic.get(name, notset))) - dic[name] = value + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dic[name] = value # type: ignore[index] - def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None: + def delitem(self, dic: Mapping[K, V], name: K, raising: bool = True) -> None: """Delete ``name`` from dict. Raises ``KeyError`` if it doesn't exist, unless ``raising`` is set to @@ -306,7 +308,8 @@ def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> N raise KeyError(name) else: self._setitem.append((dic, name, dic.get(name, notset))) - del dic[name] + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dic[name] # type: ignore[attr-defined] def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None: """Set environment variable ``name`` to ``value``. @@ -401,11 +404,13 @@ def undo(self) -> None: for dictionary, key, value in reversed(self._setitem): if value is notset: try: - del dictionary[key] + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + del dictionary[key] # type: ignore[attr-defined] except KeyError: pass # Was already deleted, so we have the desired state. else: - dictionary[key] = value + # Not all Mapping types support indexing, but MutableMapping doesn't support TypedDict + dictionary[key] = value # type: ignore[index] self._setitem[:] = [] if self._savesyspath is not None: sys.path[:] = self._savesyspath diff --git a/testing/typing_checks.py b/testing/typing_checks.py index d15b3988bb5..57f2bae475f 100644 --- a/testing/typing_checks.py +++ b/testing/typing_checks.py @@ -9,6 +9,7 @@ from typing_extensions import assert_type import pytest +from pytest import MonkeyPatch # Issue #7488. @@ -29,6 +30,19 @@ def check_parametrize_ids_callable(func) -> None: pass +# Issue #10999. +def check_monkeypatch_typeddict(monkeypatch: MonkeyPatch) -> None: + from typing import TypedDict + + class Foo(TypedDict): + x: int + y: float + + a: Foo = {"x": 1, "y": 3.14} + monkeypatch.setitem(a, "x", 2) + monkeypatch.delitem(a, "y") + + def check_raises_is_a_context_manager(val: bool) -> None: with pytest.raises(RuntimeError) if val else contextlib.nullcontext() as excinfo: pass From efcb81c4927573ef624c0fb723643f10b69cbff9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 17 May 2023 11:04:47 +0000 Subject: [PATCH 10/16] [7.3.x] Reference "Status of Python Versions" in backwards-compatibility policy (#11010) Co-authored-by: Ronny Pfannschmidt --- doc/en/backwards-compatibility.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/en/backwards-compatibility.rst b/doc/en/backwards-compatibility.rst index 64bcbf5bd49..ea0c6a71a28 100644 --- a/doc/en/backwards-compatibility.rst +++ b/doc/en/backwards-compatibility.rst @@ -92,3 +92,5 @@ pytest version min. Python version 5.0 - 6.1 3.5+ 3.3 - 4.6 2.7, 3.4+ ============== =================== + +`Status of Python Versions `__. From 6c95cb607d87f493d308b98ad21a28ac9821105e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 18 May 2023 13:32:59 +0000 Subject: [PATCH 11/16] [7.3.x] Spelling and grammar fixes (#11015) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ville Skyttä --- doc/en/changelog.rst | 4 ++-- doc/en/conf.py | 2 +- doc/en/reference/reference.rst | 4 ++-- src/_pytest/capture.py | 2 +- src/_pytest/tmpdir.py | 2 +- src/_pytest/warning_types.py | 2 +- testing/test_python_path.py | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index c13c05936d5..7d6eb1ffacc 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -567,7 +567,7 @@ Breaking Changes - `#7259 `_: The :ref:`Node.reportinfo() ` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`. Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation. - Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted. + Since `py.path.local` is an `os.PathLike[str]`, these plugins are unaffacted. Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`. Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead. @@ -4067,7 +4067,7 @@ Removals See our :ref:`docs ` on information on how to update your code. -- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check. +- :issue:`4546`: Remove ``Node.get_marker(name)`` the return value was not usable for more than an existence check. Use ``Node.get_closest_marker(name)`` as a replacement. diff --git a/doc/en/conf.py b/doc/en/conf.py index 5184ee7b1e5..32f508219a6 100644 --- a/doc/en/conf.py +++ b/doc/en/conf.py @@ -341,7 +341,7 @@ # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' -# The unique identifier of the text. This can be a ISBN number +# The unique identifier of the text. This can be an ISBN number # or the project homepage. # epub_identifier = '' diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 4bda48386a1..48fbe2734de 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1049,11 +1049,11 @@ Environment variables that can be used to change pytest's behavior. .. envvar:: CI -When set (regardless of value), pytest acknowledges that is running in a CI process. Alterative to ``BUILD_NUMBER`` variable. +When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative 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. +When set (regardless of value), pytest acknowledges that is running in a CI process. Alternative to CI variable. .. envvar:: PYTEST_ADDOPTS diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index 275322cc335..a8ca0869f33 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -241,7 +241,7 @@ def tell(self) -> int: raise UnsupportedOperation("redirected stdin is pseudofile, has no tell()") def truncate(self, size: Optional[int] = None) -> int: - raise UnsupportedOperation("cannont truncate stdin") + raise UnsupportedOperation("cannot truncate stdin") def write(self, data: str) -> int: raise UnsupportedOperation("cannot write to stdin") diff --git a/src/_pytest/tmpdir.py b/src/_pytest/tmpdir.py index d7f5ab9b459..3cc2bace55b 100644 --- a/src/_pytest/tmpdir.py +++ b/src/_pytest/tmpdir.py @@ -100,7 +100,7 @@ def from_config( policy = config.getini("tmp_path_retention_policy") if policy not in ("all", "failed", "none"): raise ValueError( - f"tmp_path_retention_policy must be either all, failed, none. Current intput: {policy}." + f"tmp_path_retention_policy must be either all, failed, none. Current input: {policy}." ) return cls( diff --git a/src/_pytest/warning_types.py b/src/_pytest/warning_types.py index 86fa9a07e0c..bd5f4187343 100644 --- a/src/_pytest/warning_types.py +++ b/src/_pytest/warning_types.py @@ -149,7 +149,7 @@ def warn_explicit_for(method: FunctionType, message: PytestWarning) -> None: """ Issue the warning :param:`message` for the definition of the given :param:`method` - this helps to log warnigns for functions defined prior to finding an issue with them + this helps to log warnings for functions defined prior to finding an issue with them (like hook wrappers being marked in a legacy mechanism) """ lineno = method.__code__.co_firstlineno diff --git a/testing/test_python_path.py b/testing/test_python_path.py index 5ee0f55e36a..e1628feb159 100644 --- a/testing/test_python_path.py +++ b/testing/test_python_path.py @@ -82,7 +82,7 @@ def test_no_ini(pytester: Pytester, file_structure) -> None: def test_clean_up(pytester: Pytester) -> None: """Test that the plugin cleans up after itself.""" - # This is tough to test behaviorly because the cleanup really runs last. + # This is tough to test behaviorally because the cleanup really runs last. # So the test make several implementation assumptions: # - Cleanup is done in pytest_unconfigure(). # - Not a hookwrapper. From 69689c6eb55188980d055e280ceca060a9be634b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 11:33:28 +0000 Subject: [PATCH 12/16] [7.3.x] nonpython example now repr all exceptions (#11034) Co-authored-by: Bruno Oliveira --- doc/en/example/nonpython/conftest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/en/example/nonpython/conftest.py b/doc/en/example/nonpython/conftest.py index bc39a1f6b20..dd1ebe88d7e 100644 --- a/doc/en/example/nonpython/conftest.py +++ b/doc/en/example/nonpython/conftest.py @@ -38,6 +38,7 @@ def repr_failure(self, excinfo): " no further details known at this point.", ] ) + return super().repr_failure(excinfo) def reportinfo(self): return self.path, 0, f"usecase: {self.name}" From 331bc1be4634cf95fdb33021d4355c35723b73fe Mon Sep 17 00:00:00 2001 From: Alessio Izzo Date: Tue, 30 May 2023 16:59:24 +0200 Subject: [PATCH 13/16] [7.3.x] 11028 - Fix warlus operator behavior when called by a function --- changelog/11028.bugfix.rst | 1 + src/_pytest/assertion/rewrite.py | 25 +++++++-- testing/test_assertrewrite.py | 90 ++++++++++++++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 changelog/11028.bugfix.rst diff --git a/changelog/11028.bugfix.rst b/changelog/11028.bugfix.rst new file mode 100644 index 00000000000..9efc04ba69f --- /dev/null +++ b/changelog/11028.bugfix.rst @@ -0,0 +1 @@ +Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call. diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 8b182347052..ab8169da2ef 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -996,7 +996,9 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: ] ): pytest_temp = self.variable() - self.variables_overwrite[v.left.target.id] = pytest_temp + self.variables_overwrite[ + v.left.target.id + ] = v.left # type:ignore[assignment] v.left.target.id = pytest_temp self.push_format_context() res, expl = self.visit(v) @@ -1037,10 +1039,19 @@ def visit_Call(self, call: ast.Call) -> Tuple[ast.Name, str]: new_args = [] new_kwargs = [] for arg in call.args: + if isinstance(arg, ast.Name) and arg.id in self.variables_overwrite: + arg = self.variables_overwrite[arg.id] # type:ignore[assignment] res, expl = self.visit(arg) arg_expls.append(expl) new_args.append(res) for keyword in call.keywords: + if ( + isinstance(keyword.value, ast.Name) + and keyword.value.id in self.variables_overwrite + ): + keyword.value = self.variables_overwrite[ + keyword.value.id + ] # type:ignore[assignment] res, expl = self.visit(keyword.value) new_kwargs.append(ast.keyword(keyword.arg, res)) if keyword.arg: @@ -1075,7 +1086,13 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: self.push_format_context() # We first check if we have overwritten a variable in the previous assert if isinstance(comp.left, ast.Name) and comp.left.id in self.variables_overwrite: - comp.left.id = self.variables_overwrite[comp.left.id] + comp.left = self.variables_overwrite[ + comp.left.id + ] # type:ignore[assignment] + if isinstance(comp.left, namedExpr): + self.variables_overwrite[ + comp.left.target.id + ] = comp.left # type:ignore[assignment] left_res, left_expl = self.visit(comp.left) if isinstance(comp.left, (ast.Compare, ast.BoolOp)): left_expl = f"({left_expl})" @@ -1093,7 +1110,9 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: and next_operand.target.id == left_res.id ): next_operand.target.id = self.variable() - self.variables_overwrite[left_res.id] = next_operand.target.id + self.variables_overwrite[ + left_res.id + ] = next_operand # type:ignore[assignment] next_res, next_expl = self.visit(next_operand) if isinstance(next_operand, (ast.Compare, ast.BoolOp)): next_expl = f"({next_expl})" diff --git a/testing/test_assertrewrite.py b/testing/test_assertrewrite.py index 8d944140307..245241af2b2 100644 --- a/testing/test_assertrewrite.py +++ b/testing/test_assertrewrite.py @@ -1436,6 +1436,96 @@ def test_walrus_operator_not_override_value(): assert result.ret == 0 +@pytest.mark.skipif( + sys.version_info < (3, 8), reason="walrus operator not available in py<38" +) +class TestIssue11028: + def test_assertion_walrus_operator_in_operand(self, pytester: Pytester) -> None: + pytester.makepyfile( + """ + def test_in_string(): + assert (obj := "foo") in obj + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_in_operand_json_dumps( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + import json + + def test_json_encoder(): + assert (obj := "foo") in json.dumps(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a): + return a + + def test_call_other_function_arg(): + assert (obj := "foo") == f(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function_keyword_arg( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a='test'): + return a + + def test_call_other_function_k_arg(): + assert (obj := "foo") == f(a=obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_equals_operand_function_arg_as_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def f(a='test'): + return a + + def test_function_of_function(): + assert (obj := "foo") == f(f(obj)) + """ + ) + result = pytester.runpytest() + assert result.ret == 0 + + def test_assertion_walrus_operator_gt_operand_function( + self, pytester: Pytester + ) -> None: + pytester.makepyfile( + """ + def add_one(a): + return a + 1 + + def test_gt(): + assert (obj := 4) > add_one(obj) + """ + ) + result = pytester.runpytest() + assert result.ret == 1 + result.stdout.fnmatch_lines(["*assert 4 > 5", "*where 5 = add_one(4)"]) + + @pytest.mark.skipif( sys.maxsize <= (2**31 - 1), reason="Causes OverflowError on 32bit systems" ) From 30a112583e27c7c2c06ccde5afd952a0306837ce Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Tue, 30 May 2023 20:04:06 +0300 Subject: [PATCH 14/16] [7.3.x] cacheprovider: fix file-skipping feature for files in packages --- changelog/11054.bugfix.rst | 1 + src/_pytest/cacheprovider.py | 2 +- testing/test_cacheprovider.py | 8 +++++++- 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 changelog/11054.bugfix.rst diff --git a/changelog/11054.bugfix.rst b/changelog/11054.bugfix.rst new file mode 100644 index 00000000000..a8ee04fe304 --- /dev/null +++ b/changelog/11054.bugfix.rst @@ -0,0 +1 @@ +Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files). diff --git a/src/_pytest/cacheprovider.py b/src/_pytest/cacheprovider.py index 719b32f7e0e..940dc5d8782 100755 --- a/src/_pytest/cacheprovider.py +++ b/src/_pytest/cacheprovider.py @@ -213,7 +213,7 @@ def __init__(self, lfplugin: "LFPlugin") -> None: @hookimpl(hookwrapper=True) def pytest_make_collect_report(self, collector: nodes.Collector): - if isinstance(collector, Session): + if isinstance(collector, (Session, Package)): out = yield res: CollectReport = out.get_result() diff --git a/testing/test_cacheprovider.py b/testing/test_cacheprovider.py index 2f8517f9962..f870d7b162e 100644 --- a/testing/test_cacheprovider.py +++ b/testing/test_cacheprovider.py @@ -420,7 +420,13 @@ def test_fail(val): result = pytester.runpytest() result.stdout.fnmatch_lines(["*1 failed in*"]) - def test_terminal_report_lastfailed(self, pytester: Pytester) -> None: + @pytest.mark.parametrize("parent", ("session", "package")) + def test_terminal_report_lastfailed(self, pytester: Pytester, parent: str) -> None: + if parent == "package": + pytester.makepyfile( + __init__="", + ) + test_a = pytester.makepyfile( test_a=""" def test_a1(): pass From 8e1bbe1a943ea103957728053ec4764a00364331 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Sat, 10 Jun 2023 20:46:02 +0300 Subject: [PATCH 15/16] [7.3.x] Python 3.12 support --- .github/workflows/test.yml | 28 ++++++++++++------ .pre-commit-config.yaml | 2 +- changelog/10894.bugfix.rst | 1 + setup.cfg | 2 ++ src/_pytest/assertion/rewrite.py | 49 +++++++++++++++++++------------- src/_pytest/mark/expression.py | 8 +++++- testing/python/collect.py | 26 ++++++++++------- 7 files changed, 75 insertions(+), 41 deletions(-) create mode 100644 changelog/10894.bugfix.rst diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cd1ffdbf9d4..cf5027223e1 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,6 +43,7 @@ jobs: "windows-py39", "windows-py310", "windows-py311", + "windows-py312", "ubuntu-py37", "ubuntu-py37-pluggy", @@ -51,12 +52,13 @@ jobs: "ubuntu-py39", "ubuntu-py310", "ubuntu-py311", + "ubuntu-py312", "ubuntu-pypy3", "macos-py37", - "macos-py38", "macos-py39", "macos-py310", + "macos-py312", "docs", "doctesting", @@ -86,9 +88,13 @@ jobs: os: windows-latest tox_env: "py310-xdist" - name: "windows-py311" - python: "3.11-dev" + python: "3.11" os: windows-latest tox_env: "py311" + - name: "windows-py312" + python: "3.12-dev" + os: windows-latest + tox_env: "py312" - name: "ubuntu-py37" python: "3.7" @@ -116,10 +122,15 @@ jobs: os: ubuntu-latest tox_env: "py310-xdist" - name: "ubuntu-py311" - python: "3.11-dev" + python: "3.11" os: ubuntu-latest tox_env: "py311" use_coverage: true + - name: "ubuntu-py312" + python: "3.12-dev" + os: ubuntu-latest + tox_env: "py312" + use_coverage: true - name: "ubuntu-pypy3" python: "pypy-3.7" os: ubuntu-latest @@ -129,19 +140,19 @@ jobs: python: "3.7" os: macos-latest tox_env: "py37-xdist" - - name: "macos-py38" - python: "3.8" - os: macos-latest - tox_env: "py38-xdist" - use_coverage: true - name: "macos-py39" python: "3.9" os: macos-latest tox_env: "py39-xdist" + use_coverage: true - name: "macos-py310" python: "3.10" os: macos-latest tox_env: "py310-xdist" + - name: "macos-py312" + python: "3.12-dev" + os: macos-latest + tox_env: "py312-xdist" - name: "plugins" python: "3.9" @@ -168,6 +179,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} + check-latest: ${{ endsWith(matrix.python, '-dev') }} - name: Install dependencies run: | diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d672875962f..d83b2cddcdb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -52,7 +52,7 @@ repos: rev: v2.2.0 hooks: - id: setup-cfg-fmt - args: ["--max-py-version=3.11", "--include-version-classifiers"] + args: ["--max-py-version=3.12", "--include-version-classifiers"] - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: diff --git a/changelog/10894.bugfix.rst b/changelog/10894.bugfix.rst new file mode 100644 index 00000000000..66c6cd73f3c --- /dev/null +++ b/changelog/10894.bugfix.rst @@ -0,0 +1 @@ +Support for Python 3.12 (beta at the time of writing). diff --git a/setup.cfg b/setup.cfg index 56dadae7bf5..b7b74cd95d3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -22,6 +22,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Topic :: Software Development :: Libraries Topic :: Software Development :: Testing Topic :: Utilities @@ -73,6 +74,7 @@ testing = nose pygments>=2.7.2 requests + setuptools xmlschema [options.package_data] diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index ab8169da2ef..ab83fee32b2 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -46,8 +46,14 @@ if sys.version_info >= (3, 8): namedExpr = ast.NamedExpr + astNameConstant = ast.Constant + astStr = ast.Constant + astNum = ast.Constant else: namedExpr = ast.Expr + astNameConstant = ast.NameConstant + astStr = ast.Str + astNum = ast.Num assertstate_key = StashKey["AssertionState"]() @@ -680,9 +686,12 @@ def run(self, mod: ast.Module) -> None: if ( expect_docstring and isinstance(item, ast.Expr) - and isinstance(item.value, ast.Str) + and isinstance(item.value, astStr) ): - doc = item.value.s + if sys.version_info >= (3, 8): + doc = item.value.value + else: + doc = item.value.s if self.is_rewrite_disabled(doc): return expect_docstring = False @@ -814,7 +823,7 @@ def pop_format_context(self, expl_expr: ast.expr) -> ast.Name: current = self.stack.pop() if self.stack: self.explanation_specifiers = self.stack[-1] - keys = [ast.Str(key) for key in current.keys()] + keys = [astStr(key) for key in current.keys()] format_dict = ast.Dict(keys, list(current.values())) form = ast.BinOp(expl_expr, ast.Mod(), format_dict) name = "@py_format" + str(next(self.variable_counter)) @@ -868,16 +877,16 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: negation = ast.UnaryOp(ast.Not(), top_condition) if self.enable_assertion_pass_hook: # Experimental pytest_assertion_pass hook - msg = self.pop_format_context(ast.Str(explanation)) + msg = self.pop_format_context(astStr(explanation)) # Failed if assert_.msg: assertmsg = self.helper("_format_assertmsg", assert_.msg) gluestr = "\n>assert " else: - assertmsg = ast.Str("") + assertmsg = astStr("") gluestr = "assert " - err_explanation = ast.BinOp(ast.Str(gluestr), ast.Add(), msg) + err_explanation = ast.BinOp(astStr(gluestr), ast.Add(), msg) err_msg = ast.BinOp(assertmsg, ast.Add(), err_explanation) err_name = ast.Name("AssertionError", ast.Load()) fmt = self.helper("_format_explanation", err_msg) @@ -893,8 +902,8 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: hook_call_pass = ast.Expr( self.helper( "_call_assertion_pass", - ast.Num(assert_.lineno), - ast.Str(orig), + astNum(assert_.lineno), + astStr(orig), fmt_pass, ) ) @@ -913,7 +922,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: variables = [ ast.Name(name, ast.Store()) for name in self.format_variables ] - clear_format = ast.Assign(variables, ast.NameConstant(None)) + clear_format = ast.Assign(variables, astNameConstant(None)) self.statements.append(clear_format) else: # Original assertion rewriting @@ -924,9 +933,9 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: assertmsg = self.helper("_format_assertmsg", assert_.msg) explanation = "\n>assert " + explanation else: - assertmsg = ast.Str("") + assertmsg = astStr("") explanation = "assert " + explanation - template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) + template = ast.BinOp(assertmsg, ast.Add(), astStr(explanation)) msg = self.pop_format_context(template) fmt = self.helper("_format_explanation", msg) err_name = ast.Name("AssertionError", ast.Load()) @@ -938,7 +947,7 @@ def visit_Assert(self, assert_: ast.Assert) -> List[ast.stmt]: # Clear temporary variables by setting them to None. if self.variables: variables = [ast.Name(name, ast.Store()) for name in self.variables] - clear = ast.Assign(variables, ast.NameConstant(None)) + clear = ast.Assign(variables, astNameConstant(None)) self.statements.append(clear) # Fix locations (line numbers/column offsets). for stmt in self.statements: @@ -952,20 +961,20 @@ def visit_NamedExpr(self, name: namedExpr) -> Tuple[namedExpr, str]: # thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) target_id = name.target.id # type: ignore[attr-defined] - inlocs = ast.Compare(ast.Str(target_id), [ast.In()], [locs]) + inlocs = ast.Compare(astStr(target_id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Str(target_id)) + expr = ast.IfExp(test, self.display(name), astStr(target_id)) return name, self.explanation_param(expr) def visit_Name(self, name: ast.Name) -> Tuple[ast.Name, str]: # Display the repr of the name if it's a local variable or # _should_repr_global_name() thinks it's acceptable. locs = ast.Call(self.builtin("locals"), [], []) - inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) + inlocs = ast.Compare(astStr(name.id), [ast.In()], [locs]) dorepr = self.helper("_should_repr_global_name", name) test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) - expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) + expr = ast.IfExp(test, self.display(name), astStr(name.id)) return name, self.explanation_param(expr) def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: @@ -1003,7 +1012,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: self.push_format_context() res, expl = self.visit(v) body.append(ast.Assign([ast.Name(res_var, ast.Store())], res)) - expl_format = self.pop_format_context(ast.Str(expl)) + expl_format = self.pop_format_context(astStr(expl)) call = ast.Call(app, [expl_format], []) self.expl_stmts.append(ast.Expr(call)) if i < levels: @@ -1015,7 +1024,7 @@ def visit_BoolOp(self, boolop: ast.BoolOp) -> Tuple[ast.Name, str]: self.statements = body = inner self.statements = save self.expl_stmts = fail_save - expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) + expl_template = self.helper("_format_boolop", expl_list, astNum(is_or)) expl = self.pop_format_context(expl_template) return ast.Name(res_var, ast.Load()), self.explanation_param(expl) @@ -1118,9 +1127,9 @@ def visit_Compare(self, comp: ast.Compare) -> Tuple[ast.expr, str]: next_expl = f"({next_expl})" results.append(next_res) sym = BINOP_MAP[op.__class__] - syms.append(ast.Str(sym)) + syms.append(astStr(sym)) expl = f"{left_expl} {sym} {next_expl}" - expls.append(ast.Str(expl)) + expls.append(astStr(expl)) res_expr = ast.Compare(left_res, [op], [next_res]) self.statements.append(ast.Assign([store_names[i]], res_expr)) left_res, left_expl = next_res, next_expl diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index f82a81d44c5..9287bcee50c 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -18,6 +18,7 @@ import dataclasses import enum import re +import sys import types from typing import Callable from typing import Iterator @@ -26,6 +27,11 @@ from typing import Optional from typing import Sequence +if sys.version_info >= (3, 8): + astNameConstant = ast.Constant +else: + astNameConstant = ast.NameConstant + __all__ = [ "Expression", @@ -132,7 +138,7 @@ def reject(self, expected: Sequence[TokenType]) -> NoReturn: def expression(s: Scanner) -> ast.Expression: if s.accept(TokenType.EOF): - ret: ast.expr = ast.NameConstant(False) + ret: ast.expr = astNameConstant(False) else: ret = expr(s) s.accept(TokenType.EOF, reject=True) diff --git a/testing/python/collect.py b/testing/python/collect.py index ac3edd395ab..52b34800965 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -897,25 +897,29 @@ def pytest_pycollect_makeitem(collector, name, obj): def test_issue2369_collect_module_fileext(self, pytester: Pytester) -> None: """Ensure we can collect files with weird file extensions as Python modules (#2369)""" - # We'll implement a little finder and loader to import files containing + # Implement a little meta path finder to import files containing # Python source code whose file extension is ".narf". pytester.makeconftest( """ - import sys, os, imp + import sys + import os.path + from importlib.util import spec_from_loader + from importlib.machinery import SourceFileLoader from _pytest.python import Module - class Loader(object): - def load_module(self, name): - return imp.load_source(name, name + ".narf") - class Finder(object): - def find_module(self, name, path=None): - if os.path.exists(name + ".narf"): - return Loader() - sys.meta_path.append(Finder()) + class MetaPathFinder: + def find_spec(self, fullname, path, target=None): + if os.path.exists(fullname + ".narf"): + return spec_from_loader( + fullname, + SourceFileLoader(fullname, fullname + ".narf"), + ) + sys.meta_path.append(MetaPathFinder()) def pytest_collect_file(file_path, parent): if file_path.suffix == ".narf": - return Module.from_parent(path=file_path, parent=parent)""" + return Module.from_parent(path=file_path, parent=parent) + """ ) pytester.makefile( ".narf", From 9d47a39bdd85e6bef40a5674e5f0eded54e54d7e Mon Sep 17 00:00:00 2001 From: pytest bot Date: Sat, 10 Jun 2023 18:52:19 +0000 Subject: [PATCH 16/16] Prepare release version 7.3.2 --- changelog/10169.bugfix.rst | 1 - changelog/10894.bugfix.rst | 1 - changelog/10987.bugfix.rst | 1 - changelog/10999.bugfix.rst | 1 - changelog/11028.bugfix.rst | 1 - changelog/11054.bugfix.rst | 1 - doc/en/announce/index.rst | 1 + doc/en/announce/release-7.3.2.rst | 21 +++++++++++++++++++++ doc/en/builtin.rst | 2 +- doc/en/changelog.rst | 24 ++++++++++++++++++++++++ doc/en/getting-started.rst | 2 +- 11 files changed, 48 insertions(+), 8 deletions(-) delete mode 100644 changelog/10169.bugfix.rst delete mode 100644 changelog/10894.bugfix.rst delete mode 100644 changelog/10987.bugfix.rst delete mode 100644 changelog/10999.bugfix.rst delete mode 100644 changelog/11028.bugfix.rst delete mode 100644 changelog/11054.bugfix.rst create mode 100644 doc/en/announce/release-7.3.2.rst diff --git a/changelog/10169.bugfix.rst b/changelog/10169.bugfix.rst deleted file mode 100644 index cbf3516a93d..00000000000 --- a/changelog/10169.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems. diff --git a/changelog/10894.bugfix.rst b/changelog/10894.bugfix.rst deleted file mode 100644 index 66c6cd73f3c..00000000000 --- a/changelog/10894.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Support for Python 3.12 (beta at the time of writing). diff --git a/changelog/10987.bugfix.rst b/changelog/10987.bugfix.rst deleted file mode 100644 index 2aafff5f512..00000000000 --- a/changelog/10987.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -:confval:`testpaths` is now honored to load root ``conftests``. diff --git a/changelog/10999.bugfix.rst b/changelog/10999.bugfix.rst deleted file mode 100644 index 08c68da01bf..00000000000 --- a/changelog/10999.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments. diff --git a/changelog/11028.bugfix.rst b/changelog/11028.bugfix.rst deleted file mode 100644 index 9efc04ba69f..00000000000 --- a/changelog/11028.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call. diff --git a/changelog/11054.bugfix.rst b/changelog/11054.bugfix.rst deleted file mode 100644 index a8ee04fe304..00000000000 --- a/changelog/11054.bugfix.rst +++ /dev/null @@ -1 +0,0 @@ -Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files). diff --git a/doc/en/announce/index.rst b/doc/en/announce/index.rst index e3919f88ea6..bcc0669a62f 100644 --- a/doc/en/announce/index.rst +++ b/doc/en/announce/index.rst @@ -6,6 +6,7 @@ Release announcements :maxdepth: 2 + release-7.3.2 release-7.3.1 release-7.3.0 release-7.2.2 diff --git a/doc/en/announce/release-7.3.2.rst b/doc/en/announce/release-7.3.2.rst new file mode 100644 index 00000000000..b3b112f0d8e --- /dev/null +++ b/doc/en/announce/release-7.3.2.rst @@ -0,0 +1,21 @@ +pytest-7.3.2 +======================================= + +pytest 7.3.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: + +* Adam J. Stewart +* Alessio Izzo +* Bruno Oliveira +* Ran Benita + + +Happy testing, +The pytest Development Team diff --git a/doc/en/builtin.rst b/doc/en/builtin.rst index 7e9b51d002d..7ae185f6c7d 100644 --- a/doc/en/builtin.rst +++ b/doc/en/builtin.rst @@ -207,7 +207,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a * caplog.record_tuples -> list of (logger_name, level, message) tuples * caplog.clear() -> clear captured records and formatted log output string - monkeypatch -- .../_pytest/monkeypatch.py:29 + monkeypatch -- .../_pytest/monkeypatch.py:30 A convenient fixture for monkey-patching. The fixture provides these methods to modify objects, dictionaries, or diff --git a/doc/en/changelog.rst b/doc/en/changelog.rst index 7d6eb1ffacc..cdc9e66f68b 100644 --- a/doc/en/changelog.rst +++ b/doc/en/changelog.rst @@ -28,6 +28,30 @@ with advance notice in the **Deprecations** section of releases. .. towncrier release notes start +pytest 7.3.2 (2023-06-10) +========================= + +Bug Fixes +--------- + +- `#10169 `_: Fix bug where very long option names could cause pytest to break with ``OSError: [Errno 36] File name too long`` on some systems. + + +- `#10894 `_: Support for Python 3.12 (beta at the time of writing). + + +- `#10987 `_: :confval:`testpaths` is now honored to load root ``conftests``. + + +- `#10999 `_: The `monkeypatch` `setitem`/`delitem` type annotations now allow `TypedDict` arguments. + + +- `#11028 `_: Fixed bug in assertion rewriting where a variable assigned with the walrus operator could not be used later in a function call. + + +- `#11054 `_: Fixed ``--last-failed``'s "(skipped N files)" functionality for files inside of packages (directories with `__init__.py` files). + + pytest 7.3.1 (2023-04-14) ========================= diff --git a/doc/en/getting-started.rst b/doc/en/getting-started.rst index f4157114153..2dbf7d3870c 100644 --- a/doc/en/getting-started.rst +++ b/doc/en/getting-started.rst @@ -22,7 +22,7 @@ Install ``pytest`` .. code-block:: bash $ pytest --version - pytest 7.3.1 + pytest 7.3.2 .. _`simpletest`: