mirror of
https://github.com/python/cpython.git
synced 2025-09-20 15:40:32 +00:00
Fixes Issue #16114: The subprocess module no longer provides a
misleading error message stating that args[0] did not exist when either the cwd or executable keyword arguments specified a path that did not exist.
This commit is contained in:
commit
a10ddb8a55
4 changed files with 59 additions and 9 deletions
|
@ -1327,6 +1327,7 @@ class Popen(object):
|
||||||
|
|
||||||
if executable is None:
|
if executable is None:
|
||||||
executable = args[0]
|
executable = args[0]
|
||||||
|
orig_executable = executable
|
||||||
|
|
||||||
# For transferring possible exec failure from child to parent.
|
# For transferring possible exec failure from child to parent.
|
||||||
# Data format: "exception name:hex errno:description"
|
# Data format: "exception name:hex errno:description"
|
||||||
|
@ -1409,10 +1410,17 @@ class Popen(object):
|
||||||
err_msg = err_msg.decode(errors="surrogatepass")
|
err_msg = err_msg.decode(errors="surrogatepass")
|
||||||
if issubclass(child_exception_type, OSError) and hex_errno:
|
if issubclass(child_exception_type, OSError) and hex_errno:
|
||||||
errno_num = int(hex_errno, 16)
|
errno_num = int(hex_errno, 16)
|
||||||
|
child_exec_never_called = (err_msg == "noexec")
|
||||||
|
if child_exec_never_called:
|
||||||
|
err_msg = ""
|
||||||
if errno_num != 0:
|
if errno_num != 0:
|
||||||
err_msg = os.strerror(errno_num)
|
err_msg = os.strerror(errno_num)
|
||||||
if errno_num == errno.ENOENT:
|
if errno_num == errno.ENOENT:
|
||||||
err_msg += ': ' + repr(args[0])
|
if child_exec_never_called:
|
||||||
|
# The error must be from chdir(cwd).
|
||||||
|
err_msg += ': ' + repr(cwd)
|
||||||
|
else:
|
||||||
|
err_msg += ': ' + repr(orig_executable)
|
||||||
raise child_exception_type(errno_num, err_msg)
|
raise child_exception_type(errno_num, err_msg)
|
||||||
raise child_exception_type(err_msg)
|
raise child_exception_type(err_msg)
|
||||||
|
|
||||||
|
|
|
@ -1038,24 +1038,30 @@ class _SuppressCoreFiles(object):
|
||||||
@unittest.skipIf(mswindows, "POSIX specific tests")
|
@unittest.skipIf(mswindows, "POSIX specific tests")
|
||||||
class POSIXProcessTestCase(BaseTestCase):
|
class POSIXProcessTestCase(BaseTestCase):
|
||||||
|
|
||||||
def test_exceptions(self):
|
def setUp(self):
|
||||||
nonexistent_dir = "/_this/pa.th/does/not/exist"
|
super().setUp()
|
||||||
|
self._nonexistent_dir = "/_this/pa.th/does/not/exist"
|
||||||
|
|
||||||
|
def _get_chdir_exception(self):
|
||||||
try:
|
try:
|
||||||
os.chdir(nonexistent_dir)
|
os.chdir(self._nonexistent_dir)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# This avoids hard coding the errno value or the OS perror()
|
# This avoids hard coding the errno value or the OS perror()
|
||||||
# string and instead capture the exception that we want to see
|
# string and instead capture the exception that we want to see
|
||||||
# below for comparison.
|
# below for comparison.
|
||||||
desired_exception = e
|
desired_exception = e
|
||||||
desired_exception.strerror += ': ' + repr(sys.executable)
|
desired_exception.strerror += ': ' + repr(self._nonexistent_dir)
|
||||||
else:
|
else:
|
||||||
self.fail("chdir to nonexistant directory %s succeeded." %
|
self.fail("chdir to nonexistant directory %s succeeded." %
|
||||||
nonexistent_dir)
|
self._nonexistent_dir)
|
||||||
|
return desired_exception
|
||||||
|
|
||||||
# Error in the child re-raised in the parent.
|
def test_exception_cwd(self):
|
||||||
|
"""Test error in the child raised in the parent for a bad cwd."""
|
||||||
|
desired_exception = self._get_chdir_exception()
|
||||||
try:
|
try:
|
||||||
p = subprocess.Popen([sys.executable, "-c", ""],
|
p = subprocess.Popen([sys.executable, "-c", ""],
|
||||||
cwd=nonexistent_dir)
|
cwd=self._nonexistent_dir)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
# Test that the child process chdir failure actually makes
|
# Test that the child process chdir failure actually makes
|
||||||
# it up to the parent process as the correct exception.
|
# it up to the parent process as the correct exception.
|
||||||
|
@ -1064,6 +1070,33 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||||
else:
|
else:
|
||||||
self.fail("Expected OSError: %s" % desired_exception)
|
self.fail("Expected OSError: %s" % desired_exception)
|
||||||
|
|
||||||
|
def test_exception_bad_executable(self):
|
||||||
|
"""Test error in the child raised in the parent for a bad executable."""
|
||||||
|
desired_exception = self._get_chdir_exception()
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen([sys.executable, "-c", ""],
|
||||||
|
executable=self._nonexistent_dir)
|
||||||
|
except OSError as e:
|
||||||
|
# Test that the child process exec failure actually makes
|
||||||
|
# it up to the parent process as the correct exception.
|
||||||
|
self.assertEqual(desired_exception.errno, e.errno)
|
||||||
|
self.assertEqual(desired_exception.strerror, e.strerror)
|
||||||
|
else:
|
||||||
|
self.fail("Expected OSError: %s" % desired_exception)
|
||||||
|
|
||||||
|
def test_exception_bad_args_0(self):
|
||||||
|
"""Test error in the child raised in the parent for a bad args[0]."""
|
||||||
|
desired_exception = self._get_chdir_exception()
|
||||||
|
try:
|
||||||
|
p = subprocess.Popen([self._nonexistent_dir, "-c", ""])
|
||||||
|
except OSError as e:
|
||||||
|
# Test that the child process exec failure actually makes
|
||||||
|
# it up to the parent process as the correct exception.
|
||||||
|
self.assertEqual(desired_exception.errno, e.errno)
|
||||||
|
self.assertEqual(desired_exception.strerror, e.strerror)
|
||||||
|
else:
|
||||||
|
self.fail("Expected OSError: %s" % desired_exception)
|
||||||
|
|
||||||
def test_restore_signals(self):
|
def test_restore_signals(self):
|
||||||
# Code coverage for both values of restore_signals to make sure it
|
# Code coverage for both values of restore_signals to make sure it
|
||||||
# at least does not blow up.
|
# at least does not blow up.
|
||||||
|
|
|
@ -37,6 +37,10 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #16114: The subprocess module no longer provides a misleading error
|
||||||
|
message stating that args[0] did not exist when either the cwd or executable
|
||||||
|
keyword arguments specified a path that did not exist.
|
||||||
|
|
||||||
- Issue #16169: Fix ctypes.WinError()'s confusion between errno and winerror.
|
- Issue #16169: Fix ctypes.WinError()'s confusion between errno and winerror.
|
||||||
|
|
||||||
- Issue #16089: Allow ElementTree.TreeBuilder to work again with a non-Element
|
- Issue #16089: Allow ElementTree.TreeBuilder to work again with a non-Element
|
||||||
|
|
|
@ -356,7 +356,7 @@ child_exec(char *const exec_array[],
|
||||||
PyObject *preexec_fn,
|
PyObject *preexec_fn,
|
||||||
PyObject *preexec_fn_args_tuple)
|
PyObject *preexec_fn_args_tuple)
|
||||||
{
|
{
|
||||||
int i, saved_errno, unused;
|
int i, saved_errno, unused, reached_preexec = 0;
|
||||||
PyObject *result;
|
PyObject *result;
|
||||||
const char* err_msg = "";
|
const char* err_msg = "";
|
||||||
/* Buffer large enough to hold a hex integer. We can't malloc. */
|
/* Buffer large enough to hold a hex integer. We can't malloc. */
|
||||||
|
@ -440,6 +440,7 @@ child_exec(char *const exec_array[],
|
||||||
POSIX_CALL(setsid());
|
POSIX_CALL(setsid());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
reached_preexec = 1;
|
||||||
if (preexec_fn != Py_None && preexec_fn_args_tuple) {
|
if (preexec_fn != Py_None && preexec_fn_args_tuple) {
|
||||||
/* This is where the user has asked us to deadlock their program. */
|
/* This is where the user has asked us to deadlock their program. */
|
||||||
result = PyObject_Call(preexec_fn, preexec_fn_args_tuple, NULL);
|
result = PyObject_Call(preexec_fn, preexec_fn_args_tuple, NULL);
|
||||||
|
@ -489,6 +490,10 @@ error:
|
||||||
}
|
}
|
||||||
unused = write(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur);
|
unused = write(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur);
|
||||||
unused = write(errpipe_write, ":", 1);
|
unused = write(errpipe_write, ":", 1);
|
||||||
|
if (!reached_preexec) {
|
||||||
|
/* Indicate to the parent that the error happened before exec(). */
|
||||||
|
unused = write(errpipe_write, "noexec", 6);
|
||||||
|
}
|
||||||
/* We can't call strerror(saved_errno). It is not async signal safe.
|
/* We can't call strerror(saved_errno). It is not async signal safe.
|
||||||
* The parent process will look the error message up. */
|
* The parent process will look the error message up. */
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue