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

Side by Side 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 unified diff | Download patch
« no previous file with comments | « py/utils/misc.py ('k') | py/utils/ssh_utils.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 """ This module contains tools for running commands in a shell. """
7
8 import datetime
9 import os
10 import Queue
11 import select
12 import subprocess
13 import sys
14 import threading
15 import time
16
17 if 'nt' in os.name:
18 import ctypes
19
20
21 DEFAULT_SECS_BETWEEN_ATTEMPTS = 10
22 POLL_MILLIS = 250
23
24
25 class CommandFailedException(Exception):
26 """Exception which gets raised when a command fails."""
27
28 def __init__(self, output, *args):
29 """Initialize the CommandFailedException.
30
31 Args:
32 output: string; output from the command.
33 """
34 Exception.__init__(self, *args)
35 self._output = output
36
37 @property
38 def output(self):
39 """Output from the command."""
40 return self._output
41
42
43 class TimeoutException(CommandFailedException):
44 """CommandFailedException which gets raised when a subprocess exceeds its
45 timeout. """
46 pass
47
48
49 def run_async(cmd, echo=True, shell=False):
50 """ Run 'cmd' in a subprocess, returning a Popen class instance referring to
51 that process. (Non-blocking) """
52 if echo:
53 print cmd
54 if 'nt' in os.name:
55 # Windows has a bad habit of opening a dialog when a console program
56 # crashes, rather than just letting it crash. Therefore, when a program
57 # crashes on Windows, we don't find out until the build step times out.
58 # This code prevents the dialog from appearing, so that we find out
59 # immediately and don't waste time waiting around.
60 SEM_NOGPFAULTERRORBOX = 0x0002
61 ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
62 flags = 0x8000000 # CREATE_NO_WINDOW
63 else:
64 flags = 0
65 return subprocess.Popen(cmd, shell=shell, stderr=subprocess.STDOUT,
66 stdout=subprocess.PIPE, creationflags=flags,
67 bufsize=1)
68
69
70 class EnqueueThread(threading.Thread):
71 """ Reads and enqueues lines from a file. """
72 def __init__(self, file_obj, queue):
73 threading.Thread.__init__(self)
74 self._file = file_obj
75 self._queue = queue
76 self._stopped = False
77
78 def run(self):
79 if sys.platform.startswith('linux'):
80 # Use a polling object to avoid the blocking call to readline().
81 poll = select.poll()
82 poll.register(self._file, select.POLLIN)
83 while not self._stopped:
84 has_output = poll.poll(POLL_MILLIS)
85 if has_output:
86 line = self._file.readline()
87 if line == '':
88 self._stopped = True
89 self._queue.put(line)
90 else:
91 # Only Unix supports polling objects, so just read from the file,
92 # Python-style.
93 for line in iter(self._file.readline, ''):
94 self._queue.put(line)
95 if self._stopped:
96 break
97
98 def stop(self):
99 self._stopped = True
100
101
102 def log_process_in_real_time(proc, echo=True, timeout=None, log_file=None,
103 halt_on_output=None, print_timestamps=True):
104 """ Log the output of proc in real time until it completes. Return a tuple
105 containing the exit code of proc and the contents of stdout.
106
107 proc: an instance of Popen referring to a running subprocess.
108 echo: boolean indicating whether to print the output received from proc.stdout
109 timeout: number of seconds allotted for the process to run. Raises a
110 TimeoutException if the run time exceeds the timeout.
111 log_file: an open file for writing output
112 halt_on_output: string; kill the process and return if this string is found
113 in the output stream from the process.
114 print_timestamps: boolean indicating whether a formatted timestamp should be
115 prepended to each line of output.
116 """
117 stdout_queue = Queue.Queue()
118 log_thread = EnqueueThread(proc.stdout, stdout_queue)
119 log_thread.start()
120 try:
121 all_output = []
122 t_0 = time.time()
123 while True:
124 code = proc.poll()
125 try:
126 output = stdout_queue.get_nowait()
127 all_output.append(output)
128 if output and print_timestamps:
129 timestamp = datetime.datetime.now().strftime('%H:%M:%S.%f')
130 output = ''.join(['[%s] %s\n' % (timestamp, line)
131 for line in output.splitlines()])
132 if echo:
133 sys.stdout.write(output)
134 sys.stdout.flush()
135 if log_file:
136 log_file.write(output)
137 log_file.flush()
138 if halt_on_output and halt_on_output in output:
139 proc.terminate()
140 break
141 except Queue.Empty:
142 if code != None: # proc has finished running
143 break
144 time.sleep(0.5)
145 if timeout and time.time() - t_0 > timeout:
146 proc.terminate()
147 raise TimeoutException(
148 ''.join(all_output),
149 'Subprocess exceeded timeout of %ds' % timeout)
150 finally:
151 log_thread.stop()
152 log_thread.join()
153 return (code, ''.join(all_output))
154
155
156 def log_process_after_completion(proc, echo=True, timeout=None, log_file=None):
157 """ Wait for proc to complete and return a tuple containing the exit code of
158 proc and the contents of stdout. Unlike log_process_in_real_time, does not
159 attempt to read stdout from proc in real time.
160
161 proc: an instance of Popen referring to a running subprocess.
162 echo: boolean indicating whether to print the output received from proc.stdout
163 timeout: number of seconds allotted for the process to run. Raises a
164 TimeoutException if the run time exceeds the timeout.
165 log_file: an open file for writing outout
166 """
167 t_0 = time.time()
168 code = None
169 while code is None:
170 if timeout and time.time() - t_0 > timeout:
171 raise TimeoutException(
172 proc.communicate()[0],
173 'Subprocess exceeded timeout of %ds' % timeout)
174 time.sleep(0.5)
175 code = proc.poll()
176 output = proc.communicate()[0]
177 if echo:
178 print output
179 if log_file:
180 log_file.write(output)
181 log_file.flush()
182 return (code, output)
183
184
185 def run(cmd, echo=True, shell=False, timeout=None, print_timestamps=True,
186 log_in_real_time=True):
187 """ Run 'cmd' in a shell and return the combined contents of stdout and
188 stderr (Blocking). Throws an exception if the command exits non-zero.
189
190 cmd: list of strings (or single string, iff shell==True) indicating the
191 command to run
192 echo: boolean indicating whether we should print the command and log output
193 shell: boolean indicating whether we are using advanced shell features. Use
194 only when absolutely necessary, since this allows a lot more freedom which
195 could be exploited by malicious code. See the warning here:
196 http://docs.python.org/library/subprocess.html#popen-constructor
197 timeout: optional, integer indicating the maximum elapsed time in seconds
198 print_timestamps: boolean indicating whether a formatted timestamp should be
199 prepended to each line of output. Unused if echo or log_in_real_time is
200 False.
201 log_in_real_time: boolean indicating whether to read stdout from the
202 subprocess in real time instead of when the process finishes. If echo is
203 False, we never log in real time, even if log_in_real_time is True.
204 """
205 proc = run_async(cmd, echo=echo, shell=shell)
206 # If we're not printing the output, we don't care if the output shows up in
207 # real time, so don't bother.
208 if log_in_real_time and echo:
209 (returncode, output) = log_process_in_real_time(proc, echo=echo,
210 timeout=timeout, print_timestamps=print_timestamps)
211 else:
212 (returncode, output) = log_process_after_completion(proc, echo=echo,
213 timeout=timeout)
214 if returncode != 0:
215 raise CommandFailedException(
216 output,
217 'Command failed with code %d: %s' % (returncode, cmd))
218 return output
219
220
221 def run_retry(cmd, echo=True, shell=False, attempts=1,
222 secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS,
223 timeout=None, print_timestamps=True):
224 """ Wrapper for run() which makes multiple attempts until either the command
225 succeeds or the maximum number of attempts is reached. """
226 attempt = 1
227 while True:
228 try:
229 return run(cmd, echo=echo, shell=shell, timeout=timeout,
230 print_timestamps=print_timestamps)
231 except CommandFailedException:
232 if attempt >= attempts:
233 raise
234 print 'Command failed. Retrying in %d seconds...' % secs_between_attempts
235 time.sleep(secs_between_attempts)
236 attempt += 1
OLDNEW
« 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