Repository: python-poetry/poetry

Issue:
Env.run doesn't check return code if input is passed in

- [x] I am on the [latest](https://github.com/sdispater/poetry/releases/latest) Poetry version.
- [x] I have searched the [issues](https://github.com/sdispater/poetry/issues) of this repo and believe that this is not a duplicate.
- [x] If an exception occurs when executing a command, I executed it again in debug mode (`-vvv` option).

- **OS version and name**:  macOS 10.14.3
- **Poetry version**: 0.12.15

## Issue

Env.run() is used internally for running a number of scripts to introspect the managed virtualenv and contained python. These scripts are passed as input to the function and then to subprocess.communicate(). However, in the case of an error from the spawned python process, the return code is not checked and any error text is passed back to the caller - which will assume its valid output. This makes for confusing error messages from poetry - making it harder to diagnose issues like #654 and #1071 





Discussion:

Resolving patch:
diff --git a/poetry/utils/_compat.py b/poetry/utils/_compat.py
--- a/poetry/utils/_compat.py
+++ b/poetry/utils/_compat.py
@@ -46,6 +46,13 @@
 else:
     from pathlib2 import Path
 
+if PY35:
+    import subprocess as subprocess
+    from subprocess import CalledProcessError
+
+else:
+    import subprocess32 as subprocess
+    from subprocess32 import CalledProcessError
 
 if not PY36:
     from collections import OrderedDict
diff --git a/poetry/utils/env.py b/poetry/utils/env.py
--- a/poetry/utils/env.py
+++ b/poetry/utils/env.py
@@ -8,7 +8,6 @@
 import warnings
 
 from contextlib import contextmanager
-from subprocess import CalledProcessError
 from typing import Any
 from typing import Dict
 from typing import Optional
@@ -21,6 +20,8 @@
 from poetry.utils._compat import decode
 from poetry.utils._compat import encode
 from poetry.utils._compat import list_to_shell_command
+from poetry.utils._compat import subprocess
+from poetry.utils._compat import CalledProcessError
 from poetry.version.markers import BaseMarker
 
 
@@ -91,11 +92,14 @@ class EnvError(Exception):
 
 
 class EnvCommandError(EnvError):
-    def __init__(self, e):  # type: (CalledProcessError) -> None
-        message = "Command {} errored with the following output: \n{}".format(
-            e.cmd, decode(e.output)
-        )
+    def __init__(self, e, input=None):  # type: (CalledProcessError) -> None
+        self.e = e
 
+        message = "Command {} errored with the following return code {}, and output: \n{}".format(
+            e.cmd, e.returncode, decode(e.output)
+        )
+        if input:
+            message += "input was : {}".format(input)
         super(EnvCommandError, self).__init__(message)
 
 
@@ -361,20 +365,19 @@ def run(self, bin, *args, **kwargs):
 
         if shell:
             cmd = list_to_shell_command(cmd)
-
         try:
             if self._is_windows:
                 kwargs["shell"] = True
 
             if input_:
-                p = subprocess.Popen(
+                output = subprocess.run(
                     cmd,
-                    stdin=subprocess.PIPE,
                     stdout=subprocess.PIPE,
                     stderr=subprocess.STDOUT,
+                    input=encode(input_),
+                    check=True,
                     **kwargs
-                )
-                output = p.communicate(encode(input_))[0]
+                ).stdout
             elif call:
                 return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs)
             else:
@@ -382,7 +385,7 @@ def run(self, bin, *args, **kwargs):
                     cmd, stderr=subprocess.STDOUT, **kwargs
                 )
         except CalledProcessError as e:
-            raise EnvCommandError(e)
+            raise EnvCommandError(e, input=input_)
 
         return decode(output)
 


Test patch:
diff --git a/tests/utils/test_env.py b/tests/utils/test_env.py
--- a/tests/utils/test_env.py
+++ b/tests/utils/test_env.py
@@ -1,8 +1,23 @@
 import os
+import pytest
+import shutil
 
 from poetry.utils._compat import Path
 from poetry.utils.env import Env
 from poetry.utils.env import VirtualEnv
+from poetry.utils.env import EnvCommandError
+
+MINIMAL_SCRIPT = """\
+
+print("Minimal Output"),
+"""
+
+# Script expected to fail.
+ERRORING_SCRIPT = """\
+import nullpackage
+
+print("nullpackage loaded"),
+"""
 
 
 def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir):
@@ -25,14 +40,22 @@ def test_env_get_in_project_venv(tmp_dir, environ):
 
     assert venv.path == Path(tmp_dir) / ".venv"
 
+    shutil.rmtree(str(venv.path))
 
-def test_env_has_symlinks_on_nix(tmp_dir):
-    venv_path = Path(tmp_dir) / "Virtual Env"
+
+@pytest.fixture
+def tmp_venv(tmp_dir, request):
+    venv_path = Path(tmp_dir) / "venv"
 
     Env.build_venv(str(venv_path))
 
     venv = VirtualEnv(venv_path)
+    yield venv
+
+    shutil.rmtree(str(venv.path))
+
 
+def test_env_has_symlinks_on_nix(tmp_dir, tmp_venv):
     venv_available = False
     try:
         from venv import EnvBuilder
@@ -42,4 +65,20 @@ def test_env_has_symlinks_on_nix(tmp_dir):
         pass
 
     if os.name != "nt" and venv_available:
-        assert os.path.islink(venv.python)
+        assert os.path.islink(tmp_venv.python)
+
+
+def test_run_with_input(tmp_dir, tmp_venv):
+    result = tmp_venv.run("python", "-", input_=MINIMAL_SCRIPT)
+
+    assert result == "Minimal Output" + os.linesep
+
+
+def test_run_with_input_non_zero_return(tmp_dir, tmp_venv):
+
+    with pytest.raises(EnvCommandError) as processError:
+
+        # Test command that will return non-zero returncode.
+        result = tmp_venv.run("python", "-", input_=ERRORING_SCRIPT)
+
+    assert processError.value.e.returncode == 1