]>
git.proxmox.com Git - mirror_zfs.git/blob - tests/test-runner/cmd/test-runner.py
5f493ef7edf0309d5287f51acea9ec19c6c3cbf9
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, 2018 by Delphix. All rights reserved.
16 # Copyright (c) 2017 Datto Inc.
19 # some python 2.7 system don't have a configparser shim
23 import ConfigParser
as configparser
28 from datetime
import datetime
29 from optparse
import OptionParser
30 from pwd
import getpwnam
31 from pwd
import getpwuid
32 from select
import select
33 from subprocess
import PIPE
34 from subprocess
import Popen
35 from threading
import Timer
38 BASEDIR
= '/var/tmp/test_results'
39 TESTDIR
= '/usr/share/zfs/'
51 runresults
= {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0}
55 self
.returncode
= None
61 def done(self
, proc
, killed
):
63 Finalize the results of this Cmd.
66 m
, s
= divmod(time() - self
.starttime
, 60)
67 self
.runtime
= '%02d:%02d' % (m
, s
)
68 self
.returncode
= proc
.returncode
70 self
.result
= 'KILLED'
71 Result
.runresults
['KILLED'] += 1
72 elif self
.returncode
is 0:
74 Result
.runresults
['PASS'] += 1
75 elif self
.returncode
is 4:
77 Result
.runresults
['SKIP'] += 1
78 elif self
.returncode
is not 0:
80 Result
.runresults
['FAIL'] += 1
85 This class is a slightly modified version of the 'Stream' class found
86 here: http://goo.gl/aSGfv
88 def __init__(self
, stream
):
94 return self
.stream
.fileno()
96 def read(self
, drain
=0):
98 Read from the file descriptor. If 'drain' set, read until EOF.
100 while self
._read
() is not None:
106 Read up to 4k of data from this output stream. Collect the output
107 up to the last newline, and append it to any leftover data from a
108 previous call. The lines are stored as a (timestamp, data) tuple
109 for easy sorting/merging later.
112 buf
= os
.read(fd
, 4096)
119 buf
= self
._buf
+ buf
120 tmp
, rest
= buf
.rsplit(b
'\n', 1)
123 rows
= tmp
.split(b
'\n')
124 self
.lines
+= [(now
, r
) for r
in rows
]
130 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
132 self
.pathname
= pathname
133 self
.outputdir
= outputdir
or 'BASEDIR'
134 self
.timeout
= timeout
135 self
.user
= user
or ''
137 self
.result
= Result()
139 if self
.timeout
is None:
143 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nUser: %s\n" % \
144 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.user
)
146 def kill_cmd(self
, proc
):
148 Kill a running command due to timeout, or ^C from the keyboard. If
149 sudo is required, this user was verified previously.
152 do_sudo
= len(self
.user
) != 0
155 cmd
= [SUDO
, KILL
, signal
, str(proc
.pid
)]
165 def update_cmd_privs(self
, cmd
, user
):
167 If a user has been specified to run this Cmd and we're not already
168 running as that user, prepend the appropriate sudo command to run
171 me
= getpwuid(os
.getuid())
173 if not user
or user
is me
:
174 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
176 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
180 if not os
.path
.isfile(cmd
):
181 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
183 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
186 ret
= '%s -E -u %s %s' % (SUDO
, user
, cmd
)
187 return ret
.split(' ')
189 def collect_output(self
, proc
):
191 Read from stdout/stderr as data becomes available, until the
192 process is no longer running. Return the lines from the stdout and
193 stderr Output objects.
195 out
= Output(proc
.stdout
)
196 err
= Output(proc
.stderr
)
198 while proc
.returncode
is None:
200 res
= select([out
, err
], [], [], .1)
206 return out
.lines
, err
.lines
208 def run(self
, options
):
210 This is the main function that runs each individual test.
211 Determine whether or not the command requires sudo, and modify it
212 if needed. Run the command, and update the result object.
214 if options
.dryrun
is True:
218 privcmd
= self
.update_cmd_privs(self
.pathname
, self
.user
)
221 if not os
.path
.isdir(self
.outputdir
):
222 os
.makedirs(self
.outputdir
, mode
=0o777)
227 self
.result
.starttime
= time()
228 proc
= Popen(privcmd
, stdout
=PIPE
, stderr
=PIPE
)
229 # Allow a special timeout value of 0 to mean infinity
230 if int(self
.timeout
) == 0:
231 self
.timeout
= sys
.maxsize
232 t
= Timer(int(self
.timeout
), self
.kill_cmd
, [proc
])
236 self
.result
.stdout
, self
.result
.stderr
= self
.collect_output(proc
)
237 except KeyboardInterrupt:
239 fail('\nRun terminated at user request.')
243 self
.result
.done(proc
, self
.killed
)
247 Initialize enough of the test result that we can log a skipped
251 Result
.runresults
['SKIP'] += 1
252 self
.result
.stdout
= self
.result
.stderr
= []
253 self
.result
.starttime
= time()
254 m
, s
= divmod(time() - self
.result
.starttime
, 60)
255 self
.result
.runtime
= '%02d:%02d' % (m
, s
)
256 self
.result
.result
= 'SKIP'
258 def log(self
, options
):
260 This function is responsible for writing all output. This includes
261 the console output, the logfile of all results (with timestamped
262 merged stdout and stderr), and for each test, the unmodified
263 stdout/stderr/merged in it's own file.
266 logname
= getpwuid(os
.getuid()).pw_name
267 user
= ' (run as %s)' % (self
.user
if len(self
.user
) else logname
)
268 msga
= 'Test: %s%s ' % (self
.pathname
, user
)
269 msgb
= '[%s] [%s]\n' % (self
.result
.runtime
, self
.result
.result
)
270 pad
= ' ' * (80 - (len(msga
) + len(msgb
)))
271 result_line
= msga
+ pad
+ msgb
273 # The result line is always written to the log file. If -q was
274 # specified only failures are written to the console, otherwise
275 # the result line is written to the console.
276 write_log(bytearray(result_line
, encoding
='utf-8'), LOG_FILE
)
277 if not options
.quiet
:
278 write_log(result_line
, LOG_OUT
)
279 elif options
.quiet
and self
.result
.result
is not 'PASS':
280 write_log(result_line
, LOG_OUT
)
282 lines
= sorted(self
.result
.stdout
+ self
.result
.stderr
,
285 # Write timestamped output (stdout and stderr) to the logfile
286 for dt
, line
in lines
:
287 timestamp
= bytearray(dt
.strftime("%H:%M:%S.%f ")[:11],
289 write_log(b
'%s %s\n' % (timestamp
, line
), LOG_FILE
)
291 # Write the separate stdout/stderr/merged files, if the data exists
292 if len(self
.result
.stdout
):
293 with
open(os
.path
.join(self
.outputdir
, 'stdout'), 'wb') as out
:
294 for _
, line
in self
.result
.stdout
:
295 os
.write(out
.fileno(), b
'%s\n' % line
)
296 if len(self
.result
.stderr
):
297 with
open(os
.path
.join(self
.outputdir
, 'stderr'), 'wb') as err
:
298 for _
, line
in self
.result
.stderr
:
299 os
.write(err
.fileno(), b
'%s\n' % line
)
300 if len(self
.result
.stdout
) and len(self
.result
.stderr
):
301 with
open(os
.path
.join(self
.outputdir
, 'merged'), 'wb') as merged
:
302 for _
, line
in lines
:
303 os
.write(merged
.fileno(), b
'%s\n' % line
)
307 props
= ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
310 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
311 pre
=None, pre_user
=None, post
=None, post_user
=None,
313 super(Test
, self
).__init
__(pathname
, outputdir
, timeout
, user
)
315 self
.pre_user
= pre_user
or ''
316 self
.post
= post
or ''
317 self
.post_user
= post_user
or ''
318 self
.tags
= tags
or []
321 post_user
= pre_user
= ''
322 if len(self
.pre_user
):
323 pre_user
= ' (as %s)' % (self
.pre_user
)
324 if len(self
.post_user
):
325 post_user
= ' (as %s)' % (self
.post_user
)
326 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \
327 "%s%s\nUser: %s\nTags: %s\n" % \
328 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.pre
,
329 pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
333 Check the pre/post scripts, user and Test. Omit the Test from this
334 run if there are any problems.
336 files
= [self
.pre
, self
.pathname
, self
.post
]
337 users
= [self
.pre_user
, self
.user
, self
.post_user
]
339 for f
in [f
for f
in files
if len(f
)]:
340 if not verify_file(f
):
341 write_log("Warning: Test '%s' not added to this run because"
342 " it failed verification.\n" % f
, LOG_ERR
)
345 for user
in [user
for user
in users
if len(user
)]:
346 if not verify_user(user
):
347 write_log("Not adding Test '%s' to this run.\n" %
348 self
.pathname
, LOG_ERR
)
353 def run(self
, options
):
355 Create Cmd instances for the pre/post scripts. If the pre script
356 doesn't pass, skip this Test. Run the post script regardless.
358 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
359 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
361 test
= Cmd(self
.pathname
, outputdir
=self
.outputdir
,
362 timeout
=self
.timeout
, user
=self
.user
)
363 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
364 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
368 if len(pretest
.pathname
):
370 cont
= pretest
.result
.result
is 'PASS'
380 if len(posttest
.pathname
):
381 posttest
.run(options
)
382 posttest
.log(options
)
385 class TestGroup(Test
):
386 props
= Test
.props
+ ['tests']
388 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
389 pre
=None, pre_user
=None, post
=None, post_user
=None,
390 tests
=None, tags
=None):
391 super(TestGroup
, self
).__init
__(pathname
, outputdir
, timeout
, user
,
392 pre
, pre_user
, post
, post_user
, tags
)
393 self
.tests
= tests
or []
396 post_user
= pre_user
= ''
397 if len(self
.pre_user
):
398 pre_user
= ' (as %s)' % (self
.pre_user
)
399 if len(self
.post_user
):
400 post_user
= ' (as %s)' % (self
.post_user
)
401 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \
402 "Pre: %s%s\nPost: %s%s\nUser: %s\nTags: %s\n" % \
403 (self
.pathname
, self
.outputdir
, self
.tests
, self
.timeout
,
404 self
.pre
, pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
408 Check the pre/post scripts, user and tests in this TestGroup. Omit
409 the TestGroup entirely, or simply delete the relevant tests in the
410 group, if that's all that's required.
412 # If the pre or post scripts are relative pathnames, convert to
413 # absolute, so they stand a chance of passing verification.
414 if len(self
.pre
) and not os
.path
.isabs(self
.pre
):
415 self
.pre
= os
.path
.join(self
.pathname
, self
.pre
)
416 if len(self
.post
) and not os
.path
.isabs(self
.post
):
417 self
.post
= os
.path
.join(self
.pathname
, self
.post
)
419 auxfiles
= [self
.pre
, self
.post
]
420 users
= [self
.pre_user
, self
.user
, self
.post_user
]
422 for f
in [f
for f
in auxfiles
if len(f
)]:
423 if self
.pathname
!= os
.path
.dirname(f
):
424 write_log("Warning: TestGroup '%s' not added to this run. "
425 "Auxiliary script '%s' exists in a different "
426 "directory.\n" % (self
.pathname
, f
), LOG_ERR
)
429 if not verify_file(f
):
430 write_log("Warning: TestGroup '%s' not added to this run. "
431 "Auxiliary script '%s' failed verification.\n" %
432 (self
.pathname
, f
), LOG_ERR
)
435 for user
in [user
for user
in users
if len(user
)]:
436 if not verify_user(user
):
437 write_log("Not adding TestGroup '%s' to this run.\n" %
438 self
.pathname
, LOG_ERR
)
441 # If one of the tests is invalid, delete it, log it, and drive on.
442 for test
in self
.tests
:
443 if not verify_file(os
.path
.join(self
.pathname
, test
)):
444 del self
.tests
[self
.tests
.index(test
)]
445 write_log("Warning: Test '%s' removed from TestGroup '%s' "
446 "because it failed verification.\n" %
447 (test
, self
.pathname
), LOG_ERR
)
449 return len(self
.tests
) is not 0
451 def run(self
, options
):
453 Create Cmd instances for the pre/post scripts. If the pre script
454 doesn't pass, skip all the tests in this TestGroup. Run the post
457 # tags assigned to this test group also include the test names
458 if options
.tags
and not set(self
.tags
).intersection(set(options
.tags
)):
461 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
462 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
464 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
465 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
469 if len(pretest
.pathname
):
471 cont
= pretest
.result
.result
is 'PASS'
474 for fname
in self
.tests
:
475 test
= Cmd(os
.path
.join(self
.pathname
, fname
),
476 outputdir
=os
.path
.join(self
.outputdir
, fname
),
477 timeout
=self
.timeout
, user
=self
.user
)
485 if len(posttest
.pathname
):
486 posttest
.run(options
)
487 posttest
.log(options
)
490 class TestRun(object):
491 props
= ['quiet', 'outputdir']
493 def __init__(self
, options
):
496 self
.starttime
= time()
497 self
.timestamp
= datetime
.now().strftime('%Y%m%dT%H%M%S')
498 self
.outputdir
= os
.path
.join(options
.outputdir
, self
.timestamp
)
499 self
.setup_logging(options
)
501 ('outputdir', BASEDIR
),
513 s
= 'TestRun:\n outputdir: %s\n' % self
.outputdir
515 for key
in sorted(self
.tests
.keys()):
516 s
+= '%s%s' % (self
.tests
[key
].__str
__(), '\n')
518 for key
in sorted(self
.testgroups
.keys()):
519 s
+= '%s%s' % (self
.testgroups
[key
].__str
__(), '\n')
522 def addtest(self
, pathname
, options
):
524 Create a new Test, and apply any properties that were passed in
525 from the command line. If it passes verification, add it to the
528 test
= Test(pathname
)
529 for prop
in Test
.props
:
530 setattr(test
, prop
, getattr(options
, prop
))
533 self
.tests
[pathname
] = test
535 def addtestgroup(self
, dirname
, filenames
, options
):
537 Create a new TestGroup, and apply any properties that were passed
538 in from the command line. If it passes verification, add it to the
541 if dirname
not in self
.testgroups
:
542 testgroup
= TestGroup(dirname
)
543 for prop
in Test
.props
:
544 setattr(testgroup
, prop
, getattr(options
, prop
))
546 # Prevent pre/post scripts from running as regular tests
547 for f
in [testgroup
.pre
, testgroup
.post
]:
549 del filenames
[filenames
.index(f
)]
551 self
.testgroups
[dirname
] = testgroup
552 self
.testgroups
[dirname
].tests
= sorted(filenames
)
556 def read(self
, options
):
558 Read in the specified runfile, and apply the TestRun properties
559 listed in the 'DEFAULT' section to our TestRun. Then read each
560 section, and apply the appropriate properties to the Test or
561 TestGroup. Properties from individual sections override those set
562 in the 'DEFAULT' section. If the Test or TestGroup passes
563 verification, add it to the TestRun.
565 config
= configparser
.RawConfigParser()
566 if not len(config
.read(options
.runfile
)):
567 fail("Coulnd't read config file %s" % options
.runfile
)
569 for opt
in TestRun
.props
:
570 if config
.has_option('DEFAULT', opt
):
571 setattr(self
, opt
, config
.get('DEFAULT', opt
))
572 self
.outputdir
= os
.path
.join(self
.outputdir
, self
.timestamp
)
574 for section
in config
.sections():
575 if 'tests' in config
.options(section
):
576 if os
.path
.isdir(section
):
578 elif os
.path
.isdir(os
.path
.join(options
.testdir
, section
)):
579 pathname
= os
.path
.join(options
.testdir
, section
)
583 testgroup
= TestGroup(os
.path
.abspath(pathname
))
584 for prop
in TestGroup
.props
:
585 for sect
in ['DEFAULT', section
]:
586 if config
.has_option(sect
, prop
):
588 setattr(testgroup
, prop
,
589 eval(config
.get(sect
, prop
)))
591 setattr(testgroup
, prop
,
592 config
.get(sect
, prop
))
594 # Repopulate tests using eval to convert the string to a list
595 testgroup
.tests
= eval(config
.get(section
, 'tests'))
597 if testgroup
.verify():
598 self
.testgroups
[section
] = testgroup
601 for prop
in Test
.props
:
602 for sect
in ['DEFAULT', section
]:
603 if config
.has_option(sect
, prop
):
604 setattr(test
, prop
, config
.get(sect
, prop
))
607 self
.tests
[section
] = test
609 def write(self
, options
):
611 Create a configuration file for editing and later use. The
612 'DEFAULT' section of the config file is created from the
613 properties that were specified on the command line. Tests are
614 simply added as sections that inherit everything from the
615 'DEFAULT' section. TestGroups are the same, except they get an
616 option including all the tests to run in that directory.
619 defaults
= dict([(prop
, getattr(options
, prop
)) for prop
, _
in
621 config
= configparser
.RawConfigParser(defaults
)
623 for test
in sorted(self
.tests
.keys()):
624 config
.add_section(test
)
626 for testgroup
in sorted(self
.testgroups
.keys()):
627 config
.add_section(testgroup
)
628 config
.set(testgroup
, 'tests', self
.testgroups
[testgroup
].tests
)
631 with
open(options
.template
, 'w') as f
:
632 return config
.write(f
)
634 fail('Could not open \'%s\' for writing.' % options
.template
)
636 def complete_outputdirs(self
):
638 Collect all the pathnames for Tests, and TestGroups. Work
639 backwards one pathname component at a time, to create a unique
640 directory name in which to deposit test output. Tests will be able
641 to write output files directly in the newly modified outputdir.
642 TestGroups will be able to create one subdirectory per test in the
643 outputdir, and are guaranteed uniqueness because a group can only
644 contain files in one directory. Pre and post tests will create a
645 directory rooted at the outputdir of the Test or TestGroup in
646 question for their output.
650 tmp_dict
= dict(list(self
.tests
.items()) +
651 list(self
.testgroups
.items()))
652 total
= len(tmp_dict
)
653 base
= self
.outputdir
658 for testfile
in list(tmp_dict
.keys()):
659 uniq
= '/'.join(testfile
.split('/')[components
:]).lstrip('/')
660 if uniq
not in paths
:
662 tmp_dict
[testfile
].outputdir
= os
.path
.join(base
, uniq
)
665 done
= total
== len(paths
)
667 def setup_logging(self
, options
):
669 This funtion creates the output directory and gets a file object
670 for the logfile. This function must be called before write_log()
673 if options
.dryrun
is True:
677 if options
.cmd
is not 'wrconfig':
680 os
.makedirs(self
.outputdir
, mode
=0o777)
682 filename
= os
.path
.join(self
.outputdir
, 'log')
683 LOG_FILE_OBJ
= open(filename
, buffering
=0, mode
='wb')
687 def run(self
, options
):
689 Walk through all the Tests and TestGroups, calling run().
692 os
.chdir(self
.outputdir
)
694 fail('Could not change to directory %s' % self
.outputdir
)
695 # make a symlink to the output for the currently running test
696 logsymlink
= os
.path
.join(self
.outputdir
, '../current')
697 if os
.path
.islink(logsymlink
):
698 os
.unlink(logsymlink
)
699 if not os
.path
.exists(logsymlink
):
700 os
.symlink(self
.outputdir
, logsymlink
)
702 write_log('Could not make a symlink to directory %s\n' %
703 self
.outputdir
, LOG_ERR
)
705 while iteration
< options
.iterations
:
706 for test
in sorted(self
.tests
.keys()):
707 self
.tests
[test
].run(options
)
708 for testgroup
in sorted(self
.testgroups
.keys()):
709 self
.testgroups
[testgroup
].run(options
)
713 if Result
.total
is 0:
716 print('\nResults Summary')
717 for key
in list(Result
.runresults
.keys()):
718 if Result
.runresults
[key
] is not 0:
719 print('%s\t% 4d' % (key
, Result
.runresults
[key
]))
721 m
, s
= divmod(time() - self
.starttime
, 60)
723 print('\nRunning Time:\t%02d:%02d:%02d' % (h
, m
, s
))
724 print('Percent passed:\t%.1f%%' % ((float(Result
.runresults
['PASS']) /
725 float(Result
.total
)) * 100))
726 print('Log directory:\t%s' % self
.outputdir
)
728 if Result
.runresults
['FAIL'] > 0:
731 if Result
.runresults
['KILLED'] > 0:
737 def write_log(msg
, target
):
739 Write the provided message to standard out, standard error or
740 the logfile. If specifying LOG_FILE, then `msg` must be a bytes
741 like object. This way we can still handle output from tests that
742 may be in unexpected encodings.
744 if target
== LOG_OUT
:
745 os
.write(sys
.stdout
.fileno(), bytearray(msg
, encoding
='utf-8'))
746 elif target
== LOG_ERR
:
747 os
.write(sys
.stderr
.fileno(), bytearray(msg
, encoding
='utf-8'))
748 elif target
== LOG_FILE
:
749 os
.write(LOG_FILE_OBJ
.fileno(), msg
)
751 fail('log_msg called with unknown target "%s"' % target
)
754 def verify_file(pathname
):
756 Verify that the supplied pathname is an executable regular file.
758 if os
.path
.isdir(pathname
) or os
.path
.islink(pathname
):
761 for ext
in '', '.ksh', '.sh':
762 script_path
= pathname
+ ext
763 if os
.path
.isfile(script_path
) and os
.access(script_path
, os
.X_OK
):
769 def verify_user(user
):
771 Verify that the specified user exists on this system, and can execute
772 sudo without being prompted for a password.
774 testcmd
= [SUDO
, '-n', '-u', user
, TRUE
]
776 if user
in Cmd
.verified_users
:
782 write_log("Warning: user '%s' does not exist.\n" % user
,
788 if p
.returncode
is not 0:
789 write_log("Warning: user '%s' cannot use passwordless sudo.\n" % user
,
793 Cmd
.verified_users
.append(user
)
798 def find_tests(testrun
, options
):
800 For the given list of pathnames, add files as Tests. For directories,
801 if do_groups is True, add the directory as a TestGroup. If False,
802 recursively search for executable files.
805 for p
in sorted(options
.pathnames
):
807 for dirname
, _
, filenames
in os
.walk(p
):
808 if options
.do_groups
:
809 testrun
.addtestgroup(dirname
, filenames
, options
)
811 for f
in sorted(filenames
):
812 testrun
.addtest(os
.path
.join(dirname
, f
), options
)
814 testrun
.addtest(p
, options
)
817 def fail(retstr
, ret
=1):
818 print('%s: %s' % (sys
.argv
[0], retstr
))
822 def options_cb(option
, opt_str
, value
, parser
):
823 path_options
= ['runfile', 'outputdir', 'template', 'testdir']
825 if option
.dest
is 'runfile' and '-w' in parser
.rargs
or \
826 option
.dest
is 'template' and '-c' in parser
.rargs
:
827 fail('-c and -w are mutually exclusive.')
829 if opt_str
in parser
.rargs
:
830 fail('%s may only be specified once.' % opt_str
)
832 if option
.dest
is 'runfile':
833 parser
.values
.cmd
= 'rdconfig'
834 if option
.dest
is 'template':
835 parser
.values
.cmd
= 'wrconfig'
836 if option
.dest
is 'tags':
837 value
= [x
.strip() for x
in value
.split(',')]
839 setattr(parser
.values
, option
.dest
, value
)
840 if option
.dest
in path_options
:
841 setattr(parser
.values
, option
.dest
, os
.path
.abspath(value
))
845 parser
= OptionParser()
846 parser
.add_option('-c', action
='callback', callback
=options_cb
,
847 type='string', dest
='runfile', metavar
='runfile',
848 help='Specify tests to run via config file.')
849 parser
.add_option('-d', action
='store_true', default
=False, dest
='dryrun',
850 help='Dry run. Print tests, but take no other action.')
851 parser
.add_option('-g', action
='store_true', default
=False,
852 dest
='do_groups', help='Make directories TestGroups.')
853 parser
.add_option('-o', action
='callback', callback
=options_cb
,
854 default
=BASEDIR
, dest
='outputdir', type='string',
855 metavar
='outputdir', help='Specify an output directory.')
856 parser
.add_option('-i', action
='callback', callback
=options_cb
,
857 default
=TESTDIR
, dest
='testdir', type='string',
858 metavar
='testdir', help='Specify a test directory.')
859 parser
.add_option('-p', action
='callback', callback
=options_cb
,
860 default
='', dest
='pre', metavar
='script',
861 type='string', help='Specify a pre script.')
862 parser
.add_option('-P', action
='callback', callback
=options_cb
,
863 default
='', dest
='post', metavar
='script',
864 type='string', help='Specify a post script.')
865 parser
.add_option('-q', action
='store_true', default
=False, dest
='quiet',
866 help='Silence on the console during a test run.')
867 parser
.add_option('-t', action
='callback', callback
=options_cb
, default
=60,
868 dest
='timeout', metavar
='seconds', type='int',
869 help='Timeout (in seconds) for an individual test.')
870 parser
.add_option('-u', action
='callback', callback
=options_cb
,
871 default
='', dest
='user', metavar
='user', type='string',
872 help='Specify a different user name to run as.')
873 parser
.add_option('-w', action
='callback', callback
=options_cb
,
874 default
=None, dest
='template', metavar
='template',
875 type='string', help='Create a new config file.')
876 parser
.add_option('-x', action
='callback', callback
=options_cb
, default
='',
877 dest
='pre_user', metavar
='pre_user', type='string',
878 help='Specify a user to execute the pre script.')
879 parser
.add_option('-X', action
='callback', callback
=options_cb
, default
='',
880 dest
='post_user', metavar
='post_user', type='string',
881 help='Specify a user to execute the post script.')
882 parser
.add_option('-T', action
='callback', callback
=options_cb
, default
='',
883 dest
='tags', metavar
='tags', type='string',
884 help='Specify tags to execute specific test groups.')
885 parser
.add_option('-I', action
='callback', callback
=options_cb
, default
=1,
886 dest
='iterations', metavar
='iterations', type='int',
887 help='Number of times to run the test run.')
888 (options
, pathnames
) = parser
.parse_args()
890 if not options
.runfile
and not options
.template
:
891 options
.cmd
= 'runtests'
893 if options
.runfile
and len(pathnames
):
894 fail('Extraneous arguments.')
896 options
.pathnames
= [os
.path
.abspath(path
) for path
in pathnames
]
902 options
= parse_args()
903 testrun
= TestRun(options
)
905 if options
.cmd
is 'runtests':
906 find_tests(testrun
, options
)
907 elif options
.cmd
is 'rdconfig':
908 testrun
.read(options
)
909 elif options
.cmd
is 'wrconfig':
910 find_tests(testrun
, options
)
911 testrun
.write(options
)
914 fail('Unknown command specified')
916 testrun
.complete_outputdirs()
918 exit(testrun
.summary())
921 if __name__
== '__main__':