diff options
author | Chris Jerdonek <chris.jerdonek@gmail.com> | 2012-09-30 07:10:28 (GMT) |
---|---|---|
committer | Chris Jerdonek <chris.jerdonek@gmail.com> | 2012-09-30 07:10:28 (GMT) |
commit | ec3ea94251307071111304b95b2ffd3b3703e967 (patch) | |
tree | 3c1f6f9cb345e61bbb3628aec4686456ed2abc88 | |
parent | f727082b71900bb78284924ad7879c85c012c9c6 (diff) | |
download | cpython-ec3ea94251307071111304b95b2ffd3b3703e967.zip cpython-ec3ea94251307071111304b95b2ffd3b3703e967.tar.gz cpython-ec3ea94251307071111304b95b2ffd3b3703e967.tar.bz2 |
Issue #15533: Clarify docs and add tests for subprocess.Popen()'s cwd argument.
-rw-r--r-- | Doc/library/subprocess.rst | 8 | ||||
-rw-r--r-- | Lib/test/test_subprocess.py | 123 | ||||
-rw-r--r-- | Misc/NEWS | 3 |
3 files changed, 101 insertions, 33 deletions
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index 9c03e06..e4c1ad4 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -415,10 +415,10 @@ functions. .. 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 index 620cd8e..ea0e3bb 100644 --- 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 @@ -164,24 +165,106 @@ class ProcessTestCase(BaseTestCase): p.wait() self.assertEqual(p.stderr, None) - 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) + # 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(p.returncode, 47) + 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(OSError, subprocess.Popen, + [rel_python]) + self.assertRaises(OSError, 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(OSError, subprocess.Popen, + [doesntexist], executable=rel_python) + self.assertRaises(OSError, 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(OSError, subprocess.Popen, + [rel_python], cwd=wrong_dir) + wrong_dir = self._normalize_cwd(wrong_dir) + self._assert_cwd(wrong_dir, abs_python, cwd=wrong_dir) + + def test_executable_with_cwd(self): + 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(sysconfig.is_python_build(), "need an installed Python. See #7774") 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 @@ -313,24 +396,6 @@ class ProcessTestCase(BaseTestCase): rc = subprocess.call([sys.executable, "-c", cmd], stdout=1) self.assertEqual(rc, 2) - 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" @@ -597,6 +597,9 @@ Build 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. |