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