blob: c29d7e412cab1853d521e065f16d7d5c09723d70 [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
mbligh02ff2d52008-06-03 15:00:21 +00006import socket, sys, time, textwrap, urllib, urlparse
mblighc1cbc992008-05-27 20:01:45 +00007import error, barrier
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):
mbligh6e8840c2008-07-11 18:05:49 +000011 return open(filename, 'r').readline().rstrip('\n')
jadmanski5182e162008-05-13 21:48:16 +000012
13
14def write_one_line(filename, str):
mbligh6e8840c2008-07-11 18:05:49 +000015 open(filename, 'w').write(str.rstrip('\n') + '\n')
jadmanski5182e162008-05-13 21:48:16 +000016
17
mblighde0d47e2008-03-28 14:37:18 +000018def read_keyval(path):
jadmanski0afbb632008-06-06 21:10:57 +000019 """
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):
jadmanskia6014a02008-07-14 19:41:54 +000028 line = re.sub('#.*', '', line).rstrip()
jadmanski0afbb632008-06-06 21:10:57 +000029 if not re.search(r'^[-\w]+=', line):
30 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
mblighde0d47e2008-03-28 14:37:18 +000038
39
jadmanskicc549172008-05-21 18:11:51 +000040def write_keyval(path, dictionary, type_tag=None):
jadmanski0afbb632008-06-06 21:10:57 +000041 """
42 Write a key-value pair format file out to a file. This uses append
43 mode to open the file, so existing text will not be overwritten or
44 reparsed.
jadmanskicc549172008-05-21 18:11:51 +000045
jadmanski0afbb632008-06-06 21:10:57 +000046 If type_tag is None, then the key must be composed of alphanumeric
47 characters (or dashes+underscores). However, if type-tag is not
48 null then the keys must also have "{type_tag}" as a suffix. At
49 the moment the only valid values of type_tag are "attr" and "perf".
50 """
51 if os.path.isdir(path):
52 path = os.path.join(path, 'keyval')
53 keyval = open(path, 'a')
jadmanskicc549172008-05-21 18:11:51 +000054
jadmanski0afbb632008-06-06 21:10:57 +000055 if type_tag is None:
56 key_regex = re.compile(r'^[-\w]+$')
57 else:
58 if type_tag not in ('attr', 'perf'):
59 raise ValueError('Invalid type tag: %s' % type_tag)
60 escaped_tag = re.escape(type_tag)
61 key_regex = re.compile(r'^[-\w]+\{%s\}$' % escaped_tag)
62 try:
63 for key, value in dictionary.iteritems():
64 if not key_regex.search(key):
65 raise ValueError('Invalid key: %s' % key)
66 keyval.write('%s=%s\n' % (key, value))
67 finally:
68 keyval.close()
mbligh6231cd62008-02-02 19:18:33 +000069
70
71def is_url(path):
jadmanski0afbb632008-06-06 21:10:57 +000072 """Return true if path looks like a URL"""
73 # for now, just handle http and ftp
74 url_parts = urlparse.urlparse(path)
75 return (url_parts[0] in ('http', 'ftp'))
mbligh6231cd62008-02-02 19:18:33 +000076
77
mbligh02ff2d52008-06-03 15:00:21 +000078def urlopen(url, data=None, proxies=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +000079 """Wrapper to urllib.urlopen with timeout addition."""
mbligh02ff2d52008-06-03 15:00:21 +000080
jadmanski0afbb632008-06-06 21:10:57 +000081 # Save old timeout
82 old_timeout = socket.getdefaulttimeout()
83 socket.setdefaulttimeout(timeout)
84 try:
85 return urllib.urlopen(url, data=data, proxies=proxies)
86 finally:
87 socket.setdefaulttimeout(old_timeout)
mbligh02ff2d52008-06-03 15:00:21 +000088
89
90def urlretrieve(url, filename=None, reporthook=None, data=None, timeout=300):
jadmanski0afbb632008-06-06 21:10:57 +000091 """Wrapper to urllib.urlretrieve with timeout addition."""
92 old_timeout = socket.getdefaulttimeout()
93 socket.setdefaulttimeout(timeout)
94 try:
95 return urllib.urlretrieve(url, filename=filename,
96 reporthook=reporthook, data=data)
97 finally:
98 socket.setdefaulttimeout(old_timeout)
99
mbligh02ff2d52008-06-03 15:00:21 +0000100
mbligh6231cd62008-02-02 19:18:33 +0000101def get_file(src, dest, permissions=None):
jadmanski0afbb632008-06-06 21:10:57 +0000102 """Get a file from src, which can be local or a remote URL"""
103 if (src == dest):
104 return
105 if (is_url(src)):
106 print 'PWD: ' + os.getcwd()
107 print 'Fetching \n\t', src, '\n\t->', dest
108 try:
109 urllib.urlretrieve(src, dest)
110 except IOError, e:
111 raise error.AutotestError('Unable to retrieve %s (to %s)'
112 % (src, dest), e)
113 else:
114 shutil.copyfile(src, dest)
115 if permissions:
116 os.chmod(dest, permissions)
117 return dest
mbligh6231cd62008-02-02 19:18:33 +0000118
119
120def unmap_url(srcdir, src, destdir='.'):
jadmanski0afbb632008-06-06 21:10:57 +0000121 """
122 Receives either a path to a local file or a URL.
123 returns either the path to the local file, or the fetched URL
mbligh6231cd62008-02-02 19:18:33 +0000124
jadmanski0afbb632008-06-06 21:10:57 +0000125 unmap_url('/usr/src', 'foo.tar', '/tmp')
126 = '/usr/src/foo.tar'
127 unmap_url('/usr/src', 'http://site/file', '/tmp')
128 = '/tmp/file'
129 (after retrieving it)
130 """
131 if is_url(src):
132 url_parts = urlparse.urlparse(src)
133 filename = os.path.basename(url_parts[2])
134 dest = os.path.join(destdir, filename)
135 return get_file(src, dest)
136 else:
137 return os.path.join(srcdir, src)
mbligh6231cd62008-02-02 19:18:33 +0000138
139
140def update_version(srcdir, preserve_srcdir, new_version, install,
jadmanski0afbb632008-06-06 21:10:57 +0000141 *args, **dargs):
142 """
143 Make sure srcdir is version new_version
mbligh6231cd62008-02-02 19:18:33 +0000144
jadmanski0afbb632008-06-06 21:10:57 +0000145 If not, delete it and install() the new version.
mbligh6231cd62008-02-02 19:18:33 +0000146
jadmanski0afbb632008-06-06 21:10:57 +0000147 In the preserve_srcdir case, we just check it's up to date,
148 and if not, we rerun install, without removing srcdir
149 """
150 versionfile = os.path.join(srcdir, '.version')
151 install_needed = True
mbligh6231cd62008-02-02 19:18:33 +0000152
jadmanski0afbb632008-06-06 21:10:57 +0000153 if os.path.exists(versionfile):
154 old_version = pickle.load(open(versionfile))
155 if old_version == new_version:
156 install_needed = False
mbligh6231cd62008-02-02 19:18:33 +0000157
jadmanski0afbb632008-06-06 21:10:57 +0000158 if install_needed:
159 if not preserve_srcdir and os.path.exists(srcdir):
160 shutil.rmtree(srcdir)
161 install(*args, **dargs)
162 if os.path.exists(srcdir):
163 pickle.dump(new_version, open(versionfile, 'w'))
mbligh462c0152008-03-13 15:37:10 +0000164
165
mbligh63073c92008-03-31 16:49:32 +0000166def run(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000167 stdout_tee=None, stderr_tee=None):
168 """
169 Run a command on the host.
mbligh63073c92008-03-31 16:49:32 +0000170
jadmanski0afbb632008-06-06 21:10:57 +0000171 Args:
172 command: the command line string
173 timeout: time limit in seconds before attempting to
174 kill the running process. The run() function
175 will take a few seconds longer than 'timeout'
176 to complete if it has to kill the process.
177 ignore_status: do not raise an exception, no matter what
178 the exit code of the command is.
179 stdout_tee: optional file-like object to which stdout data
180 will be written as it is generated (data will still
181 be stored in result.stdout)
182 stderr_tee: likewise for stderr
mbligh63073c92008-03-31 16:49:32 +0000183
jadmanski0afbb632008-06-06 21:10:57 +0000184 Returns:
185 a CmdResult object
mbligh63073c92008-03-31 16:49:32 +0000186
jadmanski0afbb632008-06-06 21:10:57 +0000187 Raises:
188 CmdError: the exit code of the command
189 execution was not 0
190 """
191 return join_bg_job(run_bg(command), command, timeout, ignore_status,
192 stdout_tee, stderr_tee)
mbligh63073c92008-03-31 16:49:32 +0000193
194
195def run_bg(command):
jadmanski0afbb632008-06-06 21:10:57 +0000196 """Run the command in a subprocess and return the subprocess."""
197 result = CmdResult(command)
jadmanskie9be8c32008-07-01 14:18:42 +0000198 def reset_sigpipe():
199 signal.signal(signal.SIGPIPE, signal.SIG_DFL)
jadmanski0afbb632008-06-06 21:10:57 +0000200 sp = subprocess.Popen(command, stdout=subprocess.PIPE,
jadmanskie9be8c32008-07-01 14:18:42 +0000201 stderr=subprocess.PIPE, preexec_fn=reset_sigpipe,
jadmanski0afbb632008-06-06 21:10:57 +0000202 shell=True, executable="/bin/bash")
203 return sp, result
mbligh63073c92008-03-31 16:49:32 +0000204
205
jadmanskid93d7d22008-05-29 21:37:29 +0000206def join_bg_job(bg_job, command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000207 stdout_tee=None, stderr_tee=None):
208 """Join the subprocess with the current thread. See run description."""
209 sp, result = bg_job
210 stdout_file = StringIO.StringIO()
211 stderr_file = StringIO.StringIO()
212 (ret, timeouterr) = (0, False)
mbligh63073c92008-03-31 16:49:32 +0000213
jadmanski0afbb632008-06-06 21:10:57 +0000214 try:
215 # We are holding ends to stdin, stdout pipes
216 # hence we need to be sure to close those fds no mater what
217 start_time = time.time()
218 (ret, timeouterr) = _wait_for_command(sp, start_time,
219 timeout, stdout_file, stderr_file,
220 stdout_tee, stderr_tee)
221 result.exit_status = ret
222 result.duration = time.time() - start_time
223 # don't use os.read now, so we get all the rest of the output
jadmanski835ae272008-07-24 15:27:56 +0000224 _process_output(sp.stdout, stdout_file, stdout_tee, final_read=True)
225 _process_output(sp.stderr, stderr_file, stderr_tee, final_read=True)
jadmanski0afbb632008-06-06 21:10:57 +0000226 finally:
227 # close our ends of the pipes to the sp no matter what
228 sp.stdout.close()
229 sp.stderr.close()
mbligh63073c92008-03-31 16:49:32 +0000230
jadmanski0afbb632008-06-06 21:10:57 +0000231 result.stdout = stdout_file.getvalue()
232 result.stderr = stderr_file.getvalue()
mbligh63073c92008-03-31 16:49:32 +0000233
jadmanski0afbb632008-06-06 21:10:57 +0000234 if result.exit_status != 0:
235 if timeouterr:
236 raise error.CmdError(command, result, "Command did not "
237 "complete within %d seconds" % timeout)
238 elif not ignore_status:
239 raise error.CmdError(command, result,
240 "Command returned non-zero exit status")
mbligh63073c92008-03-31 16:49:32 +0000241
jadmanski0afbb632008-06-06 21:10:57 +0000242 return result
mbligh63073c92008-03-31 16:49:32 +0000243
mbligh8ea61e22008-05-09 18:09:37 +0000244# this returns a tuple with the return code and a flag to specify if the error
245# is due to the process not terminating within timeout
mbligh63073c92008-03-31 16:49:32 +0000246def _wait_for_command(subproc, start_time, timeout, stdout_file, stderr_file,
jadmanski0afbb632008-06-06 21:10:57 +0000247 stdout_tee, stderr_tee):
248 if timeout:
249 stop_time = start_time + timeout
250 time_left = stop_time - time.time()
251 else:
252 time_left = None # so that select never times out
253 while not timeout or time_left > 0:
254 # select will return when stdout is ready (including when it is
255 # EOF, that is the process has terminated).
256 ready, _, _ = select.select([subproc.stdout, subproc.stderr],
257 [], [], time_left)
258 # os.read() has to be used instead of
259 # subproc.stdout.read() which will otherwise block
260 if subproc.stdout in ready:
jadmanski835ae272008-07-24 15:27:56 +0000261 _process_output(subproc.stdout, stdout_file, stdout_tee)
jadmanski0afbb632008-06-06 21:10:57 +0000262 if subproc.stderr in ready:
jadmanski835ae272008-07-24 15:27:56 +0000263 _process_output(subproc.stderr, stderr_file, stderr_tee)
mbligh63073c92008-03-31 16:49:32 +0000264
jadmanski0afbb632008-06-06 21:10:57 +0000265 exit_status_indication = subproc.poll()
mbligh63073c92008-03-31 16:49:32 +0000266
jadmanski0afbb632008-06-06 21:10:57 +0000267 if exit_status_indication is not None:
268 return (exit_status_indication, False)
mbligh8ea61e22008-05-09 18:09:37 +0000269
jadmanski0afbb632008-06-06 21:10:57 +0000270 if timeout:
271 time_left = stop_time - time.time()
mbligh63073c92008-03-31 16:49:32 +0000272
jadmanski0afbb632008-06-06 21:10:57 +0000273 # the process has not terminated within timeout,
274 # kill it via an escalating series of signals.
275 if exit_status_indication is None:
276 exit_status_indication = nuke_subprocess(subproc)
mbligh8ea61e22008-05-09 18:09:37 +0000277
jadmanski0afbb632008-06-06 21:10:57 +0000278 return (exit_status_indication, True)
mbligh63073c92008-03-31 16:49:32 +0000279
280
mbligh63073c92008-03-31 16:49:32 +0000281def nuke_subprocess(subproc):
jadmanski0afbb632008-06-06 21:10:57 +0000282 # the process has not terminated within timeout,
283 # kill it via an escalating series of signals.
284 signal_queue = [signal.SIGTERM, signal.SIGKILL]
285 for sig in signal_queue:
286 try:
287 os.kill(subproc.pid, sig)
288 # The process may have died before we could kill it.
289 except OSError:
290 pass
mbligh63073c92008-03-31 16:49:32 +0000291
jadmanski0afbb632008-06-06 21:10:57 +0000292 for i in range(5):
293 rc = subproc.poll()
294 if rc != None:
295 return rc
296 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000297
298
299def nuke_pid(pid):
jadmanski0afbb632008-06-06 21:10:57 +0000300 # the process has not terminated within timeout,
301 # kill it via an escalating series of signals.
302 signal_queue = [signal.SIGTERM, signal.SIGKILL]
303 for sig in signal_queue:
304 try:
305 os.kill(pid, sig)
mbligh63073c92008-03-31 16:49:32 +0000306
jadmanski0afbb632008-06-06 21:10:57 +0000307 # The process may have died before we could kill it.
308 except OSError:
309 pass
mbligh63073c92008-03-31 16:49:32 +0000310
jadmanski0afbb632008-06-06 21:10:57 +0000311 try:
312 for i in range(5):
313 status = os.waitpid(pid, os.WNOHANG)[0]
314 if status == pid:
315 return
316 time.sleep(1)
mbligh63073c92008-03-31 16:49:32 +0000317
jadmanski0afbb632008-06-06 21:10:57 +0000318 if status != pid:
319 raise error.AutoservRunError('Could not kill %d'
320 % pid, None)
mbligh63073c92008-03-31 16:49:32 +0000321
jadmanski0afbb632008-06-06 21:10:57 +0000322 # the process died before we join it.
323 except OSError:
324 pass
mbligh63073c92008-03-31 16:49:32 +0000325
326
jadmanski835ae272008-07-24 15:27:56 +0000327def _process_output(pipe, fbuffer, teefile=None, final_read=False):
328 if final_read:
329 # read in all the data we can from pipe and then stop
330 data = []
331 while select.select([pipe], [], [], 0)[0]:
332 data.append(os.read(pipe.fileno(), 1024))
333 if len(data[-1]) == 0:
334 break
335 data = "".join(data)
jadmanski0afbb632008-06-06 21:10:57 +0000336 else:
jadmanski835ae272008-07-24 15:27:56 +0000337 # perform a single read
338 data = os.read(pipe.fileno(), 1024)
jadmanski0afbb632008-06-06 21:10:57 +0000339 fbuffer.write(data)
340 if teefile:
341 teefile.write(data)
342 teefile.flush()
mbligh63073c92008-03-31 16:49:32 +0000343
344
345def system(command, timeout=None, ignore_status=False):
jadmanski0afbb632008-06-06 21:10:57 +0000346 return run(command, timeout, ignore_status,
347 stdout_tee=sys.stdout, stderr_tee=sys.stderr).exit_status
mbligh63073c92008-03-31 16:49:32 +0000348
349
mbligh8ea61e22008-05-09 18:09:37 +0000350def system_output(command, timeout=None, ignore_status=False,
jadmanski0afbb632008-06-06 21:10:57 +0000351 retain_output=False):
352 if retain_output:
353 out = run(command, timeout, ignore_status,
354 stdout_tee=sys.stdout, stderr_tee=sys.stderr).stdout
355 else:
356 out = run(command, timeout, ignore_status).stdout
357 if out[-1:] == '\n': out = out[:-1]
358 return out
mbligh63073c92008-03-31 16:49:32 +0000359
mblighc1cbc992008-05-27 20:01:45 +0000360"""
361This function is used when there is a need to run more than one
362job simultaneously starting exactly at the same time. It basically returns
363a modified control file (containing the synchronization code prepended)
364whenever it is ready to run the control file. The synchronization
365is done using barriers to make sure that the jobs start at the same time.
366
367Here is how the synchronization is done to make sure that the tests
368start at exactly the same time on the client.
369sc_bar is a server barrier and s_bar, c_bar are the normal barriers
370
371 Job1 Job2 ...... JobN
372 Server: | sc_bar
373 Server: | s_bar ...... s_bar
374 Server: | at.run() at.run() ...... at.run()
375 ----------|------------------------------------------------------
376 Client | sc_bar
377 Client | c_bar c_bar ...... c_bar
378 Client | <run test> <run test> ...... <run test>
379
380
381PARAMS:
382 control_file : The control file which to which the above synchronization
383 code would be prepended to
384 host_name : The host name on which the job is going to run
385 host_num (non negative) : A number to identify the machine so that we have
386 different sets of s_bar_ports for each of the machines.
387 instance : The number of the job
388 num_jobs : Total number of jobs that are going to run in parallel with
389 this job starting at the same time
390 port_base : Port number that is used to derive the actual barrier ports.
391
392RETURN VALUE:
393 The modified control file.
394
395"""
396def get_sync_control_file(control, host_name, host_num,
jadmanski0afbb632008-06-06 21:10:57 +0000397 instance, num_jobs, port_base=63100):
398 sc_bar_port = port_base
399 c_bar_port = port_base
400 if host_num < 0:
401 print "Please provide a non negative number for the host"
402 return None
403 s_bar_port = port_base + 1 + host_num # The set of s_bar_ports are
404 # the same for a given machine
mblighc1cbc992008-05-27 20:01:45 +0000405
jadmanski0afbb632008-06-06 21:10:57 +0000406 sc_bar_timeout = 180
407 s_bar_timeout = c_bar_timeout = 120
mblighc1cbc992008-05-27 20:01:45 +0000408
jadmanski0afbb632008-06-06 21:10:57 +0000409 # The barrier code snippet is prepended into the conrol file
410 # dynamically before at.run() is called finally.
411 control_new = []
mblighc1cbc992008-05-27 20:01:45 +0000412
jadmanski0afbb632008-06-06 21:10:57 +0000413 # jobid is the unique name used to identify the processes
414 # trying to reach the barriers
415 jobid = "%s#%d" % (host_name, instance)
mblighc1cbc992008-05-27 20:01:45 +0000416
jadmanski0afbb632008-06-06 21:10:57 +0000417 rendv = []
418 # rendvstr is a temp holder for the rendezvous list of the processes
419 for n in range(num_jobs):
420 rendv.append("'%s#%d'" % (host_name, n))
421 rendvstr = ",".join(rendv)
mblighc1cbc992008-05-27 20:01:45 +0000422
jadmanski0afbb632008-06-06 21:10:57 +0000423 if instance == 0:
424 # Do the setup and wait at the server barrier
425 # Clean up the tmp and the control dirs for the first instance
426 control_new.append('if os.path.exists(job.tmpdir):')
427 control_new.append("\t system('umount -f %s > /dev/null"
428 "2> /dev/null' % job.tmpdir,"
429 "ignore_status=True)")
430 control_new.append("\t system('rm -rf ' + job.tmpdir)")
431 control_new.append(
432 'b0 = job.barrier("%s", "sc_bar", %d, port=%d)'
433 % (jobid, sc_bar_timeout, sc_bar_port))
434 control_new.append(
435 'b0.rendevous_servers("PARALLEL_MASTER", "%s")'
436 % jobid)
mblighc1cbc992008-05-27 20:01:45 +0000437
jadmanski0afbb632008-06-06 21:10:57 +0000438 elif instance == 1:
439 # Wait at the server barrier to wait for instance=0
440 # process to complete setup
441 b0 = barrier.barrier("PARALLEL_MASTER", "sc_bar", sc_bar_timeout,
442 port=sc_bar_port)
443 b0.rendevous_servers("PARALLEL_MASTER", jobid)
mblighc1cbc992008-05-27 20:01:45 +0000444
jadmanski0afbb632008-06-06 21:10:57 +0000445 if(num_jobs > 2):
446 b1 = barrier.barrier(jobid, "s_bar", s_bar_timeout,
447 port=s_bar_port)
448 b1.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000449
jadmanski0afbb632008-06-06 21:10:57 +0000450 else:
451 # For the rest of the clients
452 b2 = barrier.barrier(jobid, "s_bar", s_bar_timeout, port=s_bar_port)
453 b2.rendevous(rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000454
jadmanski0afbb632008-06-06 21:10:57 +0000455 # Client side barrier for all the tests to start at the same time
456 control_new.append('b1 = job.barrier("%s", "c_bar", %d, port=%d)'
457 % (jobid, c_bar_timeout, c_bar_port))
458 control_new.append("b1.rendevous(%s)" % rendvstr)
mblighc1cbc992008-05-27 20:01:45 +0000459
jadmanski0afbb632008-06-06 21:10:57 +0000460 # Stick in the rest of the control file
461 control_new.append(control)
mblighc1cbc992008-05-27 20:01:45 +0000462
jadmanski0afbb632008-06-06 21:10:57 +0000463 return "\n".join(control_new)
mblighc1cbc992008-05-27 20:01:45 +0000464
mbligh63073c92008-03-31 16:49:32 +0000465
466class CmdResult(object):
jadmanski0afbb632008-06-06 21:10:57 +0000467 """
468 Command execution result.
mbligh63073c92008-03-31 16:49:32 +0000469
jadmanski0afbb632008-06-06 21:10:57 +0000470 command: String containing the command line itself
471 exit_status: Integer exit code of the process
472 stdout: String containing stdout of the process
473 stderr: String containing stderr of the process
474 duration: Elapsed wall clock time running the process
475 """
mbligh63073c92008-03-31 16:49:32 +0000476
477
jadmanski0afbb632008-06-06 21:10:57 +0000478 def __init__(self, command=None, stdout="", stderr="",
479 exit_status=None, duration=0):
480 self.command = command
481 self.exit_status = exit_status
482 self.stdout = stdout
483 self.stderr = stderr
484 self.duration = duration
mbligh63073c92008-03-31 16:49:32 +0000485
486
jadmanski0afbb632008-06-06 21:10:57 +0000487 def __repr__(self):
488 wrapper = textwrap.TextWrapper(width = 78,
489 initial_indent="\n ",
490 subsequent_indent=" ")
491
492 stdout = self.stdout.rstrip()
493 if stdout:
494 stdout = "\nstdout:\n%s" % stdout
495
496 stderr = self.stderr.rstrip()
497 if stderr:
498 stderr = "\nstderr:\n%s" % stderr
499
500 return ("* Command: %s\n"
501 "Exit status: %s\n"
502 "Duration: %s\n"
503 "%s"
504 "%s"
505 % (wrapper.fill(self.command), self.exit_status,
506 self.duration, stdout, stderr))
mbligh63073c92008-03-31 16:49:32 +0000507
508
mbligh462c0152008-03-13 15:37:10 +0000509class run_randomly:
jadmanski0afbb632008-06-06 21:10:57 +0000510 def __init__(self, run_sequentially=False):
511 # Run sequentially is for debugging control files
512 self.test_list = []
513 self.run_sequentially = run_sequentially
mbligh462c0152008-03-13 15:37:10 +0000514
515
jadmanski0afbb632008-06-06 21:10:57 +0000516 def add(self, *args, **dargs):
517 test = (args, dargs)
518 self.test_list.append(test)
mbligh462c0152008-03-13 15:37:10 +0000519
520
jadmanski0afbb632008-06-06 21:10:57 +0000521 def run(self, fn):
522 while self.test_list:
523 test_index = random.randint(0, len(self.test_list)-1)
524 if self.run_sequentially:
525 test_index = 0
526 (args, dargs) = self.test_list.pop(test_index)
527 fn(*args, **dargs)