blob: d2fb9d36455258db5089f3a08719cef2fd466918 [file] [log] [blame]
mbligh63073c92008-03-31 16:49:32 +00001#!/usr/bin/python
2#
3# Copyright 2008 Google Inc. Released under the GPL v2
4
5import os, pickle, random, re, select, shutil, signal, StringIO, subprocess
mbligh0a297572008-04-02 00:13:45 +00006import sys, time, textwrap, urllib, urlparse
mbligh63073c92008-03-31 16:49:32 +00007import error
mbligh6231cd62008-02-02 19:18:33 +00008
mblighde0d47e2008-03-28 14:37:18 +00009
jadmanski5182e162008-05-13 21:48:16 +000010def read_one_line(filename):
11 return open(filename, 'r').readline().strip()
12
13
14def write_one_line(filename, str):
15 open(filename, 'w').write(str.rstrip() + "\n")
16
17
mblighde0d47e2008-03-28 14:37:18 +000018def read_keyval(path):
19 """
20 Read a key-value pair format file into a dictionary, and return it.
21 Takes either a filename or directory name as input. If it's a
22 directory name, we assume you want the file to be called keyval.
23 """
24 if os.path.isdir(path):
25 path = os.path.join(path, 'keyval')
26 keyval = {}
27 for line in open(path):
28 line = re.sub('#.*', '', line.rstrip())
mblighcaa62c22008-04-07 21:51:17 +000029 if not re.search(r'^[-\w]+=', line):
mblighde0d47e2008-03-28 14:37:18 +000030 raise ValueError('Invalid format line: %s' % line)
31 key, value = line.split('=', 1)
32 if re.search('^\d+$', value):
33 value = int(value)
34 elif re.search('^(\d+\.)?\d+$', value):
35 value = float(value)
36 keyval[key] = value
37 return keyval
38
39
mbligh6231cd62008-02-02 19:18:33 +000040def write_keyval(path, dictionary):
41 if os.path.isdir(path):
42 path = os.path.join(path, 'keyval')
43 keyval = open(path, 'a')
44 try:
45 for key, value in dictionary.iteritems():
mblighcaa62c22008-04-07 21:51:17 +000046 if re.search(r'[^-\w]', key):
mbligh6231cd62008-02-02 19:18:33 +000047 raise ValueError('Invalid key: %s' % key)
48 keyval.write('%s=%s\n' % (key, value))
49 finally:
50 keyval.close()
51
52
53def is_url(path):
54 """Return true if path looks like a URL"""
55 # for now, just handle http and ftp
56 url_parts = urlparse.urlparse(path)
57 return (url_parts[0] in ('http', 'ftp'))
58
59
60def get_file(src, dest, permissions=None):
61 """Get a file from src, which can be local or a remote URL"""
62 if (src == dest):
63 return
64 if (is_url(src)):
65 print 'PWD: ' + os.getcwd()
66 print 'Fetching \n\t', src, '\n\t->', dest
67 try:
68 urllib.urlretrieve(src, dest)
69 except IOError, e:
mbligh63073c92008-03-31 16:49:32 +000070 raise error.AutotestError('Unable to retrieve %s (to %s)'
mbligh6231cd62008-02-02 19:18:33 +000071 % (src, dest), e)
72 else:
73 shutil.copyfile(src, dest)
74 if permissions:
75 os.chmod(dest, permissions)
76 return dest
77
78
79def unmap_url(srcdir, src, destdir='.'):
80 """
81 Receives either a path to a local file or a URL.
82 returns either the path to the local file, or the fetched URL
83
84 unmap_url('/usr/src', 'foo.tar', '/tmp')
85 = '/usr/src/foo.tar'
86 unmap_url('/usr/src', 'http://site/file', '/tmp')
87 = '/tmp/file'
88 (after retrieving it)
89 """
90 if is_url(src):
91 url_parts = urlparse.urlparse(src)
92 filename = os.path.basename(url_parts[2])
93 dest = os.path.join(destdir, filename)
94 return get_file(src, dest)
95 else:
96 return os.path.join(srcdir, src)
97
98
99def update_version(srcdir, preserve_srcdir, new_version, install,
100 *args, **dargs):
101 """
102 Make sure srcdir is version new_version
103
104 If not, delete it and install() the new version.
105
106 In the preserve_srcdir case, we just check it's up to date,
107 and if not, we rerun install, without removing srcdir
108 """
109 versionfile = os.path.join(srcdir, '.version')
110 install_needed = True
111
112 if os.path.exists(versionfile):
113 old_version = pickle.load(open(versionfile))
114 if old_version == new_version:
115 install_needed = False
116
117 if install_needed:
118 if not preserve_srcdir and os.path.exists(srcdir):
119 shutil.rmtree(srcdir)
120 install(*args, **dargs)
121 if os.path.exists(srcdir):
122 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000123
124
mbligh63073c92008-03-31 16:49:32 +0000125def run(command, timeout=None, ignore_status=False,
126 stdout_tee=None, stderr_tee=None):
127 """
128 Run a command on the host.
129
130 Args:
131 command: the command line string
132 timeout: time limit in seconds before attempting to
133 kill the running process. The run() function
134 will take a few seconds longer than 'timeout'
135 to complete if it has to kill the process.
136 ignore_status: do not raise an exception, no matter what
137 the exit code of the command is.
138 stdout_tee: optional file-like object to which stdout data
139 will be written as it is generated (data will still
140 be stored in result.stdout)
141 stderr_tee: likewise for stderr
142
143 Returns:
144 a CmdResult object
145
146 Raises:
mbligh8ea61e22008-05-09 18:09:37 +0000147 CmdError: the exit code of the command
mbligh63073c92008-03-31 16:49:32 +0000148 execution was not 0
149 """
150 return join_bg_job(run_bg(command), timeout, ignore_status,
151 stdout_tee, stderr_tee)
152
153
154def run_bg(command):
155 """Run the command in a subprocess and return the subprocess."""
156 result = CmdResult(command)
157 sp = subprocess.Popen(command, stdout=subprocess.PIPE,
158 stderr=subprocess.PIPE,
159 shell=True, executable="/bin/bash")
160 return sp, result
161
162
163def join_bg_job(bg_job, timeout=None, ignore_status=False,
164 stdout_tee=None, stderr_tee=None):
165 """Join the subprocess with the current thread. See run description."""
166 sp, result = bg_job
167 stdout_file = StringIO.StringIO()
168 stderr_file = StringIO.StringIO()
mbligh8ea61e22008-05-09 18:09:37 +0000169 (ret, timeouterr) = (0, False)
mbligh63073c92008-03-31 16:49:32 +0000170
171 try:
172 # We are holding ends to stdin, stdout pipes
173 # hence we need to be sure to close those fds no mater what
174 start_time = time.time()
mbligh8ea61e22008-05-09 18:09:37 +0000175 (ret, timeouterr) = _wait_for_command(sp, start_time,
176 timeout, stdout_file, stderr_file,
177 stdout_tee, stderr_tee)
mbligh63073c92008-03-31 16:49:32 +0000178 result.exit_status = ret
mbligh63073c92008-03-31 16:49:32 +0000179 result.duration = time.time() - start_time
180 # don't use os.read now, so we get all the rest of the output
181 _process_output(sp.stdout, stdout_file, stdout_tee,
182 use_os_read=False)
183 _process_output(sp.stderr, stderr_file, stderr_tee,
184 use_os_read=False)
185 finally:
186 # close our ends of the pipes to the sp no matter what
187 sp.stdout.close()
188 sp.stderr.close()
189
190 result.stdout = stdout_file.getvalue()
191 result.stderr = stderr_file.getvalue()
192
mbligh8ea61e22008-05-09 18:09:37 +0000193 if result.exit_status != 0:
194 if timeouterr:
195 raise error.CmdError('Command not complete within'
196 ' %s seconds' % timeout, result)
197 elif not ignore_status:
198 raise error.CmdError("command execution error", result)
mbligh63073c92008-03-31 16:49:32 +0000199
200 return result
201
mbligh8ea61e22008-05-09 18:09:37 +0000202# this returns a tuple with the return code and a flag to specify if the error
203# is due to the process not terminating within timeout
mbligh63073c92008-03-31 16:49:32 +0000204def _wait_for_command(subproc, start_time, timeout, stdout_file, stderr_file,
205 stdout_tee, stderr_tee):
206 if timeout:
207 stop_time = start_time + timeout
208 time_left = stop_time - time.time()
209 else:
210 time_left = None # so that select never times out
211 while not timeout or time_left > 0:
212 # select will return when stdout is ready (including when it is
213 # EOF, that is the process has terminated).
214 ready, _, _ = select.select([subproc.stdout, subproc.stderr],
215 [], [], time_left)
216 # os.read() has to be used instead of
217 # subproc.stdout.read() which will otherwise block
218 if subproc.stdout in ready:
219 _process_output(subproc.stdout, stdout_file,
220 stdout_tee)
221 if subproc.stderr in ready:
222 _process_output(subproc.stderr, stderr_file,
223 stderr_tee)
224
225 exit_status_indication = subproc.poll()
226
227 if exit_status_indication is not None:
mbligh8ea61e22008-05-09 18:09:37 +0000228 return (exit_status_indication, False)
229
mbligh63073c92008-03-31 16:49:32 +0000230 if timeout:
231 time_left = stop_time - time.time()
232
233 # the process has not terminated within timeout,
234 # kill it via an escalating series of signals.
235 if exit_status_indication is None:
mbligh8ea61e22008-05-09 18:09:37 +0000236 exit_status_indication = nuke_subprocess(subproc)
237
238 return (exit_status_indication, True)
mbligh63073c92008-03-31 16:49:32 +0000239
240
241def _process_output(pipe, fbuffer, teefile=None, use_os_read=True):
242 if use_os_read:
243 data = os.read(pipe.fileno(), 1024)
244 else:
245 data = pipe.read()
246 fbuffer.write(data)
247 if teefile:
248 teefile.write(data)
249 teefile.flush()
250
251
252def nuke_subprocess(subproc):
253 # the process has not terminated within timeout,
254 # kill it via an escalating series of signals.
255 signal_queue = [signal.SIGTERM, signal.SIGKILL]
256 for sig in signal_queue:
257 try:
258 os.kill(subproc.pid, sig)
259 # The process may have died before we could kill it.
260 except OSError:
261 pass
262
263 for i in range(5):
264 rc = subproc.poll()
265 if rc != None:
mbligh8ea61e22008-05-09 18:09:37 +0000266 return rc
mbligh63073c92008-03-31 16:49:32 +0000267 time.sleep(1)
268
269
270def nuke_pid(pid):
271 # the process has not terminated within timeout,
272 # kill it via an escalating series of signals.
273 signal_queue = [signal.SIGTERM, signal.SIGKILL]
274 for sig in signal_queue:
275 try:
276 os.kill(pid, sig)
277
278 # The process may have died before we could kill it.
279 except OSError:
280 pass
281
282 try:
283 for i in range(5):
284 status = os.waitpid(pid, os.WNOHANG)[0]
285 if status == pid:
286 return
287 time.sleep(1)
288
289 if status != pid:
290 raise error.AutoservRunError('Could not kill %d'
291 % pid, None)
292
293 # the process died before we join it.
294 except OSError:
295 pass
296
297
298def _process_output(pipe, fbuffer, teefile=None, use_os_read=True):
299 if use_os_read:
300 data = os.read(pipe.fileno(), 1024)
301 else:
302 data = pipe.read()
303 fbuffer.write(data)
304 if teefile:
305 teefile.write(data)
306 teefile.flush()
307
308
309def system(command, timeout=None, ignore_status=False):
310 return run(command, timeout, ignore_status,
311 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
312
313
mbligh8ea61e22008-05-09 18:09:37 +0000314def system_output(command, timeout=None, ignore_status=False,
315 retain_output=False):
316 if retain_output:
317 out = run(command, timeout, ignore_status,
318 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
319 else:
320 out = run(command, timeout, ignore_status).stdout
mbligh63073c92008-03-31 16:49:32 +0000321 if out[-1:] == '\n': out = out[:-1]
322 return out
323
324
325class CmdResult(object):
326 """
327 Command execution result.
328
329 command: String containing the command line itself
330 exit_status: Integer exit code of the process
331 stdout: String containing stdout of the process
332 stderr: String containing stderr of the process
333 duration: Elapsed wall clock time running the process
334 """
335
336
337 def __init__(self, command = None):
338 self.command = command
339 self.exit_status = None
340 self.stdout = ""
341 self.stderr = ""
342 self.duration = 0
343
344
345 def __repr__(self):
346 wrapper = textwrap.TextWrapper(width = 78,
347 initial_indent="\n ",
348 subsequent_indent=" ")
349
350 stdout = self.stdout.rstrip()
351 if stdout:
352 stdout = "\nstdout:\n%s" % stdout
353
354 stderr = self.stderr.rstrip()
355 if stderr:
356 stderr = "\nstderr:\n%s" % stderr
357
358 return ("* Command: %s\n"
359 "Exit status: %s\n"
360 "Duration: %s\n"
361 "%s"
362 "%s"
363 % (wrapper.fill(self.command), self.exit_status,
364 self.duration, stdout, stderr))
365
366
mbligh462c0152008-03-13 15:37:10 +0000367class run_randomly:
mbligh63073c92008-03-31 16:49:32 +0000368
369
mbligh462c0152008-03-13 15:37:10 +0000370 def __init__(self):
371 self.test_list = []
372
373
374 def add(self, *args, **dargs):
375 test = (args, dargs)
376 self.test_list.append(test)
377
378
379 def run(self, fn):
380 while self.test_list:
381 test_index = random.randint(0, len(self.test_list)-1)
382 (args, dargs) = self.test_list.pop(test_index)
383 fn(*args, **dargs)