[Python-checkins] cpython (merge 3.3 -> default): Issue #15533: Merge fix from 3.3.

chris.jerdonek python-checkins at python.org
Sun Sep 30 11:24:42 CEST 2012


http://hg.python.org/cpython/rev/37f4aa15a1c6
changeset:   79316:37f4aa15a1c6
parent:      79313:f2a7774e50e1
parent:      79315:f66ff96f0030
user:        Chris Jerdonek <chris.jerdonek at gmail.com>
date:        Sun Sep 30 02:22:50 2012 -0700
summary:
  Issue #15533: Merge fix from 3.3.

files:
  Doc/library/subprocess.rst  |    8 +-
  Lib/test/test_subprocess.py |  123 ++++++++++++++++++-----
  Misc/NEWS                   |    3 +
  3 files changed, 101 insertions(+), 33 deletions(-)


diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -484,10 +484,10 @@
    .. versionadded:: 3.2
       The *pass_fds* parameter was added.
 
-   If *cwd* is not ``None``, the child's current directory will be changed to *cwd*
-   before it is executed.  Note that this directory is not considered when
-   searching the executable, so you can't specify the program's path relative to
-   *cwd*.
+   If *cwd* is not ``None``, the function changes the working directory to
+   *cwd* before executing the child.  In particular, the function looks for
+   *executable* (or for the first item in *args*) relative to *cwd* if the
+   executable path is a relative path.
 
    If *restore_signals* is True (the default) all signals that Python has set to
    SIG_IGN are restored to SIG_DFL in the child process before the exec.
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1,4 +1,5 @@
 import unittest
+from test import script_helper
 from test import support
 import subprocess
 import sys
@@ -191,15 +192,101 @@
         p.wait()
         self.assertEqual(p.stderr, None)
 
+    # For use in the test_cwd* tests below.
+    def _normalize_cwd(self, cwd):
+        # Normalize an expected cwd (for Tru64 support).
+        # We can't use os.path.realpath since it doesn't expand Tru64 {memb}
+        # strings.  See bug #1063571.
+        original_cwd = os.getcwd()
+        os.chdir(cwd)
+        cwd = os.getcwd()
+        os.chdir(original_cwd)
+        return cwd
+
+    # For use in the test_cwd* tests below.
+    def _split_python_path(self):
+        # Return normalized (python_dir, python_base).
+        python_path = os.path.realpath(sys.executable)
+        return os.path.split(python_path)
+
+    # For use in the test_cwd* tests below.
+    def _assert_cwd(self, expected_cwd, python_arg, **kwargs):
+        # Invoke Python via Popen, and assert that (1) the call succeeds,
+        # and that (2) the current working directory of the child process
+        # matches *expected_cwd*.
+        p = subprocess.Popen([python_arg, "-c",
+                              "import os, sys; "
+                              "sys.stdout.write(os.getcwd()); "
+                              "sys.exit(47)"],
+                              stdout=subprocess.PIPE,
+                              **kwargs)
+        self.addCleanup(p.stdout.close)
+        p.wait()
+        self.assertEqual(47, p.returncode)
+        normcase = os.path.normcase
+        self.assertEqual(normcase(expected_cwd),
+                         normcase(p.stdout.read().decode("utf-8")))
+
+    def test_cwd(self):
+        # Check that cwd changes the cwd for the child process.
+        temp_dir = tempfile.gettempdir()
+        temp_dir = self._normalize_cwd(temp_dir)
+        self._assert_cwd(temp_dir, sys.executable, cwd=temp_dir)
+
+    def test_cwd_with_relative_arg(self):
+        # Check that Popen looks for args[0] relative to cwd if args[0]
+        # is relative.
+        python_dir, python_base = self._split_python_path()
+        rel_python = os.path.join(os.curdir, python_base)
+        with support.temp_cwd() as wrong_dir:
+            # Before calling with the correct cwd, confirm that the call fails
+            # without cwd and with the wrong cwd.
+            self.assertRaises(FileNotFoundError, subprocess.Popen,
+                              [rel_python])
+            self.assertRaises(FileNotFoundError, subprocess.Popen,
+                              [rel_python], cwd=wrong_dir)
+            python_dir = self._normalize_cwd(python_dir)
+            self._assert_cwd(python_dir, rel_python, cwd=python_dir)
+
+    def test_cwd_with_relative_executable(self):
+        # Check that Popen looks for executable relative to cwd if executable
+        # is relative (and that executable takes precedence over args[0]).
+        python_dir, python_base = self._split_python_path()
+        rel_python = os.path.join(os.curdir, python_base)
+        doesntexist = "somethingyoudonthave"
+        with support.temp_cwd() as wrong_dir:
+            # Before calling with the correct cwd, confirm that the call fails
+            # without cwd and with the wrong cwd.
+            self.assertRaises(FileNotFoundError, subprocess.Popen,
+                              [doesntexist], executable=rel_python)
+            self.assertRaises(FileNotFoundError, subprocess.Popen,
+                              [doesntexist], executable=rel_python,
+                              cwd=wrong_dir)
+            python_dir = self._normalize_cwd(python_dir)
+            self._assert_cwd(python_dir, doesntexist, executable=rel_python,
+                             cwd=python_dir)
+
+    def test_cwd_with_absolute_arg(self):
+        # Check that Popen can find the executable when the cwd is wrong
+        # if args[0] is an absolute path.
+        python_dir, python_base = self._split_python_path()
+        abs_python = os.path.join(python_dir, python_base)
+        rel_python = os.path.join(os.curdir, python_base)
+        with script_helper.temp_dir() as wrong_dir:
+            # Before calling with an absolute path, confirm that using a
+            # relative path fails.
+            self.assertRaises(FileNotFoundError, subprocess.Popen,
+                              [rel_python], cwd=wrong_dir)
+            wrong_dir = self._normalize_cwd(wrong_dir)
+            self._assert_cwd(wrong_dir, abs_python, cwd=wrong_dir)
+
     @unittest.skipIf(sys.base_prefix != sys.prefix,
                      'Test is not venv-compatible')
     def test_executable_with_cwd(self):
-        python_dir = os.path.dirname(os.path.realpath(sys.executable))
-        p = subprocess.Popen(["somethingyoudonthave", "-c",
-                              "import sys; sys.exit(47)"],
-                             executable=sys.executable, cwd=python_dir)
-        p.wait()
-        self.assertEqual(p.returncode, 47)
+        python_dir, python_base = self._split_python_path()
+        python_dir = self._normalize_cwd(python_dir)
+        self._assert_cwd(python_dir, "somethingyoudonthave",
+                         executable=sys.executable, cwd=python_dir)
 
     @unittest.skipIf(sys.base_prefix != sys.prefix,
                      'Test is not venv-compatible')
@@ -208,11 +295,7 @@
     def test_executable_without_cwd(self):
         # For a normal installation, it should work without 'cwd'
         # argument.  For test runs in the build directory, see #7774.
-        p = subprocess.Popen(["somethingyoudonthave", "-c",
-                              "import sys; sys.exit(47)"],
-                             executable=sys.executable)
-        p.wait()
-        self.assertEqual(p.returncode, 47)
+        self._assert_cwd('', "somethingyoudonthave", executable=sys.executable)
 
     def test_stdin_pipe(self):
         # stdin redirection
@@ -369,24 +452,6 @@
         p.wait()
         self.assertEqual(p.stdin, None)
 
-    def test_cwd(self):
-        tmpdir = tempfile.gettempdir()
-        # We cannot use os.path.realpath to canonicalize the path,
-        # since it doesn't expand Tru64 {memb} strings. See bug 1063571.
-        cwd = os.getcwd()
-        os.chdir(tmpdir)
-        tmpdir = os.getcwd()
-        os.chdir(cwd)
-        p = subprocess.Popen([sys.executable, "-c",
-                              'import sys,os;'
-                              'sys.stdout.write(os.getcwd())'],
-                             stdout=subprocess.PIPE,
-                             cwd=tmpdir)
-        self.addCleanup(p.stdout.close)
-        normcase = os.path.normcase
-        self.assertEqual(normcase(p.stdout.read().decode("utf-8")),
-                         normcase(tmpdir))
-
     def test_env(self):
         newenv = os.environ.copy()
         newenv["FRUIT"] = "orange"
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -112,6 +112,9 @@
 Documentation
 -------------
 
+- Issue #15533: Clarify docs and add tests for subprocess.Popen()'s cwd
+  argument.
+
 - Issue #16036: Improve documentation of built-in int()'s signature and
   arguments.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list