]>
git.proxmox.com Git - mirror_zfs.git/blob - tests/test-runner/cmd/test-runner.py
4 # This file and its contents are supplied under the terms of the
5 # Common Development and Distribution License ("CDDL"), version 1.0.
6 # You may only use this file in accordance with the terms of version
9 # A full copy of the text of the CDDL should have accompanied this
10 # source. A copy of the CDDL is also available via the Internet at
11 # http://www.illumos.org/license/CDDL.
15 # Copyright (c) 2013 by Delphix. All rights reserved.
21 from datetime
import datetime
22 from optparse
import OptionParser
23 from pwd
import getpwnam
24 from pwd
import getpwuid
25 from select
import select
26 from subprocess
import PIPE
27 from subprocess
import Popen
29 from sys
import maxint
31 from threading
import Timer
34 BASEDIR
= '/var/tmp/test_results'
35 TESTDIR
= '/usr/share/zfs/'
43 runresults
= {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0}
47 self
.returncode
= None
53 def done(self
, proc
, killed
):
55 Finalize the results of this Cmd.
58 m
, s
= divmod(time() - self
.starttime
, 60)
59 self
.runtime
= '%02d:%02d' % (m
, s
)
60 self
.returncode
= proc
.returncode
62 self
.result
= 'KILLED'
63 Result
.runresults
['KILLED'] += 1
64 elif self
.returncode
is 0:
66 Result
.runresults
['PASS'] += 1
67 elif self
.returncode
is 4:
69 Result
.runresults
['SKIP'] += 1
70 elif self
.returncode
is not 0:
72 Result
.runresults
['FAIL'] += 1
77 This class is a slightly modified version of the 'Stream' class found
78 here: http://goo.gl/aSGfv
80 def __init__(self
, stream
):
86 return self
.stream
.fileno()
88 def read(self
, drain
=0):
90 Read from the file descriptor. If 'drain' set, read until EOF.
92 while self
._read
() is not None:
98 Read up to 4k of data from this output stream. Collect the output
99 up to the last newline, and append it to any leftover data from a
100 previous call. The lines are stored as a (timestamp, data) tuple
101 for easy sorting/merging later.
104 buf
= os
.read(fd
, 4096)
111 buf
= self
._buf
+ buf
112 tmp
, rest
= buf
.rsplit('\n', 1)
115 rows
= tmp
.split('\n')
116 self
.lines
+= [(now
, r
) for r
in rows
]
122 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None):
123 self
.pathname
= pathname
124 self
.outputdir
= outputdir
or 'BASEDIR'
125 self
.timeout
= timeout
126 self
.user
= user
or ''
128 self
.result
= Result()
130 if self
.timeout
is None:
134 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nUser: %s\n" % (
135 self
.pathname
, self
.outputdir
, self
.timeout
, self
.user
)
137 def kill_cmd(self
, proc
):
139 Kill a running command due to timeout, or ^C from the keyboard. If
140 sudo is required, this user was verified previously.
143 do_sudo
= len(self
.user
) != 0
146 cmd
= [SUDO
, KILL
, signal
, str(proc
.pid
)]
156 def update_cmd_privs(self
, cmd
, user
):
158 If a user has been specified to run this Cmd and we're not already
159 running as that user, prepend the appropriate sudo command to run
162 me
= getpwuid(os
.getuid())
164 if not user
or user
is me
:
165 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
167 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
171 if not os
.path
.isfile(cmd
):
172 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
174 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
177 ret
= '%s -E -u %s %s' % (SUDO
, user
, cmd
)
178 return ret
.split(' ')
180 def collect_output(self
, proc
):
182 Read from stdout/stderr as data becomes available, until the
183 process is no longer running. Return the lines from the stdout and
184 stderr Output objects.
186 out
= Output(proc
.stdout
)
187 err
= Output(proc
.stderr
)
189 while proc
.returncode
is None:
191 res
= select([out
, err
], [], [], .1)
197 return out
.lines
, err
.lines
199 def run(self
, options
):
201 This is the main function that runs each individual test.
202 Determine whether or not the command requires sudo, and modify it
203 if needed. Run the command, and update the result object.
205 if options
.dryrun
is True:
209 privcmd
= self
.update_cmd_privs(self
.pathname
, self
.user
)
212 if not os
.path
.isdir(self
.outputdir
):
213 os
.makedirs(self
.outputdir
, mode
=0777)
218 self
.result
.starttime
= time()
219 proc
= Popen(privcmd
, stdout
=PIPE
, stderr
=PIPE
)
220 # Allow a special timeout value of 0 to mean infinity
221 if int(self
.timeout
) == 0:
222 self
.timeout
= maxint
223 t
= Timer(int(self
.timeout
), self
.kill_cmd
, [proc
])
227 self
.result
.stdout
, self
.result
.stderr
= self
.collect_output(proc
)
228 except KeyboardInterrupt:
230 fail('\nRun terminated at user request.')
234 self
.result
.done(proc
, self
.killed
)
238 Initialize enough of the test result that we can log a skipped
242 Result
.runresults
['SKIP'] += 1
243 self
.result
.stdout
= self
.result
.stderr
= []
244 self
.result
.starttime
= time()
245 m
, s
= divmod(time() - self
.result
.starttime
, 60)
246 self
.result
.runtime
= '%02d:%02d' % (m
, s
)
247 self
.result
.result
= 'SKIP'
249 def log(self
, logger
, options
):
251 This function is responsible for writing all output. This includes
252 the console output, the logfile of all results (with timestamped
253 merged stdout and stderr), and for each test, the unmodified
254 stdout/stderr/merged in it's own file.
259 logname
= getpwuid(os
.getuid()).pw_name
260 user
= ' (run as %s)' % (self
.user
if len(self
.user
) else logname
)
261 msga
= 'Test: %s%s ' % (self
.pathname
, user
)
262 msgb
= '[%s] [%s]' % (self
.result
.runtime
, self
.result
.result
)
263 pad
= ' ' * (80 - (len(msga
) + len(msgb
)))
265 # If -q is specified, only print a line for tests that didn't pass.
266 # This means passing tests need to be logged as DEBUG, or the one
267 # line summary will only be printed in the logfile for failures.
268 if not options
.quiet
:
269 logger
.info('%s%s%s' % (msga
, pad
, msgb
))
270 elif self
.result
.result
is not 'PASS':
271 logger
.info('%s%s%s' % (msga
, pad
, msgb
))
273 logger
.debug('%s%s%s' % (msga
, pad
, msgb
))
275 lines
= self
.result
.stdout
+ self
.result
.stderr
276 for dt
, line
in sorted(lines
):
277 logger
.debug('%s %s' % (dt
.strftime("%H:%M:%S.%f ")[:11], line
))
279 if len(self
.result
.stdout
):
280 with
open(os
.path
.join(self
.outputdir
, 'stdout'), 'w') as out
:
281 for _
, line
in self
.result
.stdout
:
282 os
.write(out
.fileno(), '%s\n' % line
)
283 if len(self
.result
.stderr
):
284 with
open(os
.path
.join(self
.outputdir
, 'stderr'), 'w') as err
:
285 for _
, line
in self
.result
.stderr
:
286 os
.write(err
.fileno(), '%s\n' % line
)
287 if len(self
.result
.stdout
) and len(self
.result
.stderr
):
288 with
open(os
.path
.join(self
.outputdir
, 'merged'), 'w') as merged
:
289 for _
, line
in sorted(lines
):
290 os
.write(merged
.fileno(), '%s\n' % line
)
294 props
= ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
297 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
298 pre
=None, pre_user
=None, post
=None, post_user
=None):
299 super(Test
, self
).__init
__(pathname
, outputdir
, timeout
, user
)
301 self
.pre_user
= pre_user
or ''
302 self
.post
= post
or ''
303 self
.post_user
= post_user
or ''
306 post_user
= pre_user
= ''
307 if len(self
.pre_user
):
308 pre_user
= ' (as %s)' % (self
.pre_user
)
309 if len(self
.post_user
):
310 post_user
= ' (as %s)' % (self
.post_user
)
311 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \
312 "%s%s\nUser: %s\n" % (
313 self
.pathname
, self
.outputdir
,
314 self
.timeout
, self
.pre
, pre_user
, self
.post
, post_user
,
317 def verify(self
, logger
):
319 Check the pre/post scripts, user and Test. Omit the Test from this
320 run if there are any problems.
322 files
= [self
.pre
, self
.pathname
, self
.post
]
323 users
= [self
.pre_user
, self
.user
, self
.post_user
]
325 for f
in [f
for f
in files
if len(f
)]:
326 if not verify_file(f
):
327 logger
.info("Warning: Test '%s' not added to this run because"
328 " it failed verification." % f
)
331 for user
in [user
for user
in users
if len(user
)]:
332 if not verify_user(user
, logger
):
333 logger
.info("Not adding Test '%s' to this run." %
339 def run(self
, logger
, options
):
341 Create Cmd instances for the pre/post scripts. If the pre script
342 doesn't pass, skip this Test. Run the post script regardless.
344 pretest
= Cmd(self
.pre
, outputdir
=os
.path
.join(self
.outputdir
,
345 os
.path
.basename(self
.pre
)), timeout
=self
.timeout
,
347 test
= Cmd(self
.pathname
, outputdir
=self
.outputdir
,
348 timeout
=self
.timeout
, user
=self
.user
)
349 posttest
= Cmd(self
.post
, outputdir
=os
.path
.join(self
.outputdir
,
350 os
.path
.basename(self
.post
)), timeout
=self
.timeout
,
354 if len(pretest
.pathname
):
356 cont
= pretest
.result
.result
is 'PASS'
357 pretest
.log(logger
, options
)
364 test
.log(logger
, options
)
366 if len(posttest
.pathname
):
367 posttest
.run(options
)
368 posttest
.log(logger
, options
)
371 class TestGroup(Test
):
372 props
= Test
.props
+ ['tests']
374 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
375 pre
=None, pre_user
=None, post
=None, post_user
=None,
377 super(TestGroup
, self
).__init
__(pathname
, outputdir
, timeout
, user
,
378 pre
, pre_user
, post
, post_user
)
379 self
.tests
= tests
or []
382 post_user
= pre_user
= ''
383 if len(self
.pre_user
):
384 pre_user
= ' (as %s)' % (self
.pre_user
)
385 if len(self
.post_user
):
386 post_user
= ' (as %s)' % (self
.post_user
)
387 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %d\n" \
388 "Pre: %s%s\nPost: %s%s\nUser: %s\n" % (
389 self
.pathname
, self
.outputdir
, self
.tests
, self
.timeout
,
390 self
.pre
, pre_user
, self
.post
, post_user
, self
.user
)
392 def verify(self
, logger
):
394 Check the pre/post scripts, user and tests in this TestGroup. Omit
395 the TestGroup entirely, or simply delete the relevant tests in the
396 group, if that's all that's required.
398 # If the pre or post scripts are relative pathnames, convert to
399 # absolute, so they stand a chance of passing verification.
400 if len(self
.pre
) and not os
.path
.isabs(self
.pre
):
401 self
.pre
= os
.path
.join(self
.pathname
, self
.pre
)
402 if len(self
.post
) and not os
.path
.isabs(self
.post
):
403 self
.post
= os
.path
.join(self
.pathname
, self
.post
)
405 auxfiles
= [self
.pre
, self
.post
]
406 users
= [self
.pre_user
, self
.user
, self
.post_user
]
408 for f
in [f
for f
in auxfiles
if len(f
)]:
409 if self
.pathname
!= os
.path
.dirname(f
):
410 logger
.info("Warning: TestGroup '%s' not added to this run. "
411 "Auxiliary script '%s' exists in a different "
412 "directory." % (self
.pathname
, f
))
415 if not verify_file(f
):
416 logger
.info("Warning: TestGroup '%s' not added to this run. "
417 "Auxiliary script '%s' failed verification." %
421 for user
in [user
for user
in users
if len(user
)]:
422 if not verify_user(user
, logger
):
423 logger
.info("Not adding TestGroup '%s' to this run." %
427 # If one of the tests is invalid, delete it, log it, and drive on.
428 for test
in self
.tests
:
429 if not verify_file(os
.path
.join(self
.pathname
, test
)):
430 del self
.tests
[self
.tests
.index(test
)]
431 logger
.info("Warning: Test '%s' removed from TestGroup '%s' "
432 "because it failed verification." %
433 (test
, self
.pathname
))
435 return len(self
.tests
) is not 0
437 def run(self
, logger
, options
):
439 Create Cmd instances for the pre/post scripts. If the pre script
440 doesn't pass, skip all the tests in this TestGroup. Run the post
443 pretest
= Cmd(self
.pre
, outputdir
=os
.path
.join(self
.outputdir
,
444 os
.path
.basename(self
.pre
)), timeout
=self
.timeout
,
446 posttest
= Cmd(self
.post
, outputdir
=os
.path
.join(self
.outputdir
,
447 os
.path
.basename(self
.post
)), timeout
=self
.timeout
,
451 if len(pretest
.pathname
):
453 cont
= pretest
.result
.result
is 'PASS'
454 pretest
.log(logger
, options
)
456 for fname
in self
.tests
:
457 test
= Cmd(os
.path
.join(self
.pathname
, fname
),
458 outputdir
=os
.path
.join(self
.outputdir
, fname
),
459 timeout
=self
.timeout
, user
=self
.user
)
465 test
.log(logger
, options
)
467 if len(posttest
.pathname
):
468 posttest
.run(options
)
469 posttest
.log(logger
, options
)
472 class TestRun(object):
473 props
= ['quiet', 'outputdir']
475 def __init__(self
, options
):
478 self
.starttime
= time()
479 self
.timestamp
= datetime
.now().strftime('%Y%m%dT%H%M%S')
480 self
.outputdir
= os
.path
.join(options
.outputdir
, self
.timestamp
)
481 self
.logger
= self
.setup_logging(options
)
483 ('outputdir', BASEDIR
),
494 s
= 'TestRun:\n outputdir: %s\n' % self
.outputdir
496 for key
in sorted(self
.tests
.keys()):
497 s
+= '%s%s' % (self
.tests
[key
].__str
__(), '\n')
499 for key
in sorted(self
.testgroups
.keys()):
500 s
+= '%s%s' % (self
.testgroups
[key
].__str
__(), '\n')
503 def addtest(self
, pathname
, options
):
505 Create a new Test, and apply any properties that were passed in
506 from the command line. If it passes verification, add it to the
509 test
= Test(pathname
)
510 for prop
in Test
.props
:
511 setattr(test
, prop
, getattr(options
, prop
))
513 if test
.verify(self
.logger
):
514 self
.tests
[pathname
] = test
516 def addtestgroup(self
, dirname
, filenames
, options
):
518 Create a new TestGroup, and apply any properties that were passed
519 in from the command line. If it passes verification, add it to the
522 if dirname
not in self
.testgroups
:
523 testgroup
= TestGroup(dirname
)
524 for prop
in Test
.props
:
525 setattr(testgroup
, prop
, getattr(options
, prop
))
527 # Prevent pre/post scripts from running as regular tests
528 for f
in [testgroup
.pre
, testgroup
.post
]:
530 del filenames
[filenames
.index(f
)]
532 self
.testgroups
[dirname
] = testgroup
533 self
.testgroups
[dirname
].tests
= sorted(filenames
)
535 testgroup
.verify(self
.logger
)
537 def read(self
, logger
, options
):
539 Read in the specified runfile, and apply the TestRun properties
540 listed in the 'DEFAULT' section to our TestRun. Then read each
541 section, and apply the appropriate properties to the Test or
542 TestGroup. Properties from individual sections override those set
543 in the 'DEFAULT' section. If the Test or TestGroup passes
544 verification, add it to the TestRun.
546 config
= ConfigParser
.RawConfigParser()
547 if not len(config
.read(options
.runfile
)):
548 fail("Coulnd't read config file %s" % options
.runfile
)
550 for opt
in TestRun
.props
:
551 if config
.has_option('DEFAULT', opt
):
552 setattr(self
, opt
, config
.get('DEFAULT', opt
))
553 self
.outputdir
= os
.path
.join(self
.outputdir
, self
.timestamp
)
555 for section
in config
.sections():
556 if 'tests' in config
.options(section
):
557 if os
.path
.isdir(section
):
559 elif os
.path
.isdir(os
.path
.join(options
.testdir
, section
)):
560 pathname
= os
.path
.join(options
.testdir
, section
)
564 testgroup
= TestGroup(os
.path
.abspath(pathname
))
565 for prop
in TestGroup
.props
:
567 setattr(testgroup
, prop
, config
.get('DEFAULT', prop
))
568 setattr(testgroup
, prop
, config
.get(section
, prop
))
569 except ConfigParser
.NoOptionError
:
572 # Repopulate tests using eval to convert the string to a list
573 testgroup
.tests
= eval(config
.get(section
, 'tests'))
575 if testgroup
.verify(logger
):
576 self
.testgroups
[section
] = testgroup
579 for prop
in Test
.props
:
581 setattr(test
, prop
, config
.get('DEFAULT', prop
))
582 setattr(test
, prop
, config
.get(section
, prop
))
583 except ConfigParser
.NoOptionError
:
585 if test
.verify(logger
):
586 self
.tests
[section
] = test
588 def write(self
, options
):
590 Create a configuration file for editing and later use. The
591 'DEFAULT' section of the config file is created from the
592 properties that were specified on the command line. Tests are
593 simply added as sections that inherit everything from the
594 'DEFAULT' section. TestGroups are the same, except they get an
595 option including all the tests to run in that directory.
598 defaults
= dict([(prop
, getattr(options
, prop
)) for prop
, _
in
600 config
= ConfigParser
.RawConfigParser(defaults
)
602 for test
in sorted(self
.tests
.keys()):
603 config
.add_section(test
)
605 for testgroup
in sorted(self
.testgroups
.keys()):
606 config
.add_section(testgroup
)
607 config
.set(testgroup
, 'tests', self
.testgroups
[testgroup
].tests
)
610 with
open(options
.template
, 'w') as f
:
611 return config
.write(f
)
613 fail('Could not open \'%s\' for writing.' % options
.template
)
615 def complete_outputdirs(self
, options
):
617 Collect all the pathnames for Tests, and TestGroups. Work
618 backwards one pathname component at a time, to create a unique
619 directory name in which to deposit test output. Tests will be able
620 to write output files directly in the newly modified outputdir.
621 TestGroups will be able to create one subdirectory per test in the
622 outputdir, and are guaranteed uniqueness because a group can only
623 contain files in one directory. Pre and post tests will create a
624 directory rooted at the outputdir of the Test or TestGroup in
625 question for their output.
629 tmp_dict
= dict(self
.tests
.items() + self
.testgroups
.items())
630 total
= len(tmp_dict
)
631 base
= self
.outputdir
636 for testfile
in tmp_dict
.keys():
637 uniq
= '/'.join(testfile
.split('/')[components
:]).lstrip('/')
640 tmp_dict
[testfile
].outputdir
= os
.path
.join(base
, uniq
)
643 done
= total
== len(l
)
645 def setup_logging(self
, options
):
647 Two loggers are set up here. The first is for the logfile which
648 will contain one line summarizing the test, including the test
649 name, result, and running time. This logger will also capture the
650 timestamped combined stdout and stderr of each run. The second
651 logger is optional console output, which will contain only the one
652 line summary. The loggers are initialized at two different levels
653 to facilitate segregating the output.
655 if options
.dryrun
is True:
658 testlogger
= logging
.getLogger(__name__
)
659 testlogger
.setLevel(logging
.DEBUG
)
661 if options
.cmd
is not 'wrconfig':
664 os
.makedirs(self
.outputdir
, mode
=0777)
668 filename
= os
.path
.join(self
.outputdir
, 'log')
670 logfile
= logging
.FileHandler(filename
)
671 logfile
.setLevel(logging
.DEBUG
)
672 logfilefmt
= logging
.Formatter('%(message)s')
673 logfile
.setFormatter(logfilefmt
)
674 testlogger
.addHandler(logfile
)
676 cons
= logging
.StreamHandler()
677 cons
.setLevel(logging
.INFO
)
678 consfmt
= logging
.Formatter('%(message)s')
679 cons
.setFormatter(consfmt
)
680 testlogger
.addHandler(cons
)
684 def run(self
, options
):
686 Walk through all the Tests and TestGroups, calling run().
689 os
.chdir(self
.outputdir
)
691 fail('Could not change to directory %s' % self
.outputdir
)
692 # make a symlink to the output for the currently running test
693 logsymlink
= os
.path
.join(self
.outputdir
, '../current')
694 if os
.path
.islink(logsymlink
):
695 os
.unlink(logsymlink
)
696 if not os
.path
.exists(logsymlink
):
697 os
.symlink(self
.outputdir
, logsymlink
)
699 print 'Could not make a symlink to directory %s' % (
701 for test
in sorted(self
.tests
.keys()):
702 self
.tests
[test
].run(self
.logger
, options
)
703 for testgroup
in sorted(self
.testgroups
.keys()):
704 self
.testgroups
[testgroup
].run(self
.logger
, options
)
707 if Result
.total
is 0:
710 print '\nResults Summary'
711 for key
in Result
.runresults
.keys():
712 if Result
.runresults
[key
] is not 0:
713 print '%s\t% 4d' % (key
, Result
.runresults
[key
])
715 m
, s
= divmod(time() - self
.starttime
, 60)
717 print '\nRunning Time:\t%02d:%02d:%02d' % (h
, m
, s
)
718 print 'Percent passed:\t%.1f%%' % (
719 (float(Result
.runresults
['PASS']) / float(Result
.total
)) * 100)
720 print 'Log directory:\t%s' % self
.outputdir
723 def verify_file(pathname
):
725 Verify that the supplied pathname is an executable regular file.
727 if os
.path
.isdir(pathname
) or os
.path
.islink(pathname
):
730 for ext
in '', '.ksh', '.sh':
731 script_path
= pathname
+ ext
732 if os
.path
.isfile(script_path
) and os
.access(script_path
, os
.X_OK
):
738 def verify_user(user
, logger
):
740 Verify that the specified user exists on this system, and can execute
741 sudo without being prompted for a password.
743 testcmd
= [SUDO
, '-n', '-u', user
, TRUE
]
745 if user
in Cmd
.verified_users
:
751 logger
.info("Warning: user '%s' does not exist.", user
)
756 if p
.returncode
is not 0:
757 logger
.info("Warning: user '%s' cannot use passwordless sudo.", user
)
760 Cmd
.verified_users
.append(user
)
765 def find_tests(testrun
, options
):
767 For the given list of pathnames, add files as Tests. For directories,
768 if do_groups is True, add the directory as a TestGroup. If False,
769 recursively search for executable files.
772 for p
in sorted(options
.pathnames
):
774 for dirname
, _
, filenames
in os
.walk(p
):
775 if options
.do_groups
:
776 testrun
.addtestgroup(dirname
, filenames
, options
)
778 for f
in sorted(filenames
):
779 testrun
.addtest(os
.path
.join(dirname
, f
), options
)
781 testrun
.addtest(p
, options
)
784 def fail(retstr
, ret
=1):
785 print '%s: %s' % (argv
[0], retstr
)
789 def options_cb(option
, opt_str
, value
, parser
):
790 path_options
= ['runfile', 'outputdir', 'template', 'testdir']
792 if option
.dest
is 'runfile' and '-w' in parser
.rargs
or \
793 option
.dest
is 'template' and '-c' in parser
.rargs
:
794 fail('-c and -w are mutually exclusive.')
796 if opt_str
in parser
.rargs
:
797 fail('%s may only be specified once.' % opt_str
)
799 if option
.dest
is 'runfile':
800 parser
.values
.cmd
= 'rdconfig'
801 if option
.dest
is 'template':
802 parser
.values
.cmd
= 'wrconfig'
804 setattr(parser
.values
, option
.dest
, value
)
805 if option
.dest
in path_options
:
806 setattr(parser
.values
, option
.dest
, os
.path
.abspath(value
))
810 parser
= OptionParser()
811 parser
.add_option('-c', action
='callback', callback
=options_cb
,
812 type='string', dest
='runfile', metavar
='runfile',
813 help='Specify tests to run via config file.')
814 parser
.add_option('-d', action
='store_true', default
=False, dest
='dryrun',
815 help='Dry run. Print tests, but take no other action.')
816 parser
.add_option('-g', action
='store_true', default
=False,
817 dest
='do_groups', help='Make directories TestGroups.')
818 parser
.add_option('-o', action
='callback', callback
=options_cb
,
819 default
=BASEDIR
, dest
='outputdir', type='string',
820 metavar
='outputdir', help='Specify an output directory.')
821 parser
.add_option('-i', action
='callback', callback
=options_cb
,
822 default
=TESTDIR
, dest
='testdir', type='string',
823 metavar
='testdir', help='Specify a test directory.')
824 parser
.add_option('-p', action
='callback', callback
=options_cb
,
825 default
='', dest
='pre', metavar
='script',
826 type='string', help='Specify a pre script.')
827 parser
.add_option('-P', action
='callback', callback
=options_cb
,
828 default
='', dest
='post', metavar
='script',
829 type='string', help='Specify a post script.')
830 parser
.add_option('-q', action
='store_true', default
=False, dest
='quiet',
831 help='Silence on the console during a test run.')
832 parser
.add_option('-t', action
='callback', callback
=options_cb
, default
=60,
833 dest
='timeout', metavar
='seconds', type='int',
834 help='Timeout (in seconds) for an individual test.')
835 parser
.add_option('-u', action
='callback', callback
=options_cb
,
836 default
='', dest
='user', metavar
='user', type='string',
837 help='Specify a different user name to run as.')
838 parser
.add_option('-w', action
='callback', callback
=options_cb
,
839 default
=None, dest
='template', metavar
='template',
840 type='string', help='Create a new config file.')
841 parser
.add_option('-x', action
='callback', callback
=options_cb
, default
='',
842 dest
='pre_user', metavar
='pre_user', type='string',
843 help='Specify a user to execute the pre script.')
844 parser
.add_option('-X', action
='callback', callback
=options_cb
, default
='',
845 dest
='post_user', metavar
='post_user', type='string',
846 help='Specify a user to execute the post script.')
847 (options
, pathnames
) = parser
.parse_args()
849 if not options
.runfile
and not options
.template
:
850 options
.cmd
= 'runtests'
852 if options
.runfile
and len(pathnames
):
853 fail('Extraneous arguments.')
855 options
.pathnames
= [os
.path
.abspath(path
) for path
in pathnames
]
861 options
= parse_args()
862 testrun
= TestRun(options
)
864 if options
.cmd
is 'runtests':
865 find_tests(testrun
, options
)
866 elif options
.cmd
is 'rdconfig':
867 testrun
.read(testrun
.logger
, options
)
868 elif options
.cmd
is 'wrconfig':
869 find_tests(testrun
, options
)
870 testrun
.write(options
)
873 fail('Unknown command specified')
875 testrun
.complete_outputdirs(options
)
881 if __name__
== '__main__':