blob: 785eb190984c923984e92b8547564fcb19b978bd [file] [log] [blame]
Raul Tambrea04028c2019-05-13 17:23:36 +00001# coding=utf-8
maruel@chromium.org4f6852c2012-04-20 20:39:20 +00002# Copyright (c) 2012 The Chromium Authors. All rights reserved.
maruel@chromium.org4860f052011-03-25 20:34:38 +00003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5"""Collection of subprocess wrapper functions.
6
7In theory you shouldn't need anything else in subprocess, or this module failed.
8"""
9
John Budorick9875e182018-12-05 22:57:31 +000010import codecs
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000011import errno
Raul Tambreb946b232019-03-26 14:48:46 +000012import io
maruel@chromium.org4860f052011-03-25 20:34:38 +000013import logging
14import os
Raul Tambreb946b232019-03-26 14:48:46 +000015
16try:
17 import Queue
18except ImportError: # For Py3 compatibility
19 import queue as Queue
20
maruel@chromium.org4860f052011-03-25 20:34:38 +000021import subprocess
22import sys
maruel@chromium.org4860f052011-03-25 20:34:38 +000023import time
24import threading
25
John Budorick9875e182018-12-05 22:57:31 +000026# Cache the string-escape codec to ensure subprocess can find it later.
27# See crbug.com/912292#c2 for context.
Raul Tambreb946b232019-03-26 14:48:46 +000028if sys.version_info.major == 2:
29 codecs.lookup('string-escape')
maruel@chromium.orga8e81632011-12-01 00:35:24 +000030
Aaron Gableac9b0f32019-04-18 17:38:37 +000031# TODO(crbug.com/953884): Remove this when python3 migration is done.
32try:
33 basestring
34except NameError:
35 # pylint: disable=redefined-builtin
36 basestring = str
37
38
maruel@chromium.org4860f052011-03-25 20:34:38 +000039# Constants forwarded from subprocess.
40PIPE = subprocess.PIPE
41STDOUT = subprocess.STDOUT
maruel@chromium.org421982f2011-04-01 17:38:06 +000042# Sends stdout or stderr to os.devnull.
maruel@chromium.org0d5ef242011-04-18 13:52:58 +000043VOID = object()
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000044# Error code when a process was killed because it timed out.
45TIMED_OUT = -2001
maruel@chromium.org4860f052011-03-25 20:34:38 +000046
47# Globals.
48# Set to True if you somehow need to disable this hack.
49SUBPROCESS_CLEANUP_HACKED = False
50
51
52class CalledProcessError(subprocess.CalledProcessError):
53 """Augment the standard exception with more data."""
54 def __init__(self, returncode, cmd, cwd, stdout, stderr):
tandrii@chromium.orgc15fe572014-09-19 11:51:43 +000055 super(CalledProcessError, self).__init__(returncode, cmd, output=stdout)
56 self.stdout = self.output # for backward compatibility.
maruel@chromium.org4860f052011-03-25 20:34:38 +000057 self.stderr = stderr
58 self.cwd = cwd
59
60 def __str__(self):
sbc@chromium.org217330f2015-06-01 22:10:14 +000061 out = 'Command %r returned non-zero exit status %s' % (
maruel@chromium.org4860f052011-03-25 20:34:38 +000062 ' '.join(self.cmd), self.returncode)
63 if self.cwd:
64 out += ' in ' + self.cwd
65 return '\n'.join(filter(None, (out, self.stdout, self.stderr)))
66
67
maruel@chromium.org1d9f6292011-04-07 14:15:36 +000068class CygwinRebaseError(CalledProcessError):
69 """Occurs when cygwin's fork() emulation fails due to rebased dll."""
70
71
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000072## Utility functions
73
74
75def kill_pid(pid):
76 """Kills a process by its process id."""
77 try:
78 # Unable to import 'module'
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -080079 # pylint: disable=no-member,F0401
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000080 import signal
81 return os.kill(pid, signal.SIGKILL)
82 except ImportError:
83 pass
84
85
86def kill_win(process):
87 """Kills a process with its windows handle.
88
89 Has no effect on other platforms.
90 """
91 try:
92 # Unable to import 'module'
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -080093 # pylint: disable=import-error
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000094 import win32process
95 # Access to a protected member _handle of a client class
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -080096 # pylint: disable=protected-access
maruel@chromium.orgfb3d3242011-04-01 14:03:08 +000097 return win32process.TerminateProcess(process._handle, -1)
98 except ImportError:
99 pass
100
101
102def add_kill():
103 """Adds kill() method to subprocess.Popen for python <2.6"""
104 if hasattr(subprocess.Popen, 'kill'):
105 return
106
107 if sys.platform == 'win32':
108 subprocess.Popen.kill = kill_win
109 else:
110 subprocess.Popen.kill = lambda process: kill_pid(process.pid)
111
112
maruel@chromium.org4860f052011-03-25 20:34:38 +0000113def hack_subprocess():
114 """subprocess functions may throw exceptions when used in multiple threads.
115
116 See http://bugs.python.org/issue1731717 for more information.
117 """
118 global SUBPROCESS_CLEANUP_HACKED
119 if not SUBPROCESS_CLEANUP_HACKED and threading.activeCount() != 1:
120 # Only hack if there is ever multiple threads.
121 # There is no point to leak with only one thread.
122 subprocess._cleanup = lambda: None
123 SUBPROCESS_CLEANUP_HACKED = True
124
125
126def get_english_env(env):
127 """Forces LANG and/or LANGUAGE to be English.
128
129 Forces encoding to utf-8 for subprocesses.
130
131 Returns None if it is unnecessary.
132 """
maruel@chromium.orgc98c0c52011-04-06 13:39:43 +0000133 if sys.platform == 'win32':
134 return None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000135 env = env or os.environ
136
137 # Test if it is necessary at all.
138 is_english = lambda name: env.get(name, 'en').startswith('en')
139
140 if is_english('LANG') and is_english('LANGUAGE'):
141 return None
142
143 # Requires modifications.
144 env = env.copy()
145 def fix_lang(name):
146 if not is_english(name):
147 env[name] = 'en_US.UTF-8'
148 fix_lang('LANG')
149 fix_lang('LANGUAGE')
150 return env
151
152
szager@chromium.org12b07e72013-05-03 22:06:34 +0000153class NagTimer(object):
154 """
155 Triggers a callback when a time interval passes without an event being fired.
156
157 For example, the event could be receiving terminal output from a subprocess;
158 and the callback could print a warning to stderr that the subprocess appeared
159 to be hung.
160 """
161 def __init__(self, interval, cb):
162 self.interval = interval
163 self.cb = cb
164 self.timer = threading.Timer(self.interval, self.fn)
165 self.last_output = self.previous_last_output = 0
166
167 def start(self):
168 self.last_output = self.previous_last_output = time.time()
169 self.timer.start()
170
171 def event(self):
172 self.last_output = time.time()
173
174 def fn(self):
175 now = time.time()
176 if self.last_output == self.previous_last_output:
177 self.cb(now - self.previous_last_output)
178 # Use 0.1 fudge factor, just in case
179 # (self.last_output - now) is very close to zero.
180 sleep_time = (self.last_output - now - 0.1) % self.interval
181 self.previous_last_output = self.last_output
182 self.timer = threading.Timer(sleep_time + 0.1, self.fn)
183 self.timer.start()
184
185 def cancel(self):
186 self.timer.cancel()
187
188
maruel@google.comef77f9e2011-11-24 15:24:02 +0000189class Popen(subprocess.Popen):
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000190 """Wraps subprocess.Popen() with various workarounds.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000191
maruel@chromium.org421982f2011-04-01 17:38:06 +0000192 - Forces English output since it's easier to parse the stdout if it is always
193 in English.
194 - Sets shell=True on windows by default. You can override this by forcing
195 shell parameter to a value.
196 - Adds support for VOID to not buffer when not needed.
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000197 - Adds self.start property.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000198
maruel@chromium.org57bf78d2011-09-08 18:57:33 +0000199 Note: Popen() can throw OSError when cwd or args[0] doesn't exist. Translate
200 exceptions generated by cygwin when it fails trying to emulate fork().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000201 """
torne@chromium.org434e7902015-09-15 09:57:01 +0000202 # subprocess.Popen.__init__() is not threadsafe; there is a race between
203 # creating the exec-error pipe for the child and setting it to CLOEXEC during
204 # which another thread can fork and cause the pipe to be inherited by its
205 # descendents, which will cause the current Popen to hang until all those
206 # descendents exit. Protect this with a lock so that only one fork/exec can
207 # happen at a time.
208 popen_lock = threading.Lock()
209
maruel@google.comef77f9e2011-11-24 15:24:02 +0000210 def __init__(self, args, **kwargs):
211 # Make sure we hack subprocess if necessary.
212 hack_subprocess()
213 add_kill()
maruel@chromium.org4860f052011-03-25 20:34:38 +0000214
maruel@google.comef77f9e2011-11-24 15:24:02 +0000215 env = get_english_env(kwargs.get('env'))
216 if env:
217 kwargs['env'] = env
218 if kwargs.get('shell') is None:
219 # *Sigh*: Windows needs shell=True, or else it won't search %PATH% for
220 # the executable, but shell=True makes subprocess on Linux fail when it's
221 # called with a list because it only tries to execute the first item in
222 # the list.
223 kwargs['shell'] = bool(sys.platform=='win32')
maruel@chromium.org4860f052011-03-25 20:34:38 +0000224
Aaron Gableac9b0f32019-04-18 17:38:37 +0000225 if isinstance(args, basestring):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000226 tmp_str = args
227 elif isinstance(args, (list, tuple)):
228 tmp_str = ' '.join(args)
229 else:
230 raise CalledProcessError(None, args, kwargs.get('cwd'), None, None)
231 if kwargs.get('cwd', None):
232 tmp_str += '; cwd=%s' % kwargs['cwd']
233 logging.debug(tmp_str)
maruel@chromium.org421982f2011-04-01 17:38:06 +0000234
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000235 self.stdout_cb = None
236 self.stderr_cb = None
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000237 self.stdin_is_void = False
238 self.stdout_is_void = False
239 self.stderr_is_void = False
szager@chromium.orge0558e62013-05-02 02:48:51 +0000240 self.cmd_str = tmp_str
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000241
242 if kwargs.get('stdin') is VOID:
243 kwargs['stdin'] = open(os.devnull, 'r')
244 self.stdin_is_void = True
245
246 for stream in ('stdout', 'stderr'):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000247 if kwargs.get(stream) in (VOID, os.devnull):
maruel@google.comef77f9e2011-11-24 15:24:02 +0000248 kwargs[stream] = open(os.devnull, 'w')
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000249 setattr(self, stream + '_is_void', True)
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000250 if callable(kwargs.get(stream)):
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000251 setattr(self, stream + '_cb', kwargs[stream])
252 kwargs[stream] = PIPE
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000253
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000254 self.start = time.time()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000255 self.timeout = None
szager@chromium.orge0558e62013-05-02 02:48:51 +0000256 self.nag_timer = None
szager@chromium.org12b07e72013-05-03 22:06:34 +0000257 self.nag_max = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000258 self.shell = kwargs.get('shell', None)
maruel@chromium.org14e37ad2011-11-30 20:26:16 +0000259 # Silence pylint on MacOSX
260 self.returncode = None
maruel@chromium.orga8e81632011-12-01 00:35:24 +0000261
maruel@google.comef77f9e2011-11-24 15:24:02 +0000262 try:
torne@chromium.org434e7902015-09-15 09:57:01 +0000263 with self.popen_lock:
264 super(Popen, self).__init__(args, **kwargs)
Raul Tambreb946b232019-03-26 14:48:46 +0000265 except OSError as e:
maruel@google.comef77f9e2011-11-24 15:24:02 +0000266 if e.errno == errno.EAGAIN and sys.platform == 'cygwin':
267 # Convert fork() emulation failure into a CygwinRebaseError().
268 raise CygwinRebaseError(
269 e.errno,
270 args,
271 kwargs.get('cwd'),
272 None,
273 'Visit '
274 'http://code.google.com/p/chromium/wiki/CygwinDllRemappingFailure '
275 'to learn how to fix this error; you need to rebase your cygwin '
276 'dlls')
luqui@chromium.org7f627a92014-03-28 00:57:44 +0000277 # Popen() can throw OSError when cwd or args[0] doesn't exist.
pgervais@chromium.orgfb653b62014-04-29 17:29:18 +0000278 raise OSError('Execution failed with error: %s.\n'
279 'Check that %s or %s exist and have execution permission.'
280 % (str(e), kwargs.get('cwd'), args[0]))
maruel@chromium.org4860f052011-03-25 20:34:38 +0000281
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800282 def _tee_threads(self, input): # pylint: disable=redefined-builtin
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000283 """Does I/O for a process's pipes using threads.
284
285 It's the simplest and slowest implementation. Expect very slow behavior.
286
287 If there is a callback and it doesn't keep up with the calls, the timeout
288 effectiveness will be delayed accordingly.
289 """
290 # Queue of either of <threadname> when done or (<threadname>, data). In
291 # theory we would like to limit to ~64kb items to not cause large memory
292 # usage when the callback blocks. It is not done because it slows down
293 # processing on OSX10.6 by a factor of 2x, making it even slower than
294 # Windows! Revisit this decision if it becomes a problem, e.g. crash
295 # because of memory exhaustion.
296 queue = Queue.Queue()
297 done = threading.Event()
szager@chromium.org12b07e72013-05-03 22:06:34 +0000298 nag = None
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000299
300 def write_stdin():
301 try:
Raul Tambreb946b232019-03-26 14:48:46 +0000302 stdin_io = io.BytesIO(input)
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000303 while True:
304 data = stdin_io.read(1024)
305 if data:
306 self.stdin.write(data)
307 else:
308 self.stdin.close()
309 break
310 finally:
311 queue.put('stdin')
312
313 def _queue_pipe_read(pipe, name):
314 """Queues characters read from a pipe into a queue."""
315 try:
316 while True:
317 data = pipe.read(1)
318 if not data:
319 break
szager@chromium.org12b07e72013-05-03 22:06:34 +0000320 if nag:
321 nag.event()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000322 queue.put((name, data))
323 finally:
324 queue.put(name)
325
326 def timeout_fn():
327 try:
328 done.wait(self.timeout)
329 finally:
330 queue.put('timeout')
331
332 def wait_fn():
333 try:
334 self.wait()
335 finally:
336 queue.put('wait')
337
338 # Starts up to 5 threads:
339 # Wait for the process to quit
340 # Read stdout
341 # Read stderr
342 # Write stdin
343 # Timeout
344 threads = {
345 'wait': threading.Thread(target=wait_fn),
346 }
347 if self.timeout is not None:
348 threads['timeout'] = threading.Thread(target=timeout_fn)
349 if self.stdout_cb:
350 threads['stdout'] = threading.Thread(
351 target=_queue_pipe_read, args=(self.stdout, 'stdout'))
352 if self.stderr_cb:
353 threads['stderr'] = threading.Thread(
354 target=_queue_pipe_read, args=(self.stderr, 'stderr'))
355 if input:
356 threads['stdin'] = threading.Thread(target=write_stdin)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000357 elif self.stdin:
358 # Pipe but no input, make sure it's closed.
359 self.stdin.close()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000360 for t in threads.itervalues():
361 t.start()
362
szager@chromium.orge0558e62013-05-02 02:48:51 +0000363 if self.nag_timer:
szager@chromium.org12b07e72013-05-03 22:06:34 +0000364 def _nag_cb(elapsed):
365 logging.warn(' No output for %.0f seconds from command:' % elapsed)
366 logging.warn(' %s' % self.cmd_str)
367 if (self.nag_max and
368 int('%.0f' % (elapsed / self.nag_timer)) >= self.nag_max):
369 queue.put('timeout')
370 done.set() # Must do this so that timeout thread stops waiting.
371 nag = NagTimer(self.nag_timer, _nag_cb)
372 nag.start()
szager@chromium.orge0558e62013-05-02 02:48:51 +0000373
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000374 timed_out = False
375 try:
376 # This thread needs to be optimized for speed.
377 while threads:
378 item = queue.get()
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000379 if item[0] == 'stdout':
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000380 self.stdout_cb(item[1])
maruel@chromium.orgcd8d8e12012-10-03 17:16:25 +0000381 elif item[0] == 'stderr':
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000382 self.stderr_cb(item[1])
383 else:
384 # A thread terminated.
szager@chromium.org12b07e72013-05-03 22:06:34 +0000385 if item in threads:
386 threads[item].join()
387 del threads[item]
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000388 if item == 'wait':
389 # Terminate the timeout thread if necessary.
390 done.set()
391 elif item == 'timeout' and not timed_out and self.poll() is None:
szager@chromium.org12b07e72013-05-03 22:06:34 +0000392 logging.debug('Timed out after %.0fs: killing' % (
393 time.time() - self.start))
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000394 self.kill()
395 timed_out = True
396 finally:
397 # Stop the threads.
398 done.set()
szager@chromium.org12b07e72013-05-03 22:06:34 +0000399 if nag:
400 nag.cancel()
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000401 if 'wait' in threads:
402 # Accelerate things, otherwise it would hang until the child process is
403 # done.
404 logging.debug('Killing child because of an exception')
405 self.kill()
406 # Join threads.
407 for thread in threads.itervalues():
408 thread.join()
409 if timed_out:
410 self.returncode = TIMED_OUT
411
Quinten Yearsleyb2cc4a92016-12-15 13:53:26 -0800412 # pylint: disable=arguments-differ,W0622
szager@chromium.org12b07e72013-05-03 22:06:34 +0000413 def communicate(self, input=None, timeout=None, nag_timer=None,
414 nag_max=None):
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000415 """Adds timeout and callbacks support.
416
417 Returns (stdout, stderr) like subprocess.Popen().communicate().
418
419 - The process will be killed after |timeout| seconds and returncode set to
420 TIMED_OUT.
szager@chromium.orge0558e62013-05-02 02:48:51 +0000421 - If the subprocess runs for |nag_timer| seconds without producing terminal
422 output, print a warning to stderr.
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000423 """
424 self.timeout = timeout
szager@chromium.orge0558e62013-05-02 02:48:51 +0000425 self.nag_timer = nag_timer
szager@chromium.org12b07e72013-05-03 22:06:34 +0000426 self.nag_max = nag_max
szager@chromium.orge0558e62013-05-02 02:48:51 +0000427 if (not self.timeout and not self.nag_timer and
428 not self.stdout_cb and not self.stderr_cb):
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000429 return super(Popen, self).communicate(input)
430
431 if self.timeout and self.shell:
432 raise TypeError(
433 'Using timeout and shell simultaneously will cause a process leak '
434 'since the shell will be killed instead of the child process.')
435
436 stdout = None
437 stderr = None
438 # Convert to a lambda to workaround python's deadlock.
439 # http://docs.python.org/library/subprocess.html#subprocess.Popen.wait
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000440 # When the pipe fills up, it would deadlock this process.
441 if self.stdout and not self.stdout_cb and not self.stdout_is_void:
442 stdout = []
443 self.stdout_cb = stdout.append
444 if self.stderr and not self.stderr_cb and not self.stderr_is_void:
445 stderr = []
446 self.stderr_cb = stderr.append
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000447 self._tee_threads(input)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000448 if stdout is not None:
449 stdout = ''.join(stdout)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000450 if stderr is not None:
451 stderr = ''.join(stderr)
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000452 return (stdout, stderr)
453
maruel@chromium.org4860f052011-03-25 20:34:38 +0000454
szager@chromium.org12b07e72013-05-03 22:06:34 +0000455def communicate(args, timeout=None, nag_timer=None, nag_max=None, **kwargs):
maruel@chromium.orgdd9837f2011-11-30 01:55:22 +0000456 """Wraps subprocess.Popen().communicate() and add timeout support.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000457
maruel@chromium.org421982f2011-04-01 17:38:06 +0000458 Returns ((stdout, stderr), returncode).
maruel@chromium.org4860f052011-03-25 20:34:38 +0000459
maruel@chromium.org1d9f6292011-04-07 14:15:36 +0000460 - The process will be killed after |timeout| seconds and returncode set to
461 TIMED_OUT.
szager@chromium.orge0558e62013-05-02 02:48:51 +0000462 - If the subprocess runs for |nag_timer| seconds without producing terminal
463 output, print a warning to stderr.
maruel@chromium.org421982f2011-04-01 17:38:06 +0000464 - Automatically passes stdin content as input so do not specify stdin=PIPE.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000465 """
466 stdin = kwargs.pop('stdin', None)
467 if stdin is not None:
Aaron Gableac9b0f32019-04-18 17:38:37 +0000468 if isinstance(stdin, basestring):
maruel@chromium.org0d5ef242011-04-18 13:52:58 +0000469 # When stdin is passed as an argument, use it as the actual input data and
470 # set the Popen() parameter accordingly.
471 kwargs['stdin'] = PIPE
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000472 else:
473 kwargs['stdin'] = stdin
474 stdin = None
maruel@chromium.org4860f052011-03-25 20:34:38 +0000475
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000476 proc = Popen(args, **kwargs)
maruel@chromium.org740a6c02011-12-05 23:46:44 +0000477 if stdin:
szager@chromium.orge0558e62013-05-02 02:48:51 +0000478 return proc.communicate(stdin, timeout, nag_timer), proc.returncode
maruel@chromium.org94c712f2011-12-01 15:04:57 +0000479 else:
szager@chromium.orge0558e62013-05-02 02:48:51 +0000480 return proc.communicate(None, timeout, nag_timer), proc.returncode
maruel@chromium.org4860f052011-03-25 20:34:38 +0000481
482
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000483def call(args, **kwargs):
484 """Emulates subprocess.call().
485
486 Automatically convert stdout=PIPE or stderr=PIPE to VOID.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000487 In no case they can be returned since no code path raises
488 subprocess2.CalledProcessError.
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000489 """
490 if kwargs.get('stdout') == PIPE:
491 kwargs['stdout'] = VOID
492 if kwargs.get('stderr') == PIPE:
493 kwargs['stderr'] = VOID
494 return communicate(args, **kwargs)[1]
495
496
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000497def check_call_out(args, **kwargs):
maruel@chromium.org421982f2011-04-01 17:38:06 +0000498 """Improved version of subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000499
maruel@chromium.org421982f2011-04-01 17:38:06 +0000500 Returns (stdout, stderr), unlike subprocess.check_call().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000501 """
maruel@chromium.org1f063db2011-04-18 19:04:52 +0000502 out, returncode = communicate(args, **kwargs)
maruel@chromium.org4860f052011-03-25 20:34:38 +0000503 if returncode:
504 raise CalledProcessError(
505 returncode, args, kwargs.get('cwd'), out[0], out[1])
506 return out
507
508
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000509def check_call(args, **kwargs):
510 """Emulate subprocess.check_call()."""
511 check_call_out(args, **kwargs)
512 return 0
513
514
maruel@chromium.org4860f052011-03-25 20:34:38 +0000515def capture(args, **kwargs):
516 """Captures stdout of a process call and returns it.
517
maruel@chromium.org421982f2011-04-01 17:38:06 +0000518 Returns stdout.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000519
maruel@chromium.org421982f2011-04-01 17:38:06 +0000520 - Discards returncode.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000521 - Blocks stdin by default if not specified since no output will be visible.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000522 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000523 kwargs.setdefault('stdin', VOID)
524
525 # Like check_output, deny the caller from using stdout arg.
526 return communicate(args, stdout=PIPE, **kwargs)[0][0]
maruel@chromium.org4860f052011-03-25 20:34:38 +0000527
528
529def check_output(args, **kwargs):
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000530 """Emulates subprocess.check_output().
maruel@chromium.org4860f052011-03-25 20:34:38 +0000531
maruel@chromium.org0bcd1d32011-04-26 15:55:49 +0000532 Captures stdout of a process call and returns stdout only.
maruel@chromium.org4860f052011-03-25 20:34:38 +0000533
maruel@chromium.org421982f2011-04-01 17:38:06 +0000534 - Throws if return code is not 0.
535 - Works even prior to python 2.7.
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000536 - Blocks stdin by default if not specified since no output will be visible.
537 - As per doc, "The stdout argument is not allowed as it is used internally."
maruel@chromium.org4860f052011-03-25 20:34:38 +0000538 """
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000539 kwargs.setdefault('stdin', VOID)
maruel@chromium.orgdb59bfc2011-11-30 14:03:14 +0000540 if 'stdout' in kwargs:
pgervais@chromium.org022d06e2014-04-29 17:08:12 +0000541 raise ValueError('stdout argument not allowed, it would be overridden.')
maruel@chromium.org87e6d332011-09-09 19:01:28 +0000542 return check_call_out(args, stdout=PIPE, **kwargs)[0]