mirror of
https://github.com/python/cpython.git
synced 2025-09-29 11:45:57 +00:00
* bpo-30121: Fix debug assert in subprocess on Windows (#1224) * bpo-30121: Fix debug assert in subprocess on Windows This is caused by closing HANDLEs using os.close which is for CRT file descriptors and not for HANDLEs. * bpo-30121: Suppress debug assertion in test_subprocess when ran directly (cherry picked from commit4d3851727f
) * Add test_subprocess.test_nonexisting_with_pipes() (#3133) bpo-30121: Test the Popen failure when Popen was created with pipes. Create also NONEXISTING_CMD variable in test_subprocess.py. (cherry picked from commit9a83f651f3
)
This commit is contained in:
parent
12a3e343e1
commit
e76cb43556
2 changed files with 59 additions and 9 deletions
|
@ -725,7 +725,10 @@ class Popen(object):
|
||||||
to_close.append(self._devnull)
|
to_close.append(self._devnull)
|
||||||
for fd in to_close:
|
for fd in to_close:
|
||||||
try:
|
try:
|
||||||
os.close(fd)
|
if _mswindows and isinstance(fd, Handle):
|
||||||
|
fd.Close()
|
||||||
|
else:
|
||||||
|
os.close(fd)
|
||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1005,6 +1008,9 @@ class Popen(object):
|
||||||
errwrite.Close()
|
errwrite.Close()
|
||||||
if hasattr(self, '_devnull'):
|
if hasattr(self, '_devnull'):
|
||||||
os.close(self._devnull)
|
os.close(self._devnull)
|
||||||
|
# Prevent a double close of these handles/fds from __init__
|
||||||
|
# on error.
|
||||||
|
self._closed_child_pipe_fds = True
|
||||||
|
|
||||||
# Retain the process handle, but close the thread handle
|
# Retain the process handle, but close the thread handle
|
||||||
self._child_created = True
|
self._child_created = True
|
||||||
|
|
|
@ -49,6 +49,8 @@ if mswindows:
|
||||||
else:
|
else:
|
||||||
SETBINARY = ''
|
SETBINARY = ''
|
||||||
|
|
||||||
|
NONEXISTING_CMD = ('nonexisting_i_hope',)
|
||||||
|
|
||||||
|
|
||||||
class BaseTestCase(unittest.TestCase):
|
class BaseTestCase(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -1120,10 +1122,11 @@ class ProcessTestCase(BaseTestCase):
|
||||||
p.stdin.write(line) # expect that it flushes the line in text mode
|
p.stdin.write(line) # expect that it flushes the line in text mode
|
||||||
os.close(p.stdin.fileno()) # close it without flushing the buffer
|
os.close(p.stdin.fileno()) # close it without flushing the buffer
|
||||||
read_line = p.stdout.readline()
|
read_line = p.stdout.readline()
|
||||||
try:
|
with support.SuppressCrashReport():
|
||||||
p.stdin.close()
|
try:
|
||||||
except OSError:
|
p.stdin.close()
|
||||||
pass
|
except OSError:
|
||||||
|
pass
|
||||||
p.stdin = None
|
p.stdin = None
|
||||||
self.assertEqual(p.returncode, 0)
|
self.assertEqual(p.returncode, 0)
|
||||||
self.assertEqual(read_line, expected)
|
self.assertEqual(read_line, expected)
|
||||||
|
@ -1148,13 +1151,54 @@ class ProcessTestCase(BaseTestCase):
|
||||||
# 1024 times (each call leaked two fds).
|
# 1024 times (each call leaked two fds).
|
||||||
for i in range(1024):
|
for i in range(1024):
|
||||||
with self.assertRaises(OSError) as c:
|
with self.assertRaises(OSError) as c:
|
||||||
subprocess.Popen(['nonexisting_i_hope'],
|
subprocess.Popen(NONEXISTING_CMD,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
# ignore errors that indicate the command was not found
|
# ignore errors that indicate the command was not found
|
||||||
if c.exception.errno not in (errno.ENOENT, errno.EACCES):
|
if c.exception.errno not in (errno.ENOENT, errno.EACCES):
|
||||||
raise c.exception
|
raise c.exception
|
||||||
|
|
||||||
|
def test_nonexisting_with_pipes(self):
|
||||||
|
# bpo-30121: Popen with pipes must close properly pipes on error.
|
||||||
|
# Previously, os.close() was called with a Windows handle which is not
|
||||||
|
# a valid file descriptor.
|
||||||
|
#
|
||||||
|
# Run the test in a subprocess to control how the CRT reports errors
|
||||||
|
# and to get stderr content.
|
||||||
|
try:
|
||||||
|
import msvcrt
|
||||||
|
msvcrt.CrtSetReportMode
|
||||||
|
except (AttributeError, ImportError):
|
||||||
|
self.skipTest("need msvcrt.CrtSetReportMode")
|
||||||
|
|
||||||
|
code = textwrap.dedent(f"""
|
||||||
|
import msvcrt
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
cmd = {NONEXISTING_CMD!r}
|
||||||
|
|
||||||
|
for report_type in [msvcrt.CRT_WARN,
|
||||||
|
msvcrt.CRT_ERROR,
|
||||||
|
msvcrt.CRT_ASSERT]:
|
||||||
|
msvcrt.CrtSetReportMode(report_type, msvcrt.CRTDBG_MODE_FILE)
|
||||||
|
msvcrt.CrtSetReportFile(report_type, msvcrt.CRTDBG_FILE_STDERR)
|
||||||
|
|
||||||
|
try:
|
||||||
|
subprocess.Popen([cmd],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
cmd = [sys.executable, "-c", code]
|
||||||
|
proc = subprocess.Popen(cmd,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
universal_newlines=True)
|
||||||
|
with proc:
|
||||||
|
stderr = proc.communicate()[1]
|
||||||
|
self.assertEqual(stderr, "")
|
||||||
|
self.assertEqual(proc.returncode, 0)
|
||||||
|
|
||||||
@unittest.skipIf(threading is None, "threading required")
|
@unittest.skipIf(threading is None, "threading required")
|
||||||
def test_double_close_on_error(self):
|
def test_double_close_on_error(self):
|
||||||
# Issue #18851
|
# Issue #18851
|
||||||
|
@ -1167,7 +1211,7 @@ class ProcessTestCase(BaseTestCase):
|
||||||
t.start()
|
t.start()
|
||||||
try:
|
try:
|
||||||
with self.assertRaises(EnvironmentError):
|
with self.assertRaises(EnvironmentError):
|
||||||
subprocess.Popen(['nonexisting_i_hope'],
|
subprocess.Popen(NONEXISTING_CMD,
|
||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
|
@ -2433,7 +2477,7 @@ class POSIXProcessTestCase(BaseTestCase):
|
||||||
# should trigger the wait() of p
|
# should trigger the wait() of p
|
||||||
time.sleep(0.2)
|
time.sleep(0.2)
|
||||||
with self.assertRaises(OSError) as c:
|
with self.assertRaises(OSError) as c:
|
||||||
with subprocess.Popen(['nonexisting_i_hope'],
|
with subprocess.Popen(NONEXISTING_CMD,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE) as proc:
|
stderr=subprocess.PIPE) as proc:
|
||||||
pass
|
pass
|
||||||
|
@ -2863,7 +2907,7 @@ class ContextManagerTests(BaseTestCase):
|
||||||
|
|
||||||
def test_invalid_args(self):
|
def test_invalid_args(self):
|
||||||
with self.assertRaises((FileNotFoundError, PermissionError)) as c:
|
with self.assertRaises((FileNotFoundError, PermissionError)) as c:
|
||||||
with subprocess.Popen(['nonexisting_i_hope'],
|
with subprocess.Popen(NONEXISTING_CMD,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE) as proc:
|
stderr=subprocess.PIPE) as proc:
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue