diff options
author | Gregory P. Smith <greg@krypto.org> | 2019-10-14 20:40:32 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-10-14 20:40:32 (GMT) |
commit | 4f0587f161786318cdfa22c42459676fa42aacb6 (patch) | |
tree | 809925c8814cd92df0dd485ba6a63a0fb8d8e59a /Lib/test | |
parent | cc06217d9d2b2d9641baabc96be62a65301bbaf9 (diff) | |
download | cpython-4f0587f161786318cdfa22c42459676fa42aacb6.zip cpython-4f0587f161786318cdfa22c42459676fa42aacb6.tar.gz cpython-4f0587f161786318cdfa22c42459676fa42aacb6.tar.bz2 |
[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.
Diffstat (limited to 'Lib/test')
-rw-r--r-- | Lib/test/test_subprocess.py | 90 |
1 files changed, 51 insertions, 39 deletions
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 69ac584..b2afd12 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -45,6 +45,18 @@ NONEXISTING_CMD = ('nonexisting_i_hope',) # 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 @@ class PopenExecuteChildRaises(subprocess.Popen): 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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class ProcessTestCase(BaseTestCase): newenv = os.environ.copy() newenv["FRUIT\0VEGETABLE"] = "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"] = "orange\0VEGETABLE=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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class ProcessTestCase(BaseTestCase): # # 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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class ProcessTestCase(BaseTestCase): # 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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class ProcessTestCase(BaseTestCase): 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 @@ class RunFuncTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): # 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 @@ class POSIXProcessTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): 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 @@ class POSIXProcessTestCase(BaseTestCase): '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 @@ class Win32ProcessTestCase(BaseTestCase): # 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 @@ class Win32ProcessTestCase(BaseTestCase): # 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 @@ class Win32ProcessTestCase(BaseTestCase): # 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 @@ class Win32ProcessTestCase(BaseTestCase): 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 @@ class Win32ProcessTestCase(BaseTestCase): 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 @@ class ContextManagerTests(BaseTestCase): 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__() |