From 897aebe1f49d3f0250e90ff3add084140bb30d33 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 30 Sep 2022 20:52:16 +0200 Subject: [PATCH 1/3] Fix crashes when warnings would be produced by running Python Fixes #6664 --- src/poetry/utils/env.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index ac4bc547c9e..408ce96a511 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -1503,7 +1503,14 @@ def run_pip(self, *args: str, **kwargs: Any) -> int | str: def run_python_script(self, content: str, **kwargs: Any) -> int | str: return self.run( - self._executable, "-I", "-W", "ignore", "-", input_=content, **kwargs + self._executable, + "-I", + "-W", + "ignore", + "-", + input_=content, + stderr=subprocess.PIPE, + **kwargs, ) def _run(self, cmd: list[str], **kwargs: Any) -> int | str: @@ -1513,23 +1520,22 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: call = kwargs.pop("call", False) input_ = kwargs.pop("input_", None) env = kwargs.pop("env", dict(os.environ)) + stderr = kwargs.pop("stderr", subprocess.STDOUT) try: if input_: output = subprocess.run( cmd, stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, + stderr=stderr, input=encode(input_), check=True, **kwargs, ).stdout elif call: - return subprocess.call(cmd, stderr=subprocess.STDOUT, env=env, **kwargs) + return subprocess.call(cmd, stderr=stderr, env=env, **kwargs) else: - output = subprocess.check_output( - cmd, stderr=subprocess.STDOUT, env=env, **kwargs - ) + output = subprocess.check_output(cmd, stderr=stderr, env=env, **kwargs) except CalledProcessError as e: raise EnvCommandError(e, input=input_) From 8e22c570aa563814a8bb5ffb3ab08547b855e669 Mon Sep 17 00:00:00 2001 From: Jerome Leclanche Date: Fri, 30 Sep 2022 23:53:58 +0200 Subject: [PATCH 2/3] Split stdout and stderr in EnvCommandError --- src/poetry/utils/env.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 408ce96a511..2a328df6cd2 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -464,11 +464,13 @@ def __init__(self, e: CalledProcessError, input: str | None = None) -> None: self.e = e message = ( - f"Command {e.cmd} errored with the following return code {e.returncode}," - f" and output: \n{decode(e.output)}" + f"Command {e.cmd} errored with the following return code {e.returncode}\n" + f"Full output:\n{decode(e.output)}\n" ) + if e.stderr: + message += f"\nError output:\n{decode(e.stderr)}\n" if input: - message += f"input was : {input}" + message += f"\ninput was : {input}" super().__init__(message) From 285c7289f89d788082d76e94f4ba36df08204133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Mon, 13 Feb 2023 18:56:12 +0100 Subject: [PATCH 3/3] tests and fine tuning --- src/poetry/utils/env.py | 19 +++++++----- tests/utils/test_env.py | 66 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/src/poetry/utils/env.py b/src/poetry/utils/env.py index 2a328df6cd2..1c01a455c48 100644 --- a/src/poetry/utils/env.py +++ b/src/poetry/utils/env.py @@ -463,15 +463,16 @@ class EnvCommandError(EnvError): def __init__(self, e: CalledProcessError, input: str | None = None) -> None: self.e = e - message = ( - f"Command {e.cmd} errored with the following return code {e.returncode}\n" - f"Full output:\n{decode(e.output)}\n" - ) + message_parts = [ + f"Command {e.cmd} errored with the following return code {e.returncode}" + ] + if e.output: + message_parts.append(f"Output:\n{decode(e.output)}") if e.stderr: - message += f"\nError output:\n{decode(e.stderr)}\n" + message_parts.append(f"Error output:\n{decode(e.stderr)}") if input: - message += f"\ninput was : {input}" - super().__init__(message) + message_parts.append(f"Input:\n{input}") + super().__init__("\n\n".join(message_parts)) class NoCompatiblePythonVersionFound(EnvError): @@ -1535,7 +1536,9 @@ def _run(self, cmd: list[str], **kwargs: Any) -> int | str: **kwargs, ).stdout elif call: - return subprocess.call(cmd, stderr=stderr, env=env, **kwargs) + return subprocess.call( + cmd, stdout=subprocess.PIPE, stderr=stderr, env=env, **kwargs + ) else: output = subprocess.check_output(cmd, stderr=stderr, env=env, **kwargs) except CalledProcessError as e: diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py index 86bf4dfbc50..eb7b2ec59eb 100644 --- a/tests/utils/test_env.py +++ b/tests/utils/test_env.py @@ -947,35 +947,89 @@ def test_run_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): mocker.patch( - "subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command") + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), ) - with pytest.raises(EnvCommandError): + with pytest.raises(EnvCommandError) as error: tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT) subprocess.run.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) def test_call_with_input_and_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): mocker.patch( - "subprocess.run", side_effect=subprocess.CalledProcessError(42, "some_command") + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), ) kwargs = {"call": True} - with pytest.raises(EnvCommandError): + with pytest.raises(EnvCommandError) as error: tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT, **kwargs) subprocess.run.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) def test_call_no_input_with_called_process_error( tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture ): mocker.patch( - "subprocess.call", side_effect=subprocess.CalledProcessError(42, "some_command") + "subprocess.call", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), ) kwargs = {"call": True} - with pytest.raises(EnvCommandError): + with pytest.raises(EnvCommandError) as error: tmp_venv.run("python", "-", **kwargs) subprocess.call.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_check_output_with_called_process_error( + tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture +): + mocker.patch( + "subprocess.check_output", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run("python", "-") + subprocess.check_output.assert_called_once() + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_run_python_script_called_process_error( + tmp_dir: str, tmp_venv: VirtualEnv, mocker: MockerFixture +): + mocker.patch( + "subprocess.run", + side_effect=subprocess.CalledProcessError( + 42, "some_command", "some output", "some error" + ), + ) + with pytest.raises(EnvCommandError) as error: + tmp_venv.run_python_script(MINIMAL_SCRIPT) + assert "some output" in str(error.value) + assert "some error" in str(error.value) + + +def test_run_python_script_only_stdout(tmp_dir: str, tmp_venv: VirtualEnv): + output = tmp_venv.run_python_script( + "import sys; print('some warning', file=sys.stderr); print('some output')" + ) + assert "some output" in output + assert "some warning" not in output def test_create_venv_tries_to_find_a_compatible_python_executable_using_generic_ones_first( # noqa: E501