summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorVictor Stinner <victor.stinner@gmail.com>2017-08-21 22:15:23 (GMT)
committerGitHub <noreply@github.com>2017-08-21 22:15:23 (GMT)
commite76cb435563cd14bb085942dfefbb469b8f40aa9 (patch)
treebe2ac66fd2bf2de02a1a2decdcb2ee0d848a508b /Lib
parent12a3e343e1667f91c0095a9a7dc15048cb620e41 (diff)
downloadcpython-e76cb435563cd14bb085942dfefbb469b8f40aa9.zip
cpython-e76cb435563cd14bb085942dfefbb469b8f40aa9.tar.gz
cpython-e76cb435563cd14bb085942dfefbb469b8f40aa9.tar.bz2
[3.6] bpo-30121: Fix debug assert in subprocess on Windows (#1224) (#3173)
* 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 commit 4d3851727fb82195e4995c6064b0b2d6cbc031c4) * 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 commit 9a83f651f31b47b3f6c8b210f7807b26e8c373a5)
Diffstat (limited to 'Lib')
-rw-r--r--Lib/subprocess.py8
-rw-r--r--Lib/test/test_subprocess.py60
2 files changed, 59 insertions, 9 deletions
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index b790cbd..e0a93c5 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -725,7 +725,10 @@ class Popen(object):
to_close.append(self._devnull)
for fd in to_close:
try:
- os.close(fd)
+ if _mswindows and isinstance(fd, Handle):
+ fd.Close()
+ else:
+ os.close(fd)
except OSError:
pass
@@ -1005,6 +1008,9 @@ class Popen(object):
errwrite.Close()
if hasattr(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
self._child_created = True
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 004f1f0..ccdd323 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -49,6 +49,8 @@ if mswindows:
else:
SETBINARY = ''
+NONEXISTING_CMD = ('nonexisting_i_hope',)
+
class BaseTestCase(unittest.TestCase):
def setUp(self):
@@ -1120,10 +1122,11 @@ class ProcessTestCase(BaseTestCase):
p.stdin.write(line) # expect that it flushes the line in text mode
os.close(p.stdin.fileno()) # close it without flushing the buffer
read_line = p.stdout.readline()
- try:
- p.stdin.close()
- except OSError:
- pass
+ with support.SuppressCrashReport():
+ try:
+ p.stdin.close()
+ except OSError:
+ pass
p.stdin = None
self.assertEqual(p.returncode, 0)
self.assertEqual(read_line, expected)
@@ -1148,13 +1151,54 @@ class ProcessTestCase(BaseTestCase):
# 1024 times (each call leaked two fds).
for i in range(1024):
with self.assertRaises(OSError) as c:
- subprocess.Popen(['nonexisting_i_hope'],
+ subprocess.Popen(NONEXISTING_CMD,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# ignore errors that indicate the command was not found
if c.exception.errno not in (errno.ENOENT, errno.EACCES):
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")
def test_double_close_on_error(self):
# Issue #18851
@@ -1167,7 +1211,7 @@ class ProcessTestCase(BaseTestCase):
t.start()
try:
with self.assertRaises(EnvironmentError):
- subprocess.Popen(['nonexisting_i_hope'],
+ subprocess.Popen(NONEXISTING_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -2433,7 +2477,7 @@ class POSIXProcessTestCase(BaseTestCase):
# should trigger the wait() of p
time.sleep(0.2)
with self.assertRaises(OSError) as c:
- with subprocess.Popen(['nonexisting_i_hope'],
+ with subprocess.Popen(NONEXISTING_CMD,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) as proc:
pass
@@ -2863,7 +2907,7 @@ class ContextManagerTests(BaseTestCase):
def test_invalid_args(self):
with self.assertRaises((FileNotFoundError, PermissionError)) as c:
- with subprocess.Popen(['nonexisting_i_hope'],
+ with subprocess.Popen(NONEXISTING_CMD,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) as proc:
pass