]>
git.proxmox.com Git - mirror_zfs-debian.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) 2012, 2015 by Delphix. All rights reserved.
16 # Copyright (c) 2017 Datto Inc.
22 from datetime
import datetime
23 from optparse
import OptionParser
24 from pwd
import getpwnam
25 from pwd
import getpwuid
26 from select
import select
27 from subprocess
import PIPE
28 from subprocess
import Popen
30 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,
124 self
.pathname
= pathname
125 self
.outputdir
= outputdir
or 'BASEDIR'
126 self
.timeout
= timeout
127 self
.user
= user
or ''
129 self
.result
= Result()
131 if self
.timeout
is None:
135 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nUser: %s\n" % \
136 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.user
)
138 def kill_cmd(self
, proc
):
140 Kill a running command due to timeout, or ^C from the keyboard. If
141 sudo is required, this user was verified previously.
144 do_sudo
= len(self
.user
) != 0
147 cmd
= [SUDO
, KILL
, signal
, str(proc
.pid
)]
157 def update_cmd_privs(self
, cmd
, user
):
159 If a user has been specified to run this Cmd and we're not already
160 running as that user, prepend the appropriate sudo command to run
163 me
= getpwuid(os
.getuid())
165 if not user
or user
is me
:
166 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
168 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
172 if not os
.path
.isfile(cmd
):
173 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
175 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
178 ret
= '%s -E -u %s %s' % (SUDO
, user
, cmd
)
179 return ret
.split(' ')
181 def collect_output(self
, proc
):
183 Read from stdout/stderr as data becomes available, until the
184 process is no longer running. Return the lines from the stdout and
185 stderr Output objects.
187 out
= Output(proc
.stdout
)
188 err
= Output(proc
.stderr
)
190 while proc
.returncode
is None:
192 res
= select([out
, err
], [], [], .1)
198 return out
.lines
, err
.lines
200 def run(self
, options
):
202 This is the main function that runs each individual test.
203 Determine whether or not the command requires sudo, and modify it
204 if needed. Run the command, and update the result object.
206 if options
.dryrun
is True:
210 privcmd
= self
.update_cmd_privs(self
.pathname
, self
.user
)
213 if not os
.path
.isdir(self
.outputdir
):
214 os
.makedirs(self
.outputdir
, mode
=0777)
219 self
.result
.starttime
= time()
220 proc
= Popen(privcmd
, stdout
=PIPE
, stderr
=PIPE
)
221 # Allow a special timeout value of 0 to mean infinity
222 if int(self
.timeout
) == 0:
223 self
.timeout
= maxint
224 t
= Timer(int(self
.timeout
), self
.kill_cmd
, [proc
])
228 self
.result
.stdout
, self
.result
.stderr
= self
.collect_output(proc
)
229 except KeyboardInterrupt:
231 fail('\nRun terminated at user request.')
235 self
.result
.done(proc
, self
.killed
)
239 Initialize enough of the test result that we can log a skipped
243 Result
.runresults
['SKIP'] += 1
244 self
.result
.stdout
= self
.result
.stderr
= []
245 self
.result
.starttime
= time()
246 m
, s
= divmod(time() - self
.result
.starttime
, 60)
247 self
.result
.runtime
= '%02d:%02d' % (m
, s
)
248 self
.result
.result
= 'SKIP'
250 def log(self
, logger
, options
):
252 This function is responsible for writing all output. This includes
253 the console output, the logfile of all results (with timestamped
254 merged stdout and stderr), and for each test, the unmodified
255 stdout/stderr/merged in it's own file.
260 logname
= getpwuid(os
.getuid()).pw_name
261 user
= ' (run as %s)' % (self
.user
if len(self
.user
) else logname
)
262 msga
= 'Test: %s%s ' % (self
.pathname
, user
)
263 msgb
= '[%s] [%s]' % (self
.result
.runtime
, self
.result
.result
)
264 pad
= ' ' * (80 - (len(msga
) + len(msgb
)))
266 # If -q is specified, only print a line for tests that didn't pass.
267 # This means passing tests need to be logged as DEBUG, or the one
268 # line summary will only be printed in the logfile for failures.
269 if not options
.quiet
:
270 logger
.info('%s%s%s' % (msga
, pad
, msgb
))
271 elif self
.result
.result
is not 'PASS':
272 logger
.info('%s%s%s' % (msga
, pad
, msgb
))
274 logger
.debug('%s%s%s' % (msga
, pad
, msgb
))
276 lines
= sorted(self
.result
.stdout
+ self
.result
.stderr
,
277 cmp=lambda x
, y
: cmp(x
[0], y
[0]))
279 for dt
, line
in lines
:
280 logger
.debug('%s %s' % (dt
.strftime("%H:%M:%S.%f ")[:11], line
))
282 if len(self
.result
.stdout
):
283 with
open(os
.path
.join(self
.outputdir
, 'stdout'), 'w') as out
:
284 for _
, line
in self
.result
.stdout
:
285 os
.write(out
.fileno(), '%s\n' % line
)
286 if len(self
.result
.stderr
):
287 with
open(os
.path
.join(self
.outputdir
, 'stderr'), 'w') as err
:
288 for _
, line
in self
.result
.stderr
:
289 os
.write(err
.fileno(), '%s\n' % line
)
290 if len(self
.result
.stdout
) and len(self
.result
.stderr
):
291 with
open(os
.path
.join(self
.outputdir
, 'merged'), 'w') as merged
:
292 for _
, line
in lines
:
293 os
.write(merged
.fileno(), '%s\n' % line
)
297 props
= ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
300 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
301 pre
=None, pre_user
=None, post
=None, post_user
=None,
303 super(Test
, self
).__init
__(pathname
, outputdir
, timeout
, user
)
305 self
.pre_user
= pre_user
or ''
306 self
.post
= post
or ''
307 self
.post_user
= post_user
or ''
308 self
.tags
= tags
or []
311 post_user
= pre_user
= ''
312 if len(self
.pre_user
):
313 pre_user
= ' (as %s)' % (self
.pre_user
)
314 if len(self
.post_user
):
315 post_user
= ' (as %s)' % (self
.post_user
)
316 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \
317 "%s%s\nUser: %s\nTags: %s\n" % \
318 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.pre
,
319 pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
321 def verify(self
, logger
):
323 Check the pre/post scripts, user and Test. Omit the Test from this
324 run if there are any problems.
326 files
= [self
.pre
, self
.pathname
, self
.post
]
327 users
= [self
.pre_user
, self
.user
, self
.post_user
]
329 for f
in [f
for f
in files
if len(f
)]:
330 if not verify_file(f
):
331 logger
.info("Warning: Test '%s' not added to this run because"
332 " it failed verification." % f
)
335 for user
in [user
for user
in users
if len(user
)]:
336 if not verify_user(user
, logger
):
337 logger
.info("Not adding Test '%s' to this run." %
343 def run(self
, logger
, options
):
345 Create Cmd instances for the pre/post scripts. If the pre script
346 doesn't pass, skip this Test. Run the post script regardless.
348 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
349 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
351 test
= Cmd(self
.pathname
, outputdir
=self
.outputdir
,
352 timeout
=self
.timeout
, user
=self
.user
)
353 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
354 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
358 if len(pretest
.pathname
):
360 cont
= pretest
.result
.result
is 'PASS'
361 pretest
.log(logger
, options
)
368 test
.log(logger
, options
)
370 if len(posttest
.pathname
):
371 posttest
.run(options
)
372 posttest
.log(logger
, options
)
375 class TestGroup(Test
):
376 props
= Test
.props
+ ['tests']
378 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
379 pre
=None, pre_user
=None, post
=None, post_user
=None,
380 tests
=None, tags
=None):
381 super(TestGroup
, self
).__init
__(pathname
, outputdir
, timeout
, user
,
382 pre
, pre_user
, post
, post_user
, tags
)
383 self
.tests
= tests
or []
386 post_user
= pre_user
= ''
387 if len(self
.pre_user
):
388 pre_user
= ' (as %s)' % (self
.pre_user
)
389 if len(self
.post_user
):
390 post_user
= ' (as %s)' % (self
.post_user
)
391 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \
392 "Pre: %s%s\nPost: %s%s\nUser: %s\nTags: %s\n" % \
393 (self
.pathname
, self
.outputdir
, self
.tests
, self
.timeout
,
394 self
.pre
, pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
396 def verify(self
, logger
):
398 Check the pre/post scripts, user and tests in this TestGroup. Omit
399 the TestGroup entirely, or simply delete the relevant tests in the
400 group, if that's all that's required.
402 # If the pre or post scripts are relative pathnames, convert to
403 # absolute, so they stand a chance of passing verification.
404 if len(self
.pre
) and not os
.path
.isabs(self
.pre
):
405 self
.pre
= os
.path
.join(self
.pathname
, self
.pre
)
406 if len(self
.post
) and not os
.path
.isabs(self
.post
):
407 self
.post
= os
.path
.join(self
.pathname
, self
.post
)
409 auxfiles
= [self
.pre
, self
.post
]
410 users
= [self
.pre_user
, self
.user
, self
.post_user
]
412 for f
in [f
for f
in auxfiles
if len(f
)]:
413 if self
.pathname
!= os
.path
.dirname(f
):
414 logger
.info("Warning: TestGroup '%s' not added to this run. "
415 "Auxiliary script '%s' exists in a different "
416 "directory." % (self
.pathname
, f
))
419 if not verify_file(f
):
420 logger
.info("Warning: TestGroup '%s' not added to this run. "
421 "Auxiliary script '%s' failed verification." %
425 for user
in [user
for user
in users
if len(user
)]:
426 if not verify_user(user
, logger
):
427 logger
.info("Not adding TestGroup '%s' to this run." %
431 # If one of the tests is invalid, delete it, log it, and drive on.
432 for test
in self
.tests
:
433 if not verify_file(os
.path
.join(self
.pathname
, test
)):
434 del self
.tests
[self
.tests
.index(test
)]
435 logger
.info("Warning: Test '%s' removed from TestGroup '%s' "
436 "because it failed verification." %
437 (test
, self
.pathname
))
439 return len(self
.tests
) is not 0
441 def run(self
, logger
, options
):
443 Create Cmd instances for the pre/post scripts. If the pre script
444 doesn't pass, skip all the tests in this TestGroup. Run the post
447 # tags assigned to this test group also include the test names
448 if options
.tags
and not set(self
.tags
).intersection(set(options
.tags
)):
451 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
452 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
454 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
455 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
459 if len(pretest
.pathname
):
461 cont
= pretest
.result
.result
is 'PASS'
462 pretest
.log(logger
, options
)
464 for fname
in self
.tests
:
465 test
= Cmd(os
.path
.join(self
.pathname
, fname
),
466 outputdir
=os
.path
.join(self
.outputdir
, fname
),
467 timeout
=self
.timeout
, user
=self
.user
)
473 test
.log(logger
, options
)
475 if len(posttest
.pathname
):
476 posttest
.run(options
)
477 posttest
.log(logger
, options
)
480 class TestRun(object):
481 props
= ['quiet', 'outputdir']
483 def __init__(self
, options
):
486 self
.starttime
= time()
487 self
.timestamp
= datetime
.now().strftime('%Y%m%dT%H%M%S')
488 self
.outputdir
= os
.path
.join(options
.outputdir
, self
.timestamp
)
489 self
.logger
= self
.setup_logging(options
)
491 ('outputdir', BASEDIR
),
503 s
= 'TestRun:\n outputdir: %s\n' % self
.outputdir
505 for key
in sorted(self
.tests
.keys()):
506 s
+= '%s%s' % (self
.tests
[key
].__str
__(), '\n')
508 for key
in sorted(self
.testgroups
.keys()):
509 s
+= '%s%s' % (self
.testgroups
[key
].__str
__(), '\n')
512 def addtest(self
, pathname
, options
):
514 Create a new Test, and apply any properties that were passed in
515 from the command line. If it passes verification, add it to the
518 test
= Test(pathname
)
519 for prop
in Test
.props
:
520 setattr(test
, prop
, getattr(options
, prop
))
522 if test
.verify(self
.logger
):
523 self
.tests
[pathname
] = test
525 def addtestgroup(self
, dirname
, filenames
, options
):
527 Create a new TestGroup, and apply any properties that were passed
528 in from the command line. If it passes verification, add it to the
531 if dirname
not in self
.testgroups
:
532 testgroup
= TestGroup(dirname
)
533 for prop
in Test
.props
:
534 setattr(testgroup
, prop
, getattr(options
, prop
))
536 # Prevent pre/post scripts from running as regular tests
537 for f
in [testgroup
.pre
, testgroup
.post
]:
539 del filenames
[filenames
.index(f
)]
541 self
.testgroups
[dirname
] = testgroup
542 self
.testgroups
[dirname
].tests
= sorted(filenames
)
544 testgroup
.verify(self
.logger
)
546 def read(self
, logger
, options
):
548 Read in the specified runfile, and apply the TestRun properties
549 listed in the 'DEFAULT' section to our TestRun. Then read each
550 section, and apply the appropriate properties to the Test or
551 TestGroup. Properties from individual sections override those set
552 in the 'DEFAULT' section. If the Test or TestGroup passes
553 verification, add it to the TestRun.
555 config
= ConfigParser
.RawConfigParser()
556 if not len(config
.read(options
.runfile
)):
557 fail("Coulnd't read config file %s" % options
.runfile
)
559 for opt
in TestRun
.props
:
560 if config
.has_option('DEFAULT', opt
):
561 setattr(self
, opt
, config
.get('DEFAULT', opt
))
562 self
.outputdir
= os
.path
.join(self
.outputdir
, self
.timestamp
)
564 for section
in config
.sections():
565 if 'tests' in config
.options(section
):
566 if os
.path
.isdir(section
):
568 elif os
.path
.isdir(os
.path
.join(options
.testdir
, section
)):
569 pathname
= os
.path
.join(options
.testdir
, section
)
573 testgroup
= TestGroup(os
.path
.abspath(pathname
))
574 for prop
in TestGroup
.props
:
575 for sect
in ['DEFAULT', section
]:
576 if config
.has_option(sect
, prop
):
578 setattr(testgroup
, prop
,
579 eval(config
.get(sect
, prop
)))
581 setattr(testgroup
, prop
,
582 config
.get(sect
, prop
))
584 # Repopulate tests using eval to convert the string to a list
585 testgroup
.tests
= eval(config
.get(section
, 'tests'))
587 if testgroup
.verify(logger
):
588 self
.testgroups
[section
] = testgroup
591 for prop
in Test
.props
:
592 for sect
in ['DEFAULT', section
]:
593 if config
.has_option(sect
, prop
):
594 setattr(test
, prop
, config
.get(sect
, prop
))
596 if test
.verify(logger
):
597 self
.tests
[section
] = test
599 def write(self
, options
):
601 Create a configuration file for editing and later use. The
602 'DEFAULT' section of the config file is created from the
603 properties that were specified on the command line. Tests are
604 simply added as sections that inherit everything from the
605 'DEFAULT' section. TestGroups are the same, except they get an
606 option including all the tests to run in that directory.
609 defaults
= dict([(prop
, getattr(options
, prop
)) for prop
, _
in
611 config
= ConfigParser
.RawConfigParser(defaults
)
613 for test
in sorted(self
.tests
.keys()):
614 config
.add_section(test
)
616 for testgroup
in sorted(self
.testgroups
.keys()):
617 config
.add_section(testgroup
)
618 config
.set(testgroup
, 'tests', self
.testgroups
[testgroup
].tests
)
621 with
open(options
.template
, 'w') as f
:
622 return config
.write(f
)
624 fail('Could not open \'%s\' for writing.' % options
.template
)
626 def complete_outputdirs(self
):
628 Collect all the pathnames for Tests, and TestGroups. Work
629 backwards one pathname component at a time, to create a unique
630 directory name in which to deposit test output. Tests will be able
631 to write output files directly in the newly modified outputdir.
632 TestGroups will be able to create one subdirectory per test in the
633 outputdir, and are guaranteed uniqueness because a group can only
634 contain files in one directory. Pre and post tests will create a
635 directory rooted at the outputdir of the Test or TestGroup in
636 question for their output.
640 tmp_dict
= dict(self
.tests
.items() + self
.testgroups
.items())
641 total
= len(tmp_dict
)
642 base
= self
.outputdir
647 for testfile
in tmp_dict
.keys():
648 uniq
= '/'.join(testfile
.split('/')[components
:]).lstrip('/')
649 if uniq
not in paths
:
651 tmp_dict
[testfile
].outputdir
= os
.path
.join(base
, uniq
)
654 done
= total
== len(paths
)
656 def setup_logging(self
, options
):
658 Two loggers are set up here. The first is for the logfile which
659 will contain one line summarizing the test, including the test
660 name, result, and running time. This logger will also capture the
661 timestamped combined stdout and stderr of each run. The second
662 logger is optional console output, which will contain only the one
663 line summary. The loggers are initialized at two different levels
664 to facilitate segregating the output.
666 if options
.dryrun
is True:
669 testlogger
= logging
.getLogger(__name__
)
670 testlogger
.setLevel(logging
.DEBUG
)
672 if options
.cmd
is not 'wrconfig':
675 os
.makedirs(self
.outputdir
, mode
=0777)
679 filename
= os
.path
.join(self
.outputdir
, 'log')
681 logfile
= logging
.FileHandler(filename
)
682 logfile
.setLevel(logging
.DEBUG
)
683 logfilefmt
= logging
.Formatter('%(message)s')
684 logfile
.setFormatter(logfilefmt
)
685 testlogger
.addHandler(logfile
)
687 cons
= logging
.StreamHandler()
688 cons
.setLevel(logging
.INFO
)
689 consfmt
= logging
.Formatter('%(message)s')
690 cons
.setFormatter(consfmt
)
691 testlogger
.addHandler(cons
)
695 def run(self
, options
):
697 Walk through all the Tests and TestGroups, calling run().
700 os
.chdir(self
.outputdir
)
702 fail('Could not change to directory %s' % self
.outputdir
)
703 # make a symlink to the output for the currently running test
704 logsymlink
= os
.path
.join(self
.outputdir
, '../current')
705 if os
.path
.islink(logsymlink
):
706 os
.unlink(logsymlink
)
707 if not os
.path
.exists(logsymlink
):
708 os
.symlink(self
.outputdir
, logsymlink
)
710 print 'Could not make a symlink to directory %s' % (
713 while iteration
< options
.iterations
:
714 for test
in sorted(self
.tests
.keys()):
715 self
.tests
[test
].run(self
.logger
, options
)
716 for testgroup
in sorted(self
.testgroups
.keys()):
717 self
.testgroups
[testgroup
].run(self
.logger
, options
)
721 if Result
.total
is 0:
724 print '\nResults Summary'
725 for key
in Result
.runresults
.keys():
726 if Result
.runresults
[key
] is not 0:
727 print '%s\t% 4d' % (key
, Result
.runresults
[key
])
729 m
, s
= divmod(time() - self
.starttime
, 60)
731 print '\nRunning Time:\t%02d:%02d:%02d' % (h
, m
, s
)
732 print 'Percent passed:\t%.1f%%' % ((float(Result
.runresults
['PASS']) /
733 float(Result
.total
)) * 100)
734 print 'Log directory:\t%s' % self
.outputdir
736 if Result
.runresults
['FAIL'] > 0:
739 if Result
.runresults
['KILLED'] > 0:
745 def verify_file(pathname
):
747 Verify that the supplied pathname is an executable regular file.
749 if os
.path
.isdir(pathname
) or os
.path
.islink(pathname
):
752 for ext
in '', '.ksh', '.sh':
753 script_path
= pathname
+ ext
754 if os
.path
.isfile(script_path
) and os
.access(script_path
, os
.X_OK
):
760 def verify_user(user
, logger
):
762 Verify that the specified user exists on this system, and can execute
763 sudo without being prompted for a password.
765 testcmd
= [SUDO
, '-n', '-u', user
, TRUE
]
767 if user
in Cmd
.verified_users
:
773 logger
.info("Warning: user '%s' does not exist.", user
)
778 if p
.returncode
is not 0:
779 logger
.info("Warning: user '%s' cannot use passwordless sudo.", user
)
782 Cmd
.verified_users
.append(user
)
787 def find_tests(testrun
, options
):
789 For the given list of pathnames, add files as Tests. For directories,
790 if do_groups is True, add the directory as a TestGroup. If False,
791 recursively search for executable files.
794 for p
in sorted(options
.pathnames
):
796 for dirname
, _
, filenames
in os
.walk(p
):
797 if options
.do_groups
:
798 testrun
.addtestgroup(dirname
, filenames
, options
)
800 for f
in sorted(filenames
):
801 testrun
.addtest(os
.path
.join(dirname
, f
), options
)
803 testrun
.addtest(p
, options
)
806 def fail(retstr
, ret
=1):
807 print '%s: %s' % (argv
[0], retstr
)
811 def options_cb(option
, opt_str
, value
, parser
):
812 path_options
= ['runfile', 'outputdir', 'template', 'testdir']
814 if option
.dest
is 'runfile' and '-w' in parser
.rargs
or \
815 option
.dest
is 'template' and '-c' in parser
.rargs
:
816 fail('-c and -w are mutually exclusive.')
818 if opt_str
in parser
.rargs
:
819 fail('%s may only be specified once.' % opt_str
)
821 if option
.dest
is 'runfile':
822 parser
.values
.cmd
= 'rdconfig'
823 if option
.dest
is 'template':
824 parser
.values
.cmd
= 'wrconfig'
825 if option
.dest
is 'tags':
826 value
= [x
.strip() for x
in value
.split(',')]
828 setattr(parser
.values
, option
.dest
, value
)
829 if option
.dest
in path_options
:
830 setattr(parser
.values
, option
.dest
, os
.path
.abspath(value
))
834 parser
= OptionParser()
835 parser
.add_option('-c', action
='callback', callback
=options_cb
,
836 type='string', dest
='runfile', metavar
='runfile',
837 help='Specify tests to run via config file.')
838 parser
.add_option('-d', action
='store_true', default
=False, dest
='dryrun',
839 help='Dry run. Print tests, but take no other action.')
840 parser
.add_option('-g', action
='store_true', default
=False,
841 dest
='do_groups', help='Make directories TestGroups.')
842 parser
.add_option('-o', action
='callback', callback
=options_cb
,
843 default
=BASEDIR
, dest
='outputdir', type='string',
844 metavar
='outputdir', help='Specify an output directory.')
845 parser
.add_option('-i', action
='callback', callback
=options_cb
,
846 default
=TESTDIR
, dest
='testdir', type='string',
847 metavar
='testdir', help='Specify a test directory.')
848 parser
.add_option('-p', action
='callback', callback
=options_cb
,
849 default
='', dest
='pre', metavar
='script',
850 type='string', help='Specify a pre script.')
851 parser
.add_option('-P', action
='callback', callback
=options_cb
,
852 default
='', dest
='post', metavar
='script',
853 type='string', help='Specify a post script.')
854 parser
.add_option('-q', action
='store_true', default
=False, dest
='quiet',
855 help='Silence on the console during a test run.')
856 parser
.add_option('-t', action
='callback', callback
=options_cb
, default
=60,
857 dest
='timeout', metavar
='seconds', type='int',
858 help='Timeout (in seconds) for an individual test.')
859 parser
.add_option('-u', action
='callback', callback
=options_cb
,
860 default
='', dest
='user', metavar
='user', type='string',
861 help='Specify a different user name to run as.')
862 parser
.add_option('-w', action
='callback', callback
=options_cb
,
863 default
=None, dest
='template', metavar
='template',
864 type='string', help='Create a new config file.')
865 parser
.add_option('-x', action
='callback', callback
=options_cb
, default
='',
866 dest
='pre_user', metavar
='pre_user', type='string',
867 help='Specify a user to execute the pre script.')
868 parser
.add_option('-X', action
='callback', callback
=options_cb
, default
='',
869 dest
='post_user', metavar
='post_user', type='string',
870 help='Specify a user to execute the post script.')
871 parser
.add_option('-T', action
='callback', callback
=options_cb
, default
='',
872 dest
='tags', metavar
='tags', type='string',
873 help='Specify tags to execute specific test groups.')
874 parser
.add_option('-I', action
='callback', callback
=options_cb
, default
=1,
875 dest
='iterations', metavar
='iterations', type='int',
876 help='Number of times to run the test run.')
877 (options
, pathnames
) = parser
.parse_args()
879 if not options
.runfile
and not options
.template
:
880 options
.cmd
= 'runtests'
882 if options
.runfile
and len(pathnames
):
883 fail('Extraneous arguments.')
885 options
.pathnames
= [os
.path
.abspath(path
) for path
in pathnames
]
891 options
= parse_args()
892 testrun
= TestRun(options
)
894 if options
.cmd
is 'runtests':
895 find_tests(testrun
, options
)
896 elif options
.cmd
is 'rdconfig':
897 testrun
.read(testrun
.logger
, options
)
898 elif options
.cmd
is 'wrconfig':
899 find_tests(testrun
, options
)
900 testrun
.write(options
)
903 fail('Unknown command specified')
905 testrun
.complete_outputdirs()
907 exit(testrun
.summary())
910 if __name__
== '__main__':