Update subprocess from v3.14.2

This commit is contained in:
CPython Developers authored and Jeong, YunWon committed 2026-02-04 11:16:15 +09:00
1 parent e3b61994cc
commit fd252a4e0c
3 files changed
+37 -72

No files matched your search

+1 -3
View File
@@ -517,15 +517,13 @@ def _flush_std_streams():
def spawnv_passfds(path, args, passfds):
import _posixsubprocess
import subprocess
passfds = tuple(sorted(map(int, passfds)))
errpipe_read, errpipe_write = os.pipe()
try:
return _posixsubprocess.fork_exec(
args, [path], True, passfds, None, None,
-1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
False, False, -1, None, None, None, -1, None,
subprocess._USE_VFORK)
False, False, -1, None, None, None, -1, None)
finally:
os.close(errpipe_read)
os.close(errpipe_write)
+5 -7
View File
@@ -79,7 +79,7 @@ _can_fork_exec = sys.platform not in {"emscripten", "wasi", "ios", "tvos", "watc
if _mswindows:
import _winapi
from _winapi import (CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP,
from _winapi import (CREATE_NEW_CONSOLE, CREATE_NEW_PROCESS_GROUP, # noqa: F401
STD_INPUT_HANDLE, STD_OUTPUT_HANDLE,
STD_ERROR_HANDLE, SW_HIDE,
STARTF_USESTDHANDLES, STARTF_USESHOWWINDOW,
@@ -752,7 +752,6 @@ def _use_posix_spawn():
# These are primarily fail-safe knobs for negatives. A True value does not
# guarantee the given libc/syscall API will be used.
_USE_POSIX_SPAWN = _use_posix_spawn()
_USE_VFORK = True
_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM')
@@ -1125,10 +1124,9 @@ class Popen:
except TimeoutExpired:
pass
self._sigint_wait_secs = 0 # Note that this has been done.
return # resume the KeyboardInterrupt
# Wait for the process to terminate, to avoid zombies.
self.wait()
else:
# Wait for the process to terminate, to avoid zombies.
self.wait()
def __del__(self, _maxsize=sys.maxsize, _warn=warnings.warn):
if not self._child_created:
@@ -1927,7 +1925,7 @@ class Popen:
errpipe_read, errpipe_write,
restore_signals, start_new_session,
process_group, gid, gids, uid, umask,
preexec_fn, _USE_VFORK)
preexec_fn)
self._child_created = True
finally:
# be sure the FD is closed no matter what
+31 -62
View File
@@ -4,6 +4,7 @@ from test import support
from test.support import check_sanitizer
from test.support import import_helper
from test.support import os_helper
from test.support import strace_helper
from test.support import warnings_helper
from test.support.script_helper import assert_python_ok
import subprocess
@@ -1288,7 +1289,7 @@ class ProcessTestCase(BaseTestCase):
self.assertEqual("line1\nline2\nline3\nline4\nline5\n", stdout)
# Python debug build push something like "[42442 refs]\n"
# to stderr at exit of subprocess.
self.assertTrue(stderr.startswith("eline2\neline6\neline7\n"))
self.assertStartsWith(stderr, "eline2\neline6\neline7\n")
def test_universal_newlines_communicate_encodings(self):
# Check that universal newlines mode works for various encodings,
@@ -1620,7 +1621,7 @@ class ProcessTestCase(BaseTestCase):
"[sys.executable, '-c', 'print(\"Hello World!\")'])",
'assert retcode == 0'))
output = subprocess.check_output([sys.executable, '-c', code])
self.assertTrue(output.startswith(b'Hello World!'), ascii(output))
self.assertStartsWith(output, b'Hello World!')
def test_handles_closed_on_exception(self):
# If CreateProcess exits with an error, ensure the
@@ -1784,7 +1785,7 @@ print()
self.assertEqual(
proc.returncode, 0,
msg=f"STDERR:\n{stderr}\nSTDOUT:\n{stdout}")
self.assertTrue(stdout.startswith("spam"), msg=stdout)
self.assertStartsWith(stdout, "spam")
self.assertIn("beans", stdout)
@@ -1980,8 +1981,8 @@ class RunFuncTestCase(BaseTestCase):
capture_output=True)
lines = cp.stderr.splitlines()
self.assertEqual(len(lines), 2, lines)
self.assertTrue(lines[0].startswith(b"<string>:2: EncodingWarning: "))
self.assertTrue(lines[1].startswith(b"<string>:3: EncodingWarning: "))
self.assertStartsWith(lines[0], b"<string>:2: EncodingWarning: ")
self.assertStartsWith(lines[1], b"<string>:3: EncodingWarning: ")
def _get_test_grp_name():
@@ -2314,7 +2315,7 @@ class POSIXProcessTestCase(BaseTestCase):
extra_groups=[name_group])
# No skip necessary, this test won't make it to a setgroup() call.
@unittest.skip('TODO: RUSTPYTHON; clarify failure condition')
@unittest.skip("TODO: RUSTPYTHON; clarify failure condition")
def test_extra_groups_invalid_gid_t_values(self):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, extra_groups=[-1])
@@ -3011,7 +3012,7 @@ class POSIXProcessTestCase(BaseTestCase):
p1.stdout.close()
p2.stdout.close()
@unittest.skip('TODO: RUSTPYTHON; flaky test')
@unittest.skip("TODO: RUSTPYTHON; flaky test")
def test_close_fds(self):
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")
@@ -3143,7 +3144,7 @@ class POSIXProcessTestCase(BaseTestCase):
# descriptor of a pipe closed in the parent process is valid in the
# child process according to fstat(), but the mode of the file
# descriptor is invalid, and read or write raise an error.
@unittest.skip('TODO: RUSTPYTHON; flaky test')
@unittest.skip("TODO: RUSTPYTHON; flaky test")
@support.requires_mac_ver(10, 5)
def test_pass_fds(self):
fd_status = support.findfile("fd_status.py", subdir="subprocessdata")
@@ -3440,7 +3441,7 @@ class POSIXProcessTestCase(BaseTestCase):
1, 2, 3, 4,
True, True, 0,
None, None, None, -1,
None, True)
None)
self.assertIn('fds_to_keep', str(c.exception))
finally:
if not gc_enabled:
@@ -3556,7 +3557,7 @@ class POSIXProcessTestCase(BaseTestCase):
except subprocess.TimeoutExpired:
pass
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: b'preexec_fn not supported at interpreter shutdown' not found in b"Exception ignored in: <function AtFinalization.__del__ at 0xa92f93840>\nAttributeError: 'NoneType' object has no attribute 'Popen'\n"
def test_preexec_at_exit(self):
code = f"""if 1:
import atexit
@@ -3578,26 +3579,7 @@ class POSIXProcessTestCase(BaseTestCase):
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
"vfork() not enabled by configure.")
@mock.patch("subprocess._fork_exec")
@mock.patch("subprocess._USE_POSIX_SPAWN", new=False)
def test__use_vfork(self, mock_fork_exec):
self.assertTrue(subprocess._USE_VFORK) # The default value regardless.
mock_fork_exec.side_effect = RuntimeError("just testing args")
with self.assertRaises(RuntimeError):
subprocess.run([sys.executable, "-c", "pass"])
mock_fork_exec.assert_called_once()
# NOTE: These assertions are *ugly* as they require the last arg
# to remain the have_vfork boolean. We really need to refactor away
# from the giant "wall of args" internal C extension API.
self.assertTrue(mock_fork_exec.call_args.args[-1])
with mock.patch.object(subprocess, '_USE_VFORK', False):
with self.assertRaises(RuntimeError):
subprocess.run([sys.executable, "-c", "pass"])
self.assertFalse(mock_fork_exec.call_args_list[-1].args[-1])
@unittest.skipIf(not sysconfig.get_config_var("HAVE_VFORK"),
"vfork() not enabled by configure.")
@unittest.skipIf(sys.platform != "linux", "Linux only, requires strace.")
@strace_helper.requires_strace()
@mock.patch("subprocess._USE_POSIX_SPAWN", new=False)
def test_vfork_used_when_expected(self):
# This is a performance regression test to ensure we default to using
@@ -3605,52 +3587,39 @@ class POSIXProcessTestCase(BaseTestCase):
# Technically this test could pass when posix_spawn is used as well
# because libc tends to implement that internally using vfork. But
# that'd just be testing a libc+kernel implementation detail.
strace_binary = "/usr/bin/strace"
# The only system calls we are interested in.
strace_filter = "--trace=clone,clone2,clone3,fork,vfork,exit,exit_group"
true_binary = "/bin/true"
strace_command = [strace_binary, strace_filter]
try:
does_strace_work_process = subprocess.run(
strace_command + [true_binary],
stderr=subprocess.PIPE,
stdout=subprocess.DEVNULL,
)
rc = does_strace_work_process.returncode
stderr = does_strace_work_process.stderr
except OSError:
rc = -1
stderr = ""
if rc or (b"+++ exited with 0 +++" not in stderr):
self.skipTest("strace not found or not working as expected.")
# Are intersted in the system calls:
# clone,clone2,clone3,fork,vfork,exit,exit_group
# Unfortunately using `--trace` with that list to strace fails because
# not all are supported on all platforms (ex. clone2 is ia64 only...)
# So instead use `%process` which is recommended by strace, and contains
# the above.
true_binary = "/bin/true"
strace_args = ["--trace=%process"]
with self.subTest(name="default_is_vfork"):
vfork_result = assert_python_ok(
"-c",
textwrap.dedent(f"""\
import subprocess
subprocess.check_call([{true_binary!r}])"""),
__run_using_command=strace_command,
vfork_result = strace_helper.strace_python(
f"""\
import subprocess
subprocess.check_call([{true_binary!r}])""",
strace_args
)
# Match both vfork() and clone(..., flags=...|CLONE_VFORK|...)
self.assertRegex(vfork_result.err, br"(?i)vfork")
self.assertRegex(vfork_result.event_bytes, br"(?i)vfork")
# Do NOT check that fork() or other clones did not happen.
# If the OS denys the vfork it'll fallback to plain fork().
# Test that each individual thing that would disable the use of vfork
# actually disables it.
for sub_name, preamble, sp_kwarg, expect_permission_error in (
("!use_vfork", "subprocess._USE_VFORK = False", "", False),
("preexec", "", "preexec_fn=lambda: None", False),
("setgid", "", f"group={os.getgid()}", True),
("setuid", "", f"user={os.getuid()}", True),
("setgroups", "", "extra_groups=[]", True),
):
with self.subTest(name=sub_name):
non_vfork_result = assert_python_ok(
"-c",
textwrap.dedent(f"""\
non_vfork_result = strace_helper.strace_python(
f"""\
import subprocess
{preamble}
try:
@@ -3658,11 +3627,11 @@ class POSIXProcessTestCase(BaseTestCase):
[{true_binary!r}], **dict({sp_kwarg}))
except PermissionError:
if not {expect_permission_error}:
raise"""),
__run_using_command=strace_command,
raise""",
strace_args
)
# Ensure neither vfork() or clone(..., flags=...|CLONE_VFORK|...).
self.assertNotRegex(non_vfork_result.err, br"(?i)vfork")
self.assertNotRegex(non_vfork_result.event_bytes, br"(?i)vfork")
@unittest.skipUnless(mswindows, "Windows specific tests")