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

Side by Side Diff: py/utils/shell_utils.py

Issue 346743008: Update common.py.utils to be used in Skia repo (Closed) Base URL: https://skia.googlesource.com/common.git@master
Patch Set: comment Created 6 years, 5 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') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """ This module contains tools for running commands in a shell. """ 6 """ This module contains tools for running commands in a shell. """
7 7
8 import datetime 8 import datetime
9 import os 9 import os
10 import Queue 10 import Queue
11 import select 11 import select
12 import subprocess 12 import subprocess
13 import sys 13 import sys
14 import threading 14 import threading
15 import time 15 import time
16 16
17 if 'nt' in os.name: 17 if 'nt' in os.name:
18 import ctypes 18 import ctypes
19 19
20 20
21 DEFAULT_SECS_BETWEEN_ATTEMPTS = 10 21 DEFAULT_SECS_BETWEEN_ATTEMPTS = 10
22 POLL_MILLIS = 250 22 POLL_MILLIS = 250
23 VERBOSE = True
23 24
24 25
25 class CommandFailedException(Exception): 26 class CommandFailedException(Exception):
26 """Exception which gets raised when a command fails.""" 27 """Exception which gets raised when a command fails."""
27 28
28 def __init__(self, output, *args): 29 def __init__(self, output, *args):
29 """Initialize the CommandFailedException. 30 """Initialize the CommandFailedException.
30 31
31 Args: 32 Args:
32 output: string; output from the command. 33 output: string; output from the command.
33 """ 34 """
34 Exception.__init__(self, *args) 35 Exception.__init__(self, *args)
35 self._output = output 36 self._output = output
36 37
37 @property 38 @property
38 def output(self): 39 def output(self):
39 """Output from the command.""" 40 """Output from the command."""
40 return self._output 41 return self._output
41 42
42 43
43 class TimeoutException(CommandFailedException): 44 class TimeoutException(CommandFailedException):
44 """CommandFailedException which gets raised when a subprocess exceeds its 45 """CommandFailedException which gets raised when a subprocess exceeds its
45 timeout. """ 46 timeout. """
46 pass 47 pass
47 48
48 49
49 def run_async(cmd, echo=True, shell=False): 50 def run_async(cmd, echo=None, shell=False):
50 """ Run 'cmd' in a subprocess, returning a Popen class instance referring to 51 """ Run 'cmd' in a subprocess, returning a Popen class instance referring to
51 that process. (Non-blocking) """ 52 that process. (Non-blocking) """
53 if echo is None:
54 echo = VERBOSE
52 if echo: 55 if echo:
53 print cmd 56 print cmd
54 if 'nt' in os.name: 57 if 'nt' in os.name:
55 # Windows has a bad habit of opening a dialog when a console program 58 # 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 59 # 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. 60 # 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 61 # This code prevents the dialog from appearing, so that we find out
59 # immediately and don't waste time waiting around. 62 # immediately and don't waste time waiting around.
60 SEM_NOGPFAULTERRORBOX = 0x0002 63 SEM_NOGPFAULTERRORBOX = 0x0002
61 ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX) 64 ctypes.windll.kernel32.SetErrorMode(SEM_NOGPFAULTERRORBOX)
(...skipping 30 matching lines...) Expand all
92 # Python-style. 95 # Python-style.
93 for line in iter(self._file.readline, ''): 96 for line in iter(self._file.readline, ''):
94 self._queue.put(line) 97 self._queue.put(line)
95 if self._stopped: 98 if self._stopped:
96 break 99 break
97 100
98 def stop(self): 101 def stop(self):
99 self._stopped = True 102 self._stopped = True
100 103
101 104
102 def log_process_in_real_time(proc, echo=True, timeout=None, log_file=None, 105 def log_process_in_real_time(proc, echo=None, timeout=None, log_file=None,
103 halt_on_output=None, print_timestamps=True): 106 halt_on_output=None, print_timestamps=True):
104 """ Log the output of proc in real time until it completes. Return a tuple 107 """ 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. 108 containing the exit code of proc and the contents of stdout.
106 109
107 proc: an instance of Popen referring to a running subprocess. 110 proc: an instance of Popen referring to a running subprocess.
108 echo: boolean indicating whether to print the output received from proc.stdout 111 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 112 timeout: number of seconds allotted for the process to run. Raises a
110 TimeoutException if the run time exceeds the timeout. 113 TimeoutException if the run time exceeds the timeout.
111 log_file: an open file for writing output 114 log_file: an open file for writing output
112 halt_on_output: string; kill the process and return if this string is found 115 halt_on_output: string; kill the process and return if this string is found
113 in the output stream from the process. 116 in the output stream from the process.
114 print_timestamps: boolean indicating whether a formatted timestamp should be 117 print_timestamps: boolean indicating whether a formatted timestamp should be
115 prepended to each line of output. 118 prepended to each line of output.
116 """ 119 """
120 if echo is None:
121 echo = VERBOSE
117 stdout_queue = Queue.Queue() 122 stdout_queue = Queue.Queue()
118 log_thread = EnqueueThread(proc.stdout, stdout_queue) 123 log_thread = EnqueueThread(proc.stdout, stdout_queue)
119 log_thread.start() 124 log_thread.start()
120 try: 125 try:
121 all_output = [] 126 all_output = []
122 t_0 = time.time() 127 t_0 = time.time()
123 while True: 128 while True:
124 code = proc.poll() 129 code = proc.poll()
125 try: 130 try:
126 output = stdout_queue.get_nowait() 131 output = stdout_queue.get_nowait()
(...skipping 19 matching lines...) Expand all
146 proc.terminate() 151 proc.terminate()
147 raise TimeoutException( 152 raise TimeoutException(
148 ''.join(all_output), 153 ''.join(all_output),
149 'Subprocess exceeded timeout of %ds' % timeout) 154 'Subprocess exceeded timeout of %ds' % timeout)
150 finally: 155 finally:
151 log_thread.stop() 156 log_thread.stop()
152 log_thread.join() 157 log_thread.join()
153 return (code, ''.join(all_output)) 158 return (code, ''.join(all_output))
154 159
155 160
156 def log_process_after_completion(proc, echo=True, timeout=None, log_file=None): 161 def log_process_after_completion(proc, echo=None, timeout=None,
162 log_file=None):
157 """ Wait for proc to complete and return a tuple containing the exit code of 163 """ 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 164 proc and the contents of stdout. Unlike log_process_in_real_time, does not
159 attempt to read stdout from proc in real time. 165 attempt to read stdout from proc in real time.
160 166
161 proc: an instance of Popen referring to a running subprocess. 167 proc: an instance of Popen referring to a running subprocess.
162 echo: boolean indicating whether to print the output received from proc.stdout 168 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 169 timeout: number of seconds allotted for the process to run. Raises a
164 TimeoutException if the run time exceeds the timeout. 170 TimeoutException if the run time exceeds the timeout.
165 log_file: an open file for writing outout 171 log_file: an open file for writing outout
166 """ 172 """
173 if echo is None:
174 echo = VERBOSE
167 t_0 = time.time() 175 t_0 = time.time()
168 code = None 176 code = None
169 while code is None: 177 while code is None:
170 if timeout and time.time() - t_0 > timeout: 178 if timeout and time.time() - t_0 > timeout:
171 raise TimeoutException( 179 raise TimeoutException(
172 proc.communicate()[0], 180 proc.communicate()[0],
173 'Subprocess exceeded timeout of %ds' % timeout) 181 'Subprocess exceeded timeout of %ds' % timeout)
174 time.sleep(0.5) 182 time.sleep(0.5)
175 code = proc.poll() 183 code = proc.poll()
176 output = proc.communicate()[0] 184 output = proc.communicate()[0]
177 if echo: 185 if echo:
178 print output 186 print output
179 if log_file: 187 if log_file:
180 log_file.write(output) 188 log_file.write(output)
181 log_file.flush() 189 log_file.flush()
182 return (code, output) 190 return (code, output)
183 191
184 192
185 def run(cmd, echo=True, shell=False, timeout=None, print_timestamps=True, 193 def run(cmd, echo=None, shell=False, timeout=None, print_timestamps=True,
186 log_in_real_time=True): 194 log_in_real_time=True):
187 """ Run 'cmd' in a shell and return the combined contents of stdout and 195 """ 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. 196 stderr (Blocking). Throws an exception if the command exits non-zero.
189 197
190 cmd: list of strings (or single string, iff shell==True) indicating the 198 cmd: list of strings (or single string, iff shell==True) indicating the
191 command to run 199 command to run
192 echo: boolean indicating whether we should print the command and log output 200 echo: boolean indicating whether we should print the command and log output
193 shell: boolean indicating whether we are using advanced shell features. Use 201 shell: boolean indicating whether we are using advanced shell features. Use
194 only when absolutely necessary, since this allows a lot more freedom which 202 only when absolutely necessary, since this allows a lot more freedom which
195 could be exploited by malicious code. See the warning here: 203 could be exploited by malicious code. See the warning here:
196 http://docs.python.org/library/subprocess.html#popen-constructor 204 http://docs.python.org/library/subprocess.html#popen-constructor
197 timeout: optional, integer indicating the maximum elapsed time in seconds 205 timeout: optional, integer indicating the maximum elapsed time in seconds
198 print_timestamps: boolean indicating whether a formatted timestamp should be 206 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 207 prepended to each line of output. Unused if echo or log_in_real_time is
200 False. 208 False.
201 log_in_real_time: boolean indicating whether to read stdout from the 209 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 210 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. 211 False, we never log in real time, even if log_in_real_time is True.
204 """ 212 """
213 if echo is None:
214 echo = VERBOSE
205 proc = run_async(cmd, echo=echo, shell=shell) 215 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 216 # If we're not printing the output, we don't care if the output shows up in
207 # real time, so don't bother. 217 # real time, so don't bother.
208 if log_in_real_time and echo: 218 if log_in_real_time and echo:
209 (returncode, output) = log_process_in_real_time(proc, echo=echo, 219 (returncode, output) = log_process_in_real_time(proc, echo=echo,
210 timeout=timeout, print_timestamps=print_timestamps) 220 timeout=timeout, print_timestamps=print_timestamps)
211 else: 221 else:
212 (returncode, output) = log_process_after_completion(proc, echo=echo, 222 (returncode, output) = log_process_after_completion(proc, echo=echo,
213 timeout=timeout) 223 timeout=timeout)
214 if returncode != 0: 224 if returncode != 0:
215 raise CommandFailedException( 225 raise CommandFailedException(
216 output, 226 output,
217 'Command failed with code %d: %s' % (returncode, cmd)) 227 'Command failed with code %d: %s' % (returncode, cmd))
218 return output 228 return output
219 229
220 230
221 def run_retry(cmd, echo=True, shell=False, attempts=1, 231 def run_retry(cmd, echo=None, shell=False, attempts=1,
222 secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS, 232 secs_between_attempts=DEFAULT_SECS_BETWEEN_ATTEMPTS,
223 timeout=None, print_timestamps=True): 233 timeout=None, print_timestamps=True):
224 """ Wrapper for run() which makes multiple attempts until either the command 234 """ Wrapper for run() which makes multiple attempts until either the command
225 succeeds or the maximum number of attempts is reached. """ 235 succeeds or the maximum number of attempts is reached. """
236 if echo is None:
237 echo = VERBOSE
226 attempt = 1 238 attempt = 1
227 while True: 239 while True:
228 try: 240 try:
229 return run(cmd, echo=echo, shell=shell, timeout=timeout, 241 return run(cmd, echo=echo, shell=shell, timeout=timeout,
230 print_timestamps=print_timestamps) 242 print_timestamps=print_timestamps)
231 except CommandFailedException: 243 except CommandFailedException:
232 if attempt >= attempts: 244 if attempt >= attempts:
233 raise 245 raise
234 print 'Command failed. Retrying in %d seconds...' % secs_between_attempts 246 print 'Command failed. Retrying in %d seconds...' % secs_between_attempts
235 time.sleep(secs_between_attempts) 247 time.sleep(secs_between_attempts)
236 attempt += 1 248 attempt += 1
OLDNEW
« no previous file with comments | « py/utils/misc.py ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698