From 1a772a3e330d900e00187109f7a9b830312ad87a Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:08:28 +0300 Subject: [PATCH 01/22] progress --- src/_pytest/pytester.py | 129 +++++++++++++++++---------------------- testing/test_pytester.py | 34 +++++++++++ 2 files changed, 90 insertions(+), 73 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 3f7520ee4ad..954732a893e 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -889,18 +889,18 @@ def test_something(pytester): return self._makefile(".txt", args, kwargs) def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: - """Prepend a directory to sys.path, defaults to :attr:`path`. - - This is undone automatically when this object dies at the end of each - test. - - :param path: - The path. - """ + """Prepend a directory to sys.path, defaults to :attr:`path`.""" if path is None: path = self.path - self._monkeypatch.syspath_prepend(str(path)) + path_str = str(path) + self._monkeypatch.syspath_prepend(path_str) + self._syspath_prepended = path_str + + # Store the prepended path in an attribute that persists across method calls + if not hasattr(self, '_prepended_syspaths'): + self._prepended_syspaths = [] + self._prepended_syspaths.append(path_str) def mkdir(self, name: str | os.PathLike[str]) -> Path: """Create a new (sub)directory. @@ -1323,12 +1323,12 @@ def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | No return None def popen( - self, - cmdargs: Sequence[str | os.PathLike[str]], - stdout: int | TextIO = subprocess.PIPE, - stderr: int | TextIO = subprocess.PIPE, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, - **kw, + self, + cmdargs: Sequence[str | os.PathLike[str]], + stdout: int | TextIO = subprocess.PIPE, + stderr: int | TextIO = subprocess.PIPE, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + **kw, ): """Invoke :py:class:`subprocess.Popen`. @@ -1337,10 +1337,18 @@ def popen( You probably want to use :py:meth:`run` instead. """ - env = os.environ.copy() - env["PYTHONPATH"] = os.pathsep.join( - filter(None, [os.getcwd(), env.get("PYTHONPATH", "")]) + env = kw.pop('env', os.environ.copy()) + pythonpath = env.get("PYTHONPATH", "") + + paths_to_add = [os.getcwd()] + if hasattr(self, "_syspath_prepended"): + paths_to_add.insert(0, self._syspath_prepended) + + pythonpath = os.pathsep.join( + filter(None, paths_to_add + [pythonpath]) ) + + env["PYTHONPATH"] = pythonpath kw["env"] = env if stdin is self.CLOSE_STDIN: @@ -1361,42 +1369,12 @@ def popen( return popen def run( - self, - *cmdargs: str | os.PathLike[str], - timeout: float | None = None, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + self, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + env: dict[str, str] | None = None, ) -> RunResult: - """Run a command with arguments. - - Run a process using :py:class:`subprocess.Popen` saving the stdout and - stderr. - - :param cmdargs: - The sequence of arguments to pass to :py:class:`subprocess.Popen`, - with path-like objects being converted to :py:class:`str` - automatically. - :param timeout: - The period in seconds after which to timeout and raise - :py:class:`Pytester.TimeoutExpired`. - :param stdin: - Optional standard input. - - - If it is ``CLOSE_STDIN`` (Default), then this method calls - :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and - the standard input is closed immediately after the new command is - started. - - - If it is of type :py:class:`bytes`, these bytes are sent to the - standard input of the command. - - - Otherwise, it is passed through to :py:class:`subprocess.Popen`. - For further information in this case, consult the document of the - ``stdin`` parameter in :py:class:`subprocess.Popen`. - :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int - :returns: - The result. - - """ __tracebackhide__ = True cmdargs = tuple(os.fspath(arg) for arg in cmdargs) @@ -1413,6 +1391,7 @@ def run( stdout=f1, stderr=f2, close_fds=(sys.platform != "win32"), + env=env, ) if popen.stdin is not None: popen.stdin.close() @@ -1441,8 +1420,10 @@ def handle_timeout() -> None: self._dump_lines(out, sys.stdout) self._dump_lines(err, sys.stderr) - with contextlib.suppress(ValueError): + try: ret = ExitCode(ret) + except ValueError: + pass return RunResult(ret, out, err, timing.time() - now) def _dump_lines(self, lines, fp): @@ -1464,32 +1445,34 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: str | os.PathLike[str], timeout: float | None = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: - """Run pytest as a subprocess with given arguments. - - Any plugins added to the :py:attr:`plugins` list will be added using the - ``-p`` command line option. Additionally ``--basetemp`` is used to put - any temporary files and directories in a numbered directory prefixed - with "runpytest-" to not conflict with the normal numbered pytest - location for temporary files and directories. - - :param args: - The sequence of arguments to pass to the pytest subprocess. - :param timeout: - The period in seconds after which to timeout and raise - :py:class:`Pytester.TimeoutExpired`. - :returns: - The result. - """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) args = (f"--basetemp={p}", *args) plugins = [x for x in self.plugins if isinstance(x, str)] if plugins: args = ("-p", plugins[0], *args) - args = self._getpytestargs() + args - return self.run(*args, timeout=timeout) + + env = os.environ.copy() + pythonpath = env.get("PYTHONPATH", "") + + if hasattr(self, "_syspath_prepended"): + pythonpath = os.pathsep.join(filter(None, [self._syspath_prepended, pythonpath])) + + env["PYTHONPATH"] = pythonpath + + python_executable = sys.executable + pytest_command = [python_executable, "-m", "pytest"] + + if hasattr(self, "_syspath_prepended"): + prepend_command = f"import sys; sys.path.insert(0, '{self._syspath_prepended}');" + pytest_command = [python_executable, "-c", + f"{prepend_command} import pytest; pytest.main({list(args)})"] + else: + pytest_command.extend(args) + + return self.run(*pytest_command, timeout=timeout, env=env) def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run pytest using pexpect. diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 87714b4708f..6adf9c64aca 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -835,3 +835,37 @@ def test_two(): result.assert_outcomes(passed=1, deselected=1) # If deselected is not passed, it is not checked at all. result.assert_outcomes(passed=1) + + +def test_syspathinsert__in_process__path_exists(pytester: Pytester): + some_dir = "abcd" + pytester.syspathinsert(some_dir) + pytester.makepyfile( + f""" + import sys + + def test_foo(): + assert "{some_dir}" in sys.path + """ + ) + + result = pytester.runpytest_inprocess() + + result.assert_outcomes(passed=1) + + +def test_syspathinsert__sub_process__path_exists(pytester: Pytester): + some_dir = "abcd" + pytester.syspathinsert(some_dir) + pytester.makepyfile( + f""" + import sys + + def test_foo(): + assert "{some_dir}" in sys.path + """ + ) + + result = pytester.runpytest_subprocess(timeout=1) + + result.assert_outcomes(passed=1) From 7c775f35eec11af75ea76037f0bdc381ead3415b Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:11:20 +0300 Subject: [PATCH 02/22] Update Authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 8bc8ad5cbde..0d70e1d5e91 100644 --- a/AUTHORS +++ b/AUTHORS @@ -318,6 +318,7 @@ Oliver Bestwalter Omar Kohl Omer Hadari Ondřej Súkup +Orel Damari Oscar Benjamin Parth Patel Patrick Hayes From 0a569caed1e4977e2b3ca94277c433643f317a22 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:12:03 +0000 Subject: [PATCH 03/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 48 ++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 954732a893e..fa43d8a441a 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -7,7 +7,6 @@ from __future__ import annotations import collections.abc -import contextlib from fnmatch import fnmatch import gc import importlib @@ -898,7 +897,7 @@ def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: self._syspath_prepended = path_str # Store the prepended path in an attribute that persists across method calls - if not hasattr(self, '_prepended_syspaths'): + if not hasattr(self, "_prepended_syspaths"): self._prepended_syspaths = [] self._prepended_syspaths.append(path_str) @@ -1323,12 +1322,12 @@ def collect_by_name(self, modcol: Collector, name: str) -> Item | Collector | No return None def popen( - self, - cmdargs: Sequence[str | os.PathLike[str]], - stdout: int | TextIO = subprocess.PIPE, - stderr: int | TextIO = subprocess.PIPE, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, - **kw, + self, + cmdargs: Sequence[str | os.PathLike[str]], + stdout: int | TextIO = subprocess.PIPE, + stderr: int | TextIO = subprocess.PIPE, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + **kw, ): """Invoke :py:class:`subprocess.Popen`. @@ -1337,16 +1336,14 @@ def popen( You probably want to use :py:meth:`run` instead. """ - env = kw.pop('env', os.environ.copy()) + env = kw.pop("env", os.environ.copy()) pythonpath = env.get("PYTHONPATH", "") paths_to_add = [os.getcwd()] if hasattr(self, "_syspath_prepended"): paths_to_add.insert(0, self._syspath_prepended) - pythonpath = os.pathsep.join( - filter(None, paths_to_add + [pythonpath]) - ) + pythonpath = os.pathsep.join(filter(None, paths_to_add + [pythonpath])) env["PYTHONPATH"] = pythonpath kw["env"] = env @@ -1369,11 +1366,11 @@ def popen( return popen def run( - self, - *cmdargs: str | os.PathLike[str], - timeout: float | None = None, - stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, - env: dict[str, str] | None = None, + self, + *cmdargs: str | os.PathLike[str], + timeout: float | None = None, + stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, + env: dict[str, str] | None = None, ) -> RunResult: __tracebackhide__ = True @@ -1445,7 +1442,7 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: str | os.PathLike[str], timeout: float | None = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) @@ -1458,7 +1455,9 @@ def runpytest_subprocess( pythonpath = env.get("PYTHONPATH", "") if hasattr(self, "_syspath_prepended"): - pythonpath = os.pathsep.join(filter(None, [self._syspath_prepended, pythonpath])) + pythonpath = os.pathsep.join( + filter(None, [self._syspath_prepended, pythonpath]) + ) env["PYTHONPATH"] = pythonpath @@ -1466,9 +1465,14 @@ def runpytest_subprocess( pytest_command = [python_executable, "-m", "pytest"] if hasattr(self, "_syspath_prepended"): - prepend_command = f"import sys; sys.path.insert(0, '{self._syspath_prepended}');" - pytest_command = [python_executable, "-c", - f"{prepend_command} import pytest; pytest.main({list(args)})"] + prepend_command = ( + f"import sys; sys.path.insert(0, '{self._syspath_prepended}');" + ) + pytest_command = [ + python_executable, + "-c", + f"{prepend_command} import pytest; pytest.main({list(args)})", + ] else: pytest_command.extend(args) From 6f95f59d1c65343d1e9a006115fa0b67a665df52 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:16:17 +0300 Subject: [PATCH 04/22] Fix DOCString --- src/_pytest/pytester.py | 46 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index fa43d8a441a..868c0e64c57 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -888,7 +888,12 @@ def test_something(pytester): return self._makefile(".txt", args, kwargs) def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: - """Prepend a directory to sys.path, defaults to :attr:`path`.""" + """Prepend a directory to sys.path, defaults to :attr:`path`. + This is undone automatically when this object dies at the end of each + test. + :param path: + The path. + """ if path is None: path = self.path @@ -1372,6 +1377,31 @@ def run( stdin: NotSetType | bytes | IO[Any] | int = CLOSE_STDIN, env: dict[str, str] | None = None, ) -> RunResult: + """Run a command with arguments. + Run a process using :py:class:`subprocess.Popen` saving the stdout and + stderr. + :param cmdargs: + The sequence of arguments to pass to :py:class:`subprocess.Popen`, + with path-like objects being converted to :py:class:`str` + automatically. + :param timeout: + The period in seconds after which to timeout and raise + :py:class:`Pytester.TimeoutExpired`. + :param stdin: + Optional standard input. + - If it is ``CLOSE_STDIN`` (Default), then this method calls + :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and + the standard input is closed immediately after the new command is + started. + - If it is of type :py:class:`bytes`, these bytes are sent to the + standard input of the command. + - Otherwise, it is passed through to :py:class:`subprocess.Popen`. + For further information in this case, consult the document of the + ``stdin`` parameter in :py:class:`subprocess.Popen`. + :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int + :returns: + The result. + """ __tracebackhide__ = True cmdargs = tuple(os.fspath(arg) for arg in cmdargs) @@ -1444,6 +1474,20 @@ def runpython_c(self, command: str) -> RunResult: def runpytest_subprocess( self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: + """Run pytest as a subprocess with given arguments. + Any plugins added to the :py:attr:`plugins` list will be added using the + ``-p`` command line option. Additionally ``--basetemp`` is used to put + any temporary files and directories in a numbered directory prefixed + with "runpytest-" to not conflict with the normal numbered pytest + location for temporary files and directories. + :param args: + The sequence of arguments to pass to the pytest subprocess. + :param timeout: + The period in seconds after which to timeout and raise + :py:class:`Pytester.TimeoutExpired`. + :returns: + The result. + """ __tracebackhide__ = True p = make_numbered_dir(root=self.path, prefix="runpytest-", mode=0o700) args = (f"--basetemp={p}", *args) From c78ed4af7adaf839a9c4dfcb8a094a928e012371 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:17:34 +0300 Subject: [PATCH 05/22] Fix DOCString --- src/_pytest/pytester.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 868c0e64c57..26a6719cec8 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -889,8 +889,10 @@ def test_something(pytester): def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: """Prepend a directory to sys.path, defaults to :attr:`path`. + This is undone automatically when this object dies at the end of each test. + :param path: The path. """ @@ -1378,8 +1380,10 @@ def run( env: dict[str, str] | None = None, ) -> RunResult: """Run a command with arguments. + Run a process using :py:class:`subprocess.Popen` saving the stdout and stderr. + :param cmdargs: The sequence of arguments to pass to :py:class:`subprocess.Popen`, with path-like objects being converted to :py:class:`str` @@ -1389,10 +1393,12 @@ def run( :py:class:`Pytester.TimeoutExpired`. :param stdin: Optional standard input. + - If it is ``CLOSE_STDIN`` (Default), then this method calls :py:class:`subprocess.Popen` with ``stdin=subprocess.PIPE``, and the standard input is closed immediately after the new command is started. + - If it is of type :py:class:`bytes`, these bytes are sent to the standard input of the command. - Otherwise, it is passed through to :py:class:`subprocess.Popen`. @@ -1401,6 +1407,7 @@ def run( :type stdin: _pytest.compat.NotSetType | bytes | IO[Any] | int :returns: The result. + """ __tracebackhide__ = True @@ -1475,11 +1482,13 @@ def runpytest_subprocess( self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. + Any plugins added to the :py:attr:`plugins` list will be added using the ``-p`` command line option. Additionally ``--basetemp`` is used to put any temporary files and directories in a numbered directory prefixed with "runpytest-" to not conflict with the normal numbered pytest location for temporary files and directories. + :param args: The sequence of arguments to pass to the pytest subprocess. :param timeout: From b88d0d3422d383bff2f2a73f673f7bcd90c18861 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:17:57 +0000 Subject: [PATCH 06/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 26a6719cec8..4538f34c0d3 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1488,7 +1488,7 @@ def runpytest_subprocess( any temporary files and directories in a numbered directory prefixed with "runpytest-" to not conflict with the normal numbered pytest location for temporary files and directories. - + :param args: The sequence of arguments to pass to the pytest subprocess. :param timeout: From 5bb131fabfc490df9f3c9344faf33709d896840a Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:19:10 +0300 Subject: [PATCH 07/22] Fix DOCString --- src/_pytest/pytester.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 26a6719cec8..32aa5eedd44 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1401,6 +1401,7 @@ def run( - If it is of type :py:class:`bytes`, these bytes are sent to the standard input of the command. + - Otherwise, it is passed through to :py:class:`subprocess.Popen`. For further information in this case, consult the document of the ``stdin`` parameter in :py:class:`subprocess.Popen`. @@ -1488,7 +1489,7 @@ def runpytest_subprocess( any temporary files and directories in a numbered directory prefixed with "runpytest-" to not conflict with the normal numbered pytest location for temporary files and directories. - + :param args: The sequence of arguments to pass to the pytest subprocess. :param timeout: From 4d691ea52eb7f748cb408fd8d4fddf821480a4fe Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:23:34 +0300 Subject: [PATCH 08/22] progress --- src/_pytest/pytester.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 32aa5eedd44..234dd98fcd6 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -7,6 +7,7 @@ from __future__ import annotations import collections.abc +import contextlib from fnmatch import fnmatch import gc import importlib @@ -1455,10 +1456,8 @@ def handle_timeout() -> None: self._dump_lines(out, sys.stdout) self._dump_lines(err, sys.stderr) - try: + with contextlib.suppress(ValueError): ret = ExitCode(ret) - except ValueError: - pass return RunResult(ret, out, err, timing.time() - now) def _dump_lines(self, lines, fp): From a87a19083505bcf16872082025234c4444e569bc Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:24:22 +0300 Subject: [PATCH 09/22] progress --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 234dd98fcd6..953f776fdf5 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1427,7 +1427,7 @@ def run( stdout=f1, stderr=f2, close_fds=(sys.platform != "win32"), - env=env, + env=env ) if popen.stdin is not None: popen.stdin.close() From 6b468fec9a93630f6c731103e7b8f40edcc64d56 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:24:50 +0000 Subject: [PATCH 10/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 953f776fdf5..234dd98fcd6 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1427,7 +1427,7 @@ def run( stdout=f1, stderr=f2, close_fds=(sys.platform != "win32"), - env=env + env=env, ) if popen.stdin is not None: popen.stdin.close() From 30f2ba775381dd3e462cce22d9676c5aa028058d Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:27:26 +0300 Subject: [PATCH 11/22] added bugfix.rst --- changelog/10651.bugfix.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/10651.bugfix.rst diff --git a/changelog/10651.bugfix.rst b/changelog/10651.bugfix.rst new file mode 100644 index 00000000000..eadc4d0ff07 --- /dev/null +++ b/changelog/10651.bugfix.rst @@ -0,0 +1 @@ +Fixed bug where the `Pytester.syspathinsert` has no effect when using subprocess. \ No newline at end of file From d31e435de80795f7d83496788b994d84c802afed Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:28:01 +0000 Subject: [PATCH 12/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/10651.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/10651.bugfix.rst b/changelog/10651.bugfix.rst index eadc4d0ff07..3ad18232cf1 100644 --- a/changelog/10651.bugfix.rst +++ b/changelog/10651.bugfix.rst @@ -1 +1 @@ -Fixed bug where the `Pytester.syspathinsert` has no effect when using subprocess. \ No newline at end of file +Fixed bug where the `Pytester.syspathinsert` has no effect when using subprocess. From ac930cd38d8f4868586ff0522d5c6143e5c15063 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:32:08 +0300 Subject: [PATCH 13/22] progress --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 234dd98fcd6..f72a8e5f897 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1527,7 +1527,7 @@ def runpytest_subprocess( f"{prepend_command} import pytest; pytest.main({list(args)})", ] else: - pytest_command.extend(args) + pytest_command.extend(str(arg) for arg in args) # Convert all args to strings return self.run(*pytest_command, timeout=timeout, env=env) From 03d80d01f080626f084c48116634356d769893a2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:32:57 +0000 Subject: [PATCH 14/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index f72a8e5f897..a3a9749ee6c 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1527,7 +1527,9 @@ def runpytest_subprocess( f"{prepend_command} import pytest; pytest.main({list(args)})", ] else: - pytest_command.extend(str(arg) for arg in args) # Convert all args to strings + pytest_command.extend( + str(arg) for arg in args + ) # Convert all args to strings return self.run(*pytest_command, timeout=timeout, env=env) From 3d24eb38f1996a2e4852cd839af5bbe8cdceb8e3 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:37:38 +0300 Subject: [PATCH 15/22] progress --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index f72a8e5f897..5a5489638aa 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1351,7 +1351,7 @@ def popen( if hasattr(self, "_syspath_prepended"): paths_to_add.insert(0, self._syspath_prepended) - pythonpath = os.pathsep.join(filter(None, paths_to_add + [pythonpath])) + pythonpath = os.pathsep.join(filter(None, [*paths_to_add, pythonpath])) env["PYTHONPATH"] = pythonpath kw["env"] = env From 269045592285ad683c35d6b806bb6088e0dc0d7f Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:41:46 +0300 Subject: [PATCH 16/22] progress --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 802b2efc771..496889ed460 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1344,7 +1344,7 @@ def popen( You probably want to use :py:meth:`run` instead. """ - env = kw.pop("env", os.environ.copy()) + env = kw.pop('env', None) or os.environ.copy() pythonpath = env.get("PYTHONPATH", "") paths_to_add = [os.getcwd()] From 6679929d419aab13ef4b596165da0023d60f35e0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:42:06 +0000 Subject: [PATCH 17/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 496889ed460..ac91cb1c30a 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1344,7 +1344,7 @@ def popen( You probably want to use :py:meth:`run` instead. """ - env = kw.pop('env', None) or os.environ.copy() + env = kw.pop("env", None) or os.environ.copy() pythonpath = env.get("PYTHONPATH", "") paths_to_add = [os.getcwd()] From 080b763d344b5a60d574f668a7da6b5a717b3ed9 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 13 Sep 2024 10:53:30 +0300 Subject: [PATCH 18/22] Fix SyntaxError in Pytester.runpytest_subprocess due to unescaped paths --- src/_pytest/pytester.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 496889ed460..d6f58841644 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1519,17 +1519,15 @@ def runpytest_subprocess( if hasattr(self, "_syspath_prepended"): prepend_command = ( - f"import sys; sys.path.insert(0, '{self._syspath_prepended}');" + f"import sys; sys.path.insert(0, {repr(self._syspath_prepended)});" ) pytest_command = [ python_executable, "-c", - f"{prepend_command} import pytest; pytest.main({list(args)})", + f"{prepend_command} import pytest; pytest.main({repr(list(args))})", ] else: - pytest_command.extend( - str(arg) for arg in args - ) # Convert all args to strings + pytest_command.extend(str(arg) for arg in args) return self.run(*pytest_command, timeout=timeout, env=env) From 571942229c6fae2e2ab0fb9d2e1ec757a8a986b9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 13 Sep 2024 07:53:57 +0000 Subject: [PATCH 19/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 7a76928ec8a..0b8492e9c5e 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1519,12 +1519,12 @@ def runpytest_subprocess( if hasattr(self, "_syspath_prepended"): prepend_command = ( - f"import sys; sys.path.insert(0, {repr(self._syspath_prepended)});" + f"import sys; sys.path.insert(0, {self._syspath_prepended!r});" ) pytest_command = [ python_executable, "-c", - f"{prepend_command} import pytest; pytest.main({repr(list(args))})", + f"{prepend_command} import pytest; pytest.main({list(args)!r})", ] else: pytest_command.extend(str(arg) for arg in args) From 2924ad0b1408b4e4891703ed8bed1284c5433f44 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 20 Sep 2024 10:14:41 +0300 Subject: [PATCH 20/22] [PR Fix] Update testing/test_pytester.py remove timeout Co-authored-by: Bruno Oliveira --- testing/test_pytester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index 6adf9c64aca..1437cb8931d 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -866,6 +866,6 @@ def test_foo(): """ ) - result = pytester.runpytest_subprocess(timeout=1) + result = pytester.runpytest_subprocess() result.assert_outcomes(passed=1) From deee731a533dec9776527e374f8fef35086aff02 Mon Sep 17 00:00:00 2001 From: Orel Damari Date: Fri, 20 Sep 2024 10:24:48 +0300 Subject: [PATCH 21/22] PR Fix - multiple env variable fix --- src/_pytest/pytester.py | 44 +++++++++++------------------------------ 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 0b8492e9c5e..b48a2ce7035 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -689,6 +689,7 @@ def __init__( self._request.addfinalizer(self._finalize) self._method = self._request.config.getoption("--runpytest") self._test_tmproot = tmp_path_factory.mktemp(f"tmp-{name}", numbered=True) + self._prepended_syspaths: list[Path] = [] self._monkeypatch = mp = monkeypatch self.chdir() @@ -900,14 +901,9 @@ def syspathinsert(self, path: str | os.PathLike[str] | None = None) -> None: if path is None: path = self.path - path_str = str(path) - self._monkeypatch.syspath_prepend(path_str) - self._syspath_prepended = path_str - - # Store the prepended path in an attribute that persists across method calls - if not hasattr(self, "_prepended_syspaths"): - self._prepended_syspaths = [] - self._prepended_syspaths.append(path_str) + path_obj = Path(path) + self._monkeypatch.syspath_prepend(str(path_obj)) + self._prepended_syspaths.append(path_obj) def mkdir(self, name: str | os.PathLike[str]) -> Path: """Create a new (sub)directory. @@ -1347,11 +1343,9 @@ def popen( env = kw.pop("env", None) or os.environ.copy() pythonpath = env.get("PYTHONPATH", "") - paths_to_add = [os.getcwd()] - if hasattr(self, "_syspath_prepended"): - paths_to_add.insert(0, self._syspath_prepended) + paths_to_add = [os.getcwd(), *self._prepended_syspaths] - pythonpath = os.pathsep.join(filter(None, [*paths_to_add, pythonpath])) + pythonpath = os.pathsep.join(filter(None, [*map(str, paths_to_add), pythonpath])) env["PYTHONPATH"] = pythonpath kw["env"] = env @@ -1479,7 +1473,7 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: str | os.PathLike[str], timeout: float | None = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1507,29 +1501,13 @@ def runpytest_subprocess( env = os.environ.copy() pythonpath = env.get("PYTHONPATH", "") - if hasattr(self, "_syspath_prepended"): - pythonpath = os.pathsep.join( - filter(None, [self._syspath_prepended, pythonpath]) - ) + # Add all prepended syspaths to PYTHONPATH + prepended_paths = [str(path) for path in self._prepended_syspaths] + pythonpath = os.pathsep.join(filter(None, [*prepended_paths, pythonpath])) env["PYTHONPATH"] = pythonpath - python_executable = sys.executable - pytest_command = [python_executable, "-m", "pytest"] - - if hasattr(self, "_syspath_prepended"): - prepend_command = ( - f"import sys; sys.path.insert(0, {self._syspath_prepended!r});" - ) - pytest_command = [ - python_executable, - "-c", - f"{prepend_command} import pytest; pytest.main({list(args)!r})", - ] - else: - pytest_command.extend(str(arg) for arg in args) - - return self.run(*pytest_command, timeout=timeout, env=env) + return self.run(*[sys.executable, "-m", "pytest", *args], timeout=timeout, env=env) def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run pytest using pexpect. From 8bd1a5bc3fdc16d1aa5aa259f4067e43654543bd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 20 Sep 2024 07:25:11 +0000 Subject: [PATCH 22/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/pytester.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index b48a2ce7035..57e18ddcf25 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1345,7 +1345,9 @@ def popen( paths_to_add = [os.getcwd(), *self._prepended_syspaths] - pythonpath = os.pathsep.join(filter(None, [*map(str, paths_to_add), pythonpath])) + pythonpath = os.pathsep.join( + filter(None, [*map(str, paths_to_add), pythonpath]) + ) env["PYTHONPATH"] = pythonpath kw["env"] = env @@ -1473,7 +1475,7 @@ def runpython_c(self, command: str) -> RunResult: return self.run(sys.executable, "-c", command) def runpytest_subprocess( - self, *args: str | os.PathLike[str], timeout: float | None = None + self, *args: str | os.PathLike[str], timeout: float | None = None ) -> RunResult: """Run pytest as a subprocess with given arguments. @@ -1507,7 +1509,9 @@ def runpytest_subprocess( env["PYTHONPATH"] = pythonpath - return self.run(*[sys.executable, "-m", "pytest", *args], timeout=timeout, env=env) + return self.run( + *[sys.executable, "-m", "pytest", *args], timeout=timeout, env=env + ) def spawn_pytest(self, string: str, expect_timeout: float = 10.0) -> pexpect.spawn: """Run pytest using pexpect.