Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(769)

Unified Diff: py/utils/shell_utils.py

Issue 341193004: Add lots of utils, PRESUBMIT.py (Closed) Base URL: https://skia.googlesource.com/common.git@master
Patch Set: Address comments Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « py/utils/misc.py ('k') | py/utils/ssh_utils.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: py/utils/shell_utils.py
diff --git a/py/utils/shell_utils.py b/py/utils/shell_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6b54ef9754c41448506d81de80fc036a2511566
--- /dev/null
+++ b/py/utils/shell_utils.py
@@ -0,0 +1,236 @@
+#!/usr/bin/env python
+# 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.
+
+""" This module contains tools for running commands in a shell. """
+
+import datetime
+import os
+import Queue
+import select
+import subprocess
+import sys
+import threading
+import time
+
+if 'nt' in os.name:
+ import ctypes
+
+
+DEFAULT_SECS_BETWEEN_ATTEMPTS = 10
+POLL_MILLIS = 250
+
+
+class CommandFailedException(Exception):
+ """Exception which gets raised when a command fails."""
+
+ def __init__(self, output, *args):
+ """Initialize the CommandFailedException.
+
+ Args:
+ output: string; output from the command.
+ """
+ Exception.__init__(self, *args)
+ self._output = output
+
+ @property
+ def output(self):
+ """Output from the command."""
+ return self._output
+
+
+class TimeoutException(CommandFailedException):
+ """CommandFailedException which gets raised when a subprocess exceeds its
+ timeout. """
+ pass
+
+
+def run_async(cmd, echo=True, shell=False):
+ """ Run 'cmd' in a subprocess, returning a Popen class instance referring to
+ that process. (Non-blocking) """
+ if echo:
+ print cmd
+ if 'nt' in os.name:
+ # Windows has a bad habit of opening a dialog when a console program
+ # crashes, rather than just letting it crash. Therefore, when a program
+ # crashes on Windows, we don't find out until the build step times out.
+ # This code prevents the dialog from appearing, so that we find out
+ # immediately and don't waste time waiting around.
+ SEM_NOGPFAULTERRORBOX = 0x0002
+ ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
+ flags = 0x8000000 # CREATE_NO_WINDOW
+ else:
+ flags = 0
+ return subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT,
+ stdout=subprocess.PIPE, creationflags=flags,
+ bufsize=1)
+
+
+class EnqueueThread(threading.Thread):
+ """ Reads and enqueues lines from a file. """
+ def __init__(self, file_obj, queue):
+ threading.Thread.__init__(self)
+ self._file = file_obj
+ self._queue = queue
+ self._stopped = False
+
+ def run(self):
+ if sys.platform.startswith('linux'):
+ # Use a polling object to avoid the blocking call to readline().
+ poll = select.poll()
+ poll.register(self._file, select.POLLIN)
+ while not self._stopped:
+ has_output = poll.poll(POLL_MILLIS)
+ if has_output:
+ line = self._file.readline()
+ if line == '':
+ self._stopped = True
+ self._queue.put(line)
+ else:
+ # Only Unix supports polling objects, so just read from the file,
+ # Python-style.
+ for line in iter(self._file.readline, ''):
+ self._queue.put(line)
+ if self._stopped:
+ break
+
+ def stop(self):
+ self._stopped = True
+
+
+def log_process_in_real_time(proc, echo=True, timeout=None, log_file=None,
+ halt_on_output=None, print_timestamps=True):
+ """ Log the output of proc in real time until it completes. Return a tuple
+ containing the exit code of proc and the contents of stdout.
+
+ proc: an instance of Popen referring to a running subprocess.
+ echo: boolean indicating whether to print the output received from proc.stdout
+ timeout: number of seconds allotted for the process to run. Raises a
+ TimeoutException if the run time exceeds the timeout.
+ log_file: an open file for writing output
+ halt_on_output: string; kill the process and return if this string is found
+ in the output stream from the process.
+ print_timestamps: boolean indicating whether a formatted timestamp should be
+ prepended to each line of output.
+ """
+ stdout_queue = Queue.Queue()
+ log_thread = EnqueueThread(proc.stdout, stdout_queue)
+ log_thread.start()
+ try:
+ all_output = []
+ t_0 = time.time()
+ while True:
+ code = proc.poll()
+ try:
+ output = stdout_queue.get_nowait()
+ all_output.append(output)
+ if output and print_timestamps:
+ timestamp = datetime.datetime.now().strftime('%H:%M:%S.%f')
+ output = ''.join(['[%s] %s\n' % (timestamp, line)
+ for line in output.splitlines()])
+ if echo:
+ sys.stdout.write(output)
+ sys.stdout.flush()
+ if log_file:
+ log_file.write(output)
+ log_file.flush()
+ if halt_on_output and halt_on_output in output:
+ proc.terminate()
+ break
+ except Queue.Empty:
+ if code != None: # proc has finished running
+ break
+ time.sleep(0.5)
+ if timeout and time.time() - t_0 > timeout:
+ proc.terminate()
+ raise TimeoutException(
+ ''.join(all_output),
+ 'Subprocess exceeded timeout of %ds' % timeout)
+ finally:
+ log_thread.stop()
+ log_thread.join()
+ return (code, ''.join(all_output))
+
+
+def log_process_after_completion(proc, echo=True, timeout=None, log_file=None):
+ """ Wait for proc to complete and return a tuple containing the exit code of
+ proc and the contents of stdout. Unlike log_process_in_real_time, does not
+ attempt to read stdout from proc in real time.
+
+ proc: an instance of Popen referring to a running subprocess.
+ echo: boolean indicating whether to print the output received from proc.stdout
+ timeout: number of seconds allotted for the process to run. Raises a
+ TimeoutException if the run time exceeds the timeout.
+ log_file: an open file for writing outout
+ """
+ t_0 = time.time()
+ code = None
+ while code is None:
+ if timeout and time.time() - t_0 > timeout:
+ raise TimeoutException(
+ proc.communicate()[0],
+ 'Subprocess exceeded timeout of %ds' % timeout)
+ time.sleep(0.5)
+ code = proc.poll()
+ output = proc.communicate()[0]
+ if echo:
+ print output
+ if log_file:
+ log_file.write(output)
+ log_file.flush()
+ return (code, output)
+
+
+def run(cmd, echo=True, shell=False, timeout=None, print_timestamps=True,
+ log_in_real_time=True):
+ """ Run 'cmd' in a shell and return the combined contents of stdout and
+ stderr (Blocking). Throws an exception if the command exits non-zero.
+
+ cmd: list of strings (or single string, iff shell==True) indicating the
+ command to run
+ echo: boolean indicating whether we should print the command and log output
+ shell: boolean indicating whether we are using advanced shell features. Use
+ only when absolutely necessary, since this allows a lot more freedom which
+ could be exploited by malicious code. See the warning here:
+ http://docs.python.org/library/subprocess.html#popen-constructor
+ timeout: optional, integer indicating the maximum elapsed time in seconds
+ print_timestamps: boolean indicating whether a formatted timestamp should be
+ prepended to each line of output. Unused if echo or log_in_real_time is
+ False.
+ log_in_real_time: boolean indicating whether to read stdout from the
+ subprocess in real time instead of when the process finishes. If echo is
+ False, we never log in real time, even if log_in_real_time is True.
+ """
+ proc = run_async(cmd, echo=echo, shell=shell)
+ # If we're not printing the output, we don't care if the output shows up in
+ # real time, so don't bother.
+ if log_in_real_time and echo:
+ (returncode, output) = log_process_in_real_time(proc, echo=echo,
+ timeout=timeout, print_timestamps=print_timestamps)
+ else:
+ (returncode, output) = log_process_after_completion(proc, echo=echo,
+ timeout=timeout)
+ if returncode != 0:
+ raise CommandFailedException(
+ output,
+ 'Command failed with code %d: %s' % (returncode, cmd))
+ return output
+
+
+def run_retry(cmd, echo=True, shell=False, attempts=1,
+ secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS,
+ timeout=None, print_timestamps=True):
+ """ Wrapper for run() which makes multiple attempts until either the command
+ succeeds or the maximum number of attempts is reached. """
+ attempt = 1
+ while True:
+ try:
+ return run(cmd, echo=echo, shell=shell, timeout=timeout,
+ print_timestamps=print_timestamps)
+ except CommandFailedException:
+ if attempt >= attempts:
+ raise
+ print 'Command failed. Retrying in %d seconds...' % secs_between_attempts
+ time.sleep(secs_between_attempts)
+ attempt += 1
« no previous file with comments | « py/utils/misc.py ('k') | py/utils/ssh_utils.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698