summaryrefslogtreecommitdiff
path: root/Lib/test/test_subprocess.py
diff options
context:
space:
mode:
authorSegev Finer <segev208@gmail.com>2017-12-18 11:28:19 +0200
committerVictor Stinner <victor.stinner@gmail.com>2017-12-18 10:28:19 +0100
commitb2a6083eb0384f38839d3f1ed32262a3852026fa (patch)
treed95a4dd911ebc05549fe54dee0b76c67fe5c727a /Lib/test/test_subprocess.py
parent87010e85cb37192d63b1a30e5fabba307ad5a3f5 (diff)
downloadcpython-git-b2a6083eb0384f38839d3f1ed32262a3852026fa.tar.gz
bpo-19764: Implemented support for subprocess.Popen(close_fds=True) on Windows (#1218)
Even though Python marks any handles it opens as non-inheritable there is still a race when using `subprocess.Popen` since creating a process with redirected stdio requires temporarily creating inheritable handles. By implementing support for `subprocess.Popen(close_fds=True)` we fix this race. In order to implement this we use PROC_THREAD_ATTRIBUTE_HANDLE_LIST which is available since Windows Vista. Which allows to pass an explicit list of handles to inherit when creating a process. This commit also adds `STARTUPINFO.lpAttributeList["handle_list"]` which can be used to control PROC_THREAD_ATTRIBUTE_HANDLE_LIST directly.
Diffstat (limited to 'Lib/test/test_subprocess.py')
-rw-r--r--Lib/test/test_subprocess.py66
1 files changed, 61 insertions, 5 deletions
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index fff1b0db10..bd3b9b46f7 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -2743,11 +2743,6 @@ class Win32ProcessTestCase(BaseTestCase):
[sys.executable, "-c",
"import sys; sys.exit(47)"],
preexec_fn=lambda: 1)
- self.assertRaises(ValueError, subprocess.call,
- [sys.executable, "-c",
- "import sys; sys.exit(47)"],
- stdout=subprocess.PIPE,
- close_fds=True)
@support.cpython_only
def test_issue31471(self):
@@ -2765,6 +2760,67 @@ class Win32ProcessTestCase(BaseTestCase):
close_fds=True)
self.assertEqual(rc, 47)
+ def test_close_fds_with_stdio(self):
+ import msvcrt
+
+ fds = os.pipe()
+ self.addCleanup(os.close, fds[0])
+ self.addCleanup(os.close, fds[1])
+
+ handles = []
+ for fd in fds:
+ os.set_inheritable(fd, True)
+ handles.append(msvcrt.get_osfhandle(fd))
+
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, close_fds=False)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 0)
+ int(stdout.strip()) # Check that stdout is an integer
+
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 1)
+ self.assertIn(b"OSError", stderr)
+
+ # The same as the previous call, but with an empty handle_list
+ handle_list = []
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {"handle_list": handle_list}
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ startupinfo=startupinfo, close_fds=True)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 1)
+ self.assertIn(b"OSError", stderr)
+
+ # Check for a warning due to using handle_list and close_fds=False
+ with support.check_warnings((".*overriding close_fds", RuntimeWarning)):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {"handle_list": handles[:]}
+ p = subprocess.Popen([sys.executable, "-c",
+ "import msvcrt; print(msvcrt.open_osfhandle({}, 0))".format(handles[0])],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE,
+ startupinfo=startupinfo, close_fds=False)
+ stdout, stderr = p.communicate()
+ self.assertEqual(p.returncode, 0)
+
+ def test_empty_attribute_list(self):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {}
+ subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ startupinfo=startupinfo)
+
+ def test_empty_handle_list(self):
+ startupinfo = subprocess.STARTUPINFO()
+ startupinfo.lpAttributeList = {"handle_list": []}
+ subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ startupinfo=startupinfo)
+
def test_shell_sequence(self):
# Run command through the shell (sequence)
newenv = os.environ.copy()