aboutsummaryrefslogtreecommitdiff
path: root/third_party/catapult/devil/devil/utils/cmd_helper.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/catapult/devil/devil/utils/cmd_helper.py')
-rw-r--r--third_party/catapult/devil/devil/utils/cmd_helper.py394
1 files changed, 0 insertions, 394 deletions
diff --git a/third_party/catapult/devil/devil/utils/cmd_helper.py b/third_party/catapult/devil/devil/utils/cmd_helper.py
deleted file mode 100644
index 06c105f..0000000
--- a/third_party/catapult/devil/devil/utils/cmd_helper.py
+++ /dev/null
@@ -1,394 +0,0 @@
-# Copyright (c) 2012 The Chromium Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style license that can be
-# found in the LICENSE file.
-
-"""A wrapper for subprocess to make calling shell commands easier."""
-
-import logging
-import os
-import pipes
-import select
-import signal
-import string
-import StringIO
-import subprocess
-import sys
-import time
-
-# fcntl is not available on Windows.
-try:
- import fcntl
-except ImportError:
- fcntl = None
-
-logger = logging.getLogger(__name__)
-
-_SafeShellChars = frozenset(string.ascii_letters + string.digits + '@%_-+=:,./')
-
-
-def SingleQuote(s):
- """Return an shell-escaped version of the string using single quotes.
-
- Reliably quote a string which may contain unsafe characters (e.g. space,
- quote, or other special characters such as '$').
-
- The returned value can be used in a shell command line as one token that gets
- to be interpreted literally.
-
- Args:
- s: The string to quote.
-
- Return:
- The string quoted using single quotes.
- """
- return pipes.quote(s)
-
-
-def DoubleQuote(s):
- """Return an shell-escaped version of the string using double quotes.
-
- Reliably quote a string which may contain unsafe characters (e.g. space
- or quote characters), while retaining some shell features such as variable
- interpolation.
-
- The returned value can be used in a shell command line as one token that gets
- to be further interpreted by the shell.
-
- The set of characters that retain their special meaning may depend on the
- shell implementation. This set usually includes: '$', '`', '\', '!', '*',
- and '@'.
-
- Args:
- s: The string to quote.
-
- Return:
- The string quoted using double quotes.
- """
- if not s:
- return '""'
- elif all(c in _SafeShellChars for c in s):
- return s
- else:
- return '"' + s.replace('"', '\\"') + '"'
-
-
-def ShrinkToSnippet(cmd_parts, var_name, var_value):
- """Constructs a shell snippet for a command using a variable to shrink it.
-
- Takes into account all quoting that needs to happen.
-
- Args:
- cmd_parts: A list of command arguments.
- var_name: The variable that holds var_value.
- var_value: The string to replace in cmd_parts with $var_name
-
- Returns:
- A shell snippet that does not include setting the variable.
- """
- def shrink(value):
- parts = (x and SingleQuote(x) for x in value.split(var_value))
- with_substitutions = ('"$%s"' % var_name).join(parts)
- return with_substitutions or "''"
-
- return ' '.join(shrink(part) for part in cmd_parts)
-
-
-def Popen(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
- # preexec_fn isn't supported on windows.
- if sys.platform == 'win32':
- preexec_fn = None
- else:
- preexec_fn = lambda: signal.signal(signal.SIGPIPE, signal.SIG_DFL)
-
- return subprocess.Popen(
- args=args, cwd=cwd, stdout=stdout, stderr=stderr,
- shell=shell, close_fds=True, env=env, preexec_fn=preexec_fn)
-
-
-def Call(args, stdout=None, stderr=None, shell=None, cwd=None, env=None):
- pipe = Popen(args, stdout=stdout, stderr=stderr, shell=shell, cwd=cwd,
- env=env)
- pipe.communicate()
- return pipe.wait()
-
-
-def RunCmd(args, cwd=None):
- """Opens a subprocess to execute a program and returns its return value.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
-
- Returns:
- Return code from the command execution.
- """
- logger.info(str(args) + ' ' + (cwd or ''))
- return Call(args, cwd=cwd)
-
-
-def GetCmdOutput(args, cwd=None, shell=False):
- """Open a subprocess to execute a program and returns its output.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command.
-
- Returns:
- Captures and returns the command's stdout.
- Prints the command's stderr to logger (which defaults to stdout).
- """
- (_, output) = GetCmdStatusAndOutput(args, cwd, shell)
- return output
-
-
-def _ValidateAndLogCommand(args, cwd, shell):
- if isinstance(args, basestring):
- if not shell:
- raise Exception('string args must be run with shell=True')
- else:
- if shell:
- raise Exception('array args must be run with shell=False')
- args = ' '.join(SingleQuote(c) for c in args)
- if cwd is None:
- cwd = ''
- else:
- cwd = ':' + cwd
- logger.info('[host]%s> %s', cwd, args)
- return args
-
-
-def GetCmdStatusAndOutput(args, cwd=None, shell=False):
- """Executes a subprocess and returns its exit code and output.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
-
- Returns:
- The 2-tuple (exit code, output).
- """
- status, stdout, stderr = GetCmdStatusOutputAndError(
- args, cwd=cwd, shell=shell)
-
- if stderr:
- logger.critical('STDERR: %s', stderr)
- logger.debug('STDOUT: %s%s', stdout[:4096].rstrip(),
- '<truncated>' if len(stdout) > 4096 else '')
- return (status, stdout)
-
-
-def GetCmdStatusOutputAndError(args, cwd=None, shell=False):
- """Executes a subprocess and returns its exit code, output, and errors.
-
- Args:
- args: A string or a sequence of program arguments. The program to execute is
- the string or the first item in the args sequence.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
-
- Returns:
- The 2-tuple (exit code, output).
- """
- _ValidateAndLogCommand(args, cwd, shell)
- pipe = Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
- shell=shell, cwd=cwd)
- stdout, stderr = pipe.communicate()
- return (pipe.returncode, stdout, stderr)
-
-
-class TimeoutError(Exception):
- """Module-specific timeout exception."""
-
- def __init__(self, output=None):
- super(TimeoutError, self).__init__()
- self._output = output
-
- @property
- def output(self):
- return self._output
-
-
-def _IterProcessStdout(process, iter_timeout=None, timeout=None,
- buffer_size=4096, poll_interval=1):
- """Iterate over a process's stdout.
-
- This is intentionally not public.
-
- Args:
- process: The process in question.
- iter_timeout: An optional length of time, in seconds, to wait in
- between each iteration. If no output is received in the given
- time, this generator will yield None.
- timeout: An optional length of time, in seconds, during which
- the process must finish. If it fails to do so, a TimeoutError
- will be raised.
- buffer_size: The maximum number of bytes to read (and thus yield) at once.
- poll_interval: The length of time to wait in calls to `select.select`.
- If iter_timeout is set, the remaining length of time in the iteration
- may take precedence.
- Raises:
- TimeoutError: if timeout is set and the process does not complete.
- Yields:
- basestrings of data or None.
- """
-
- assert fcntl, 'fcntl module is required'
- try:
- # Enable non-blocking reads from the child's stdout.
- child_fd = process.stdout.fileno()
- fl = fcntl.fcntl(child_fd, fcntl.F_GETFL)
- fcntl.fcntl(child_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
-
- end_time = (time.time() + timeout) if timeout else None
- iter_end_time = (time.time() + iter_timeout) if iter_timeout else None
-
- while True:
- if end_time and time.time() > end_time:
- raise TimeoutError()
- if iter_end_time and time.time() > iter_end_time:
- yield None
- iter_end_time = time.time() + iter_timeout
-
- if iter_end_time:
- iter_aware_poll_interval = min(
- poll_interval,
- max(0, iter_end_time - time.time()))
- else:
- iter_aware_poll_interval = poll_interval
-
- read_fds, _, _ = select.select(
- [child_fd], [], [], iter_aware_poll_interval)
- if child_fd in read_fds:
- data = os.read(child_fd, buffer_size)
- if not data:
- break
- yield data
- if process.poll() is not None:
- break
- finally:
- try:
- if process.returncode is None:
- # Make sure the process doesn't stick around if we fail with an
- # exception.
- process.kill()
- except OSError:
- pass
- process.wait()
-
-
-def GetCmdStatusAndOutputWithTimeout(args, timeout, cwd=None, shell=False,
- logfile=None):
- """Executes a subprocess with a timeout.
-
- Args:
- args: List of arguments to the program, the program to execute is the first
- element.
- timeout: the timeout in seconds or None to wait forever.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
- logfile: Optional file-like object that will receive output from the
- command as it is running.
-
- Returns:
- The 2-tuple (exit code, output).
- Raises:
- TimeoutError on timeout.
- """
- _ValidateAndLogCommand(args, cwd, shell)
- output = StringIO.StringIO()
- process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- try:
- for data in _IterProcessStdout(process, timeout=timeout):
- if logfile:
- logfile.write(data)
- output.write(data)
- except TimeoutError:
- raise TimeoutError(output.getvalue())
-
- str_output = output.getvalue()
- logger.debug('STDOUT+STDERR: %s%s', str_output[:4096].rstrip(),
- '<truncated>' if len(str_output) > 4096 else '')
- return process.returncode, str_output
-
-
-def IterCmdOutputLines(args, iter_timeout=None, timeout=None, cwd=None,
- shell=False, check_status=True):
- """Executes a subprocess and continuously yields lines from its output.
-
- Args:
- args: List of arguments to the program, the program to execute is the first
- element.
- iter_timeout: Timeout for each iteration, in seconds.
- timeout: Timeout for the entire command, in seconds.
- cwd: If not None, the subprocess's current directory will be changed to
- |cwd| before it's executed.
- shell: Whether to execute args as a shell command. Must be True if args
- is a string and False if args is a sequence.
- check_status: A boolean indicating whether to check the exit status of the
- process after all output has been read.
- Yields:
- The output of the subprocess, line by line.
-
- Raises:
- CalledProcessError if check_status is True and the process exited with a
- non-zero exit status.
- """
- cmd = _ValidateAndLogCommand(args, cwd, shell)
- process = Popen(args, cwd=cwd, shell=shell, stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- return _IterCmdOutputLines(
- process, cmd, iter_timeout=iter_timeout, timeout=timeout,
- check_status=check_status)
-
-def _IterCmdOutputLines(process, cmd, iter_timeout=None, timeout=None,
- check_status=True):
- buffer_output = ''
-
- iter_end = None
- cur_iter_timeout = None
- if iter_timeout:
- iter_end = time.time() + iter_timeout
- cur_iter_timeout = iter_timeout
-
- for data in _IterProcessStdout(process, iter_timeout=cur_iter_timeout,
- timeout=timeout):
- if iter_timeout:
- # Check whether the current iteration has timed out.
- cur_iter_timeout = iter_end - time.time()
- if data is None or cur_iter_timeout < 0:
- yield None
- iter_end = time.time() + iter_timeout
- continue
- else:
- assert data is not None, (
- 'Iteration received no data despite no iter_timeout being set. '
- 'cmd: %s' % cmd)
-
- # Construct lines to yield from raw data.
- buffer_output += data
- has_incomplete_line = buffer_output[-1] not in '\r\n'
- lines = buffer_output.splitlines()
- buffer_output = lines.pop() if has_incomplete_line else ''
- for line in lines:
- yield line
- if iter_timeout:
- iter_end = time.time() + iter_timeout
-
- if buffer_output:
- yield buffer_output
- if check_status and process.returncode:
- raise subprocess.CalledProcessError(process.returncode, cmd)