[Python-checkins] [3.8] bpo-38456: Use /bin/true in test_subprocess (GH-16737)

Gregory P. Smith webhook-mailer at python.org
Mon Oct 14 16:40:37 EDT 2019


https://github.com/python/cpython/commit/4f0587f161786318cdfa22c42459676fa42aacb6
commit: 4f0587f161786318cdfa22c42459676fa42aacb6
branch: 3.8
author: Gregory P. Smith <greg at krypto.org>
committer: GitHub <noreply at github.com>
date: 2019年10月14日T13:40:32-07:00
summary:
[3.8] bpo-38456: Use /bin/true in test_subprocess (GH-16737)
* [3.8] bpo-38456: Use /bin/true in test_subprocess (GH-16736)
Instead of sys.executable, "-c", "pass" or "import sys; sys.exit(0)"
use /bin/true when it is available. On a reasonable machine this
shaves up to two seconds wall time off the otherwise ~40sec execution
on a --with-pydebug build. It should be more notable on many
buildbots or overloaded slower I/O systems (CI, etc)..
(cherry picked from commit 67b93f80c764bca01c81c989d74a99df208bea4d)
* Handle when there is no 'true' command
backport of 46113e0cf32748f66cf64cd633984d143b433cd1 by Pablo Galindo.
files:
M Lib/test/test_subprocess.py
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 69ac584667d3e..b2afd127dfed8 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -45,6 +45,18 @@
 # Ignore errors that indicate the command was not found
 NONEXISTING_ERRORS = (FileNotFoundError, NotADirectoryError, PermissionError)
 
+ZERO_RETURN_CMD = (sys.executable, '-c', 'pass')
+
+
+def setUpModule():
+ shell_true = shutil.which('true')
+ if shell_true is None:
+ return
+ if (os.access(shell_true, os.X_OK) and
+ subprocess.run([shell_true]).returncode == 0):
+ global ZERO_RETURN_CMD
+ ZERO_RETURN_CMD = (shell_true,) # Faster than Python startup.
+
 
 class BaseTestCase(unittest.TestCase):
 def setUp(self):
@@ -89,7 +101,7 @@ def _execute_child(self, *args, **kwargs):
 class ProcessTestCase(BaseTestCase):
 
 def test_io_buffered_by_default(self):
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
 stderr=subprocess.PIPE)
 try:
@@ -103,7 +115,7 @@ def test_io_buffered_by_default(self):
 p.wait()
 
 def test_io_unbuffered_works(self):
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
 stderr=subprocess.PIPE, bufsize=0)
 try:
@@ -133,8 +145,7 @@ def test_call_timeout(self):
 
 def test_check_call_zero(self):
 # check_call() function with zero return code
- rc = subprocess.check_call([sys.executable, "-c",
- "import sys; sys.exit(0)"])
+ rc = subprocess.check_call(ZERO_RETURN_CMD)
 self.assertEqual(rc, 0)
 
 def test_check_call_nonzero(self):
@@ -700,19 +711,19 @@ def test_invalid_env(self):
 newenv = os.environ.copy()
 newenv["FRUIT0円VEGETABLE"] = "cabbage"
 with self.assertRaises(ValueError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
+ subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
 
 # null character in the environment variable value
 newenv = os.environ.copy()
 newenv["FRUIT"] = "orange0円VEGETABLE=cabbage"
 with self.assertRaises(ValueError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
+ subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
 
 # equal character in the environment variable name
 newenv = os.environ.copy()
 newenv["FRUIT=ORANGE"] = "lemon"
 with self.assertRaises(ValueError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
+ subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
 
 # equal character in the environment variable value
 newenv = os.environ.copy()
@@ -813,7 +824,7 @@ def test_communicate_pipe_fd_leak(self):
 options['stderr'] = subprocess.PIPE
 if not options:
 continue
- p = subprocess.Popen((sys.executable, "-c", "pass"), **options)
+ p = subprocess.Popen(ZERO_RETURN_CMD, **options)
 p.communicate()
 if p.stdin is not None:
 self.assertTrue(p.stdin.closed)
@@ -952,7 +963,7 @@ def test_universal_newlines_communicate_input_none(self):
 #
 # We set stdout to PIPE because, as of this writing, a different
 # code path is tested when the number of pipes is zero or one.
- p = subprocess.Popen([sys.executable, "-c", "pass"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdin=subprocess.PIPE,
 stdout=subprocess.PIPE,
 universal_newlines=True)
@@ -1100,7 +1111,7 @@ def test_poll(self):
 self.assertEqual(p.poll(), 0)
 
 def test_wait(self):
- p = subprocess.Popen([sys.executable, "-c", "pass"])
+ p = subprocess.Popen(ZERO_RETURN_CMD)
 self.assertEqual(p.wait(), 0)
 # Subsequent invocations should just return the returncode
 self.assertEqual(p.wait(), 0)
@@ -1119,14 +1130,14 @@ def test_invalid_bufsize(self):
 # an invalid type of the bufsize argument should raise
 # TypeError.
 with self.assertRaises(TypeError):
- subprocess.Popen([sys.executable, "-c", "pass"], "orange")
+ subprocess.Popen(ZERO_RETURN_CMD, "orange")
 
 def test_bufsize_is_none(self):
 # bufsize=None should be the same as bufsize=0.
- p = subprocess.Popen([sys.executable, "-c", "pass"], None)
+ p = subprocess.Popen(ZERO_RETURN_CMD, None)
 self.assertEqual(p.wait(), 0)
 # Again with keyword arg
- p = subprocess.Popen([sys.executable, "-c", "pass"], bufsize=None)
+ p = subprocess.Popen(ZERO_RETURN_CMD, bufsize=None)
 self.assertEqual(p.wait(), 0)
 
 def _test_bufsize_equal_one(self, line, expected, universal_newlines):
@@ -1331,7 +1342,7 @@ def test_handles_closed_on_exception(self):
 
 def test_communicate_epipe(self):
 # Issue 10963: communicate() should hide EPIPE
- p = subprocess.Popen([sys.executable, "-c", 'pass'],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdin=subprocess.PIPE,
 stdout=subprocess.PIPE,
 stderr=subprocess.PIPE)
@@ -1342,7 +1353,7 @@ def test_communicate_epipe(self):
 
 def test_communicate_epipe_only_stdin(self):
 # Issue 10963: communicate() should hide EPIPE
- p = subprocess.Popen([sys.executable, "-c", 'pass'],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdin=subprocess.PIPE)
 self.addCleanup(p.stdin.close)
 p.wait()
@@ -1381,7 +1392,7 @@ def test_failed_child_execute_fd_leak(self):
 fds_before_popen = os.listdir(fd_directory)
 with self.assertRaises(PopenTestException):
 PopenExecuteChildRaises(
- [sys.executable, '-c', 'pass'], stdin=subprocess.PIPE,
+ ZERO_RETURN_CMD, stdin=subprocess.PIPE,
 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
 # NOTE: This test doesn't verify that the real _execute_child
@@ -1424,7 +1435,7 @@ def test_check(self):
 
 def test_check_zero(self):
 # check_returncode shouldn't raise when returncode is zero
- cp = self.run_python("import sys; sys.exit(0)", check=True)
+ cp = subprocess.run(ZERO_RETURN_CMD, check=True)
 self.assertEqual(cp.returncode, 0)
 
 def test_timeout(self):
@@ -1824,7 +1835,7 @@ def raise_it():
 
 with self.assertRaises(subprocess.SubprocessError):
 self._TestExecuteChildPopen(
- self, [sys.executable, "-c", "pass"],
+ self, ZERO_RETURN_CMD,
 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
 stderr=subprocess.PIPE, preexec_fn=raise_it)
 
@@ -2281,7 +2292,7 @@ def prepare():
 
 try:
 subprocess.call(
- [sys.executable, "-c", "pass"],
+ ZERO_RETURN_CMD,
 preexec_fn=prepare)
 except ValueError as err:
 # Pure Python implementations keeps the message
@@ -2324,29 +2335,30 @@ def test_undecodable_env(self):
 self.assertEqual(stdout.decode('ascii'), ascii(encoded_value))
 
 def test_bytes_program(self):
- abs_program = os.fsencode(sys.executable)
- path, program = os.path.split(sys.executable)
+ abs_program = os.fsencode(ZERO_RETURN_CMD[0])
+ args = list(ZERO_RETURN_CMD[1:])
+ path, program = os.path.split(ZERO_RETURN_CMD[0])
 program = os.fsencode(program)
 
 # absolute bytes path
- exitcode = subprocess.call([abs_program, "-c", "pass"])
+ exitcode = subprocess.call([abs_program]+args)
 self.assertEqual(exitcode, 0)
 
 # absolute bytes path as a string
- cmd = b"'" + abs_program + b"' -c pass"
+ cmd = b"'%s' %s" % (abs_program, " ".join(args).encode("utf-8"))
 exitcode = subprocess.call(cmd, shell=True)
 self.assertEqual(exitcode, 0)
 
 # bytes program, unicode PATH
 env = os.environ.copy()
 env["PATH"] = path
- exitcode = subprocess.call([program, "-c", "pass"], env=env)
+ exitcode = subprocess.call([program]+args, env=env)
 self.assertEqual(exitcode, 0)
 
 # bytes program, bytes PATH
 envb = os.environb.copy()
 envb[b"PATH"] = os.fsencode(path)
- exitcode = subprocess.call([program, "-c", "pass"], env=envb)
+ exitcode = subprocess.call([program]+args, env=envb)
 self.assertEqual(exitcode, 0)
 
 def test_pipe_cloexec(self):
@@ -2574,7 +2586,7 @@ def test_pass_fds(self):
 # pass_fds overrides close_fds with a warning.
 with self.assertWarns(RuntimeWarning) as context:
 self.assertFalse(subprocess.call(
- [sys.executable, "-c", "import sys; sys.exit(0)"],
+ ZERO_RETURN_CMD,
 close_fds=False, pass_fds=(fd, )))
 self.assertIn('overriding close_fds', str(context.warning))
 
@@ -2636,19 +2648,19 @@ def test_pass_fds_redirected(self):
 
 def test_stdout_stdin_are_single_inout_fd(self):
 with io.open(os.devnull, "r+") as inout:
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdout=inout, stdin=inout)
 p.wait()
 
 def test_stdout_stderr_are_single_inout_fd(self):
 with io.open(os.devnull, "r+") as inout:
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stdout=inout, stderr=inout)
 p.wait()
 
 def test_stderr_stdin_are_single_inout_fd(self):
 with io.open(os.devnull, "r+") as inout:
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
 stderr=inout, stdin=inout)
 p.wait()
 
@@ -2836,7 +2848,7 @@ def __int__(self):
 def test_communicate_BrokenPipeError_stdin_close(self):
 # By not setting stdout or stderr or a timeout we force the fast path
 # that just calls _stdin_write() internally due to our mock.
- proc = subprocess.Popen([sys.executable, '-c', 'pass'])
+ proc = subprocess.Popen(ZERO_RETURN_CMD)
 with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
 mock_proc_stdin.close.side_effect = BrokenPipeError
 proc.communicate() # Should swallow BrokenPipeError from close.
@@ -2845,7 +2857,7 @@ def test_communicate_BrokenPipeError_stdin_close(self):
 def test_communicate_BrokenPipeError_stdin_write(self):
 # By not setting stdout or stderr or a timeout we force the fast path
 # that just calls _stdin_write() internally due to our mock.
- proc = subprocess.Popen([sys.executable, '-c', 'pass'])
+ proc = subprocess.Popen(ZERO_RETURN_CMD)
 with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
 mock_proc_stdin.write.side_effect = BrokenPipeError
 proc.communicate(b'stuff') # Should swallow the BrokenPipeError.
@@ -2884,7 +2896,7 @@ def test_communicate_BrokenPipeError_stdin_close_with_timeout(self):
 'need _testcapi.W_STOPCODE')
 def test_stopped(self):
 """Test wait() behavior when waitpid returns WIFSTOPPED; issue29335."""
- args = [sys.executable, '-c', 'pass']
+ args = ZERO_RETURN_CMD
 proc = subprocess.Popen(args)
 
 # Wait until the real process completes to avoid zombie process
@@ -2914,7 +2926,7 @@ def test_startupinfo(self):
 # Since Python is a console process, it won't be affected
 # by wShowWindow, but the argument should be silently
 # ignored
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
 startupinfo=startupinfo)
 
 def test_startupinfo_keywords(self):
@@ -2930,7 +2942,7 @@ def test_startupinfo_keywords(self):
 # Since Python is a console process, it won't be affected
 # by wShowWindow, but the argument should be silently
 # ignored
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
 startupinfo=startupinfo)
 
 def test_startupinfo_copy(self):
@@ -2942,7 +2954,7 @@ def test_startupinfo_copy(self):
 # Call Popen() twice with the same startupinfo object to make sure
 # that it's not modified
 for _ in range(2):
- cmd = [sys.executable, "-c", "pass"]
+ cmd = ZERO_RETURN_CMD
 with open(os.devnull, 'w') as null:
 proc = subprocess.Popen(cmd,
 stdout=null,
@@ -2982,7 +2994,7 @@ def test_issue31471(self):
 class BadEnv(dict):
 keys = None
 with self.assertRaises(TypeError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=BadEnv())
+ subprocess.Popen(ZERO_RETURN_CMD, env=BadEnv())
 
 def test_close_fds(self):
 # close file descriptors
@@ -3043,13 +3055,13 @@ def test_close_fds_with_stdio(self):
 def test_empty_attribute_list(self):
 startupinfo = subprocess.STARTUPINFO()
 startupinfo.lpAttributeList = {}
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
 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)"],
+ subprocess.call(ZERO_RETURN_CMD,
 startupinfo=startupinfo)
 
 def test_shell_sequence(self):
@@ -3348,7 +3360,7 @@ def test_invalid_args(self):
 
 def test_broken_pipe_cleanup(self):
 """Broken pipe error should not prevent wait() (Issue 21619)"""
- proc = subprocess.Popen([sys.executable, '-c', 'pass'],
+ proc = subprocess.Popen(ZERO_RETURN_CMD,
 stdin=subprocess.PIPE,
 bufsize=support.PIPE_MAX_SIZE*2)
 proc = proc.__enter__()


More information about the Python-checkins mailing list

AltStyle によって変換されたページ (->オリジナル) /