]>
git.proxmox.com Git - mirror_zfs.git/blob - tests/test-runner/bin/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, 2018 by Delphix. All rights reserved.
16 # Copyright (c) 2017 Datto Inc.
18 # This script must remain compatible with Python 2.6+ and Python 3.4+.
21 # some python 2.7 system don't have a configparser shim
25 import ConfigParser
as configparser
30 from datetime
import datetime
31 from optparse
import OptionParser
32 from pwd
import getpwnam
33 from pwd
import getpwuid
34 from select
import select
35 from subprocess
import PIPE
36 from subprocess
import Popen
37 from threading
import Timer
40 BASEDIR
= '/var/tmp/test_results'
41 TESTDIR
= '/usr/share/zfs/'
53 runresults
= {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0}
57 self
.returncode
= None
63 def done(self
, proc
, killed
):
65 Finalize the results of this Cmd.
68 m
, s
= divmod(time() - self
.starttime
, 60)
69 self
.runtime
= '%02d:%02d' % (m
, s
)
70 self
.returncode
= proc
.returncode
72 self
.result
= 'KILLED'
73 Result
.runresults
['KILLED'] += 1
74 elif self
.returncode
== 0:
76 Result
.runresults
['PASS'] += 1
77 elif self
.returncode
== 4:
79 Result
.runresults
['SKIP'] += 1
80 elif self
.returncode
!= 0:
82 Result
.runresults
['FAIL'] += 1
87 This class is a slightly modified version of the 'Stream' class found
88 here: http://goo.gl/aSGfv
90 def __init__(self
, stream
):
96 return self
.stream
.fileno()
98 def read(self
, drain
=0):
100 Read from the file descriptor. If 'drain' set, read until EOF.
102 while self
._read
() is not None:
108 Read up to 4k of data from this output stream. Collect the output
109 up to the last newline, and append it to any leftover data from a
110 previous call. The lines are stored as a (timestamp, data) tuple
111 for easy sorting/merging later.
114 buf
= os
.read(fd
, 4096)
121 buf
= self
._buf
+ buf
122 tmp
, rest
= buf
.rsplit(b
'\n', 1)
125 rows
= tmp
.split(b
'\n')
126 self
.lines
+= [(now
, r
) for r
in rows
]
132 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
134 self
.pathname
= pathname
135 self
.outputdir
= outputdir
or 'BASEDIR'
136 self
.timeout
= timeout
137 self
.user
= user
or ''
139 self
.result
= Result()
141 if self
.timeout
is None:
145 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nUser: %s\n" % \
146 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.user
)
148 def kill_cmd(self
, proc
):
150 Kill a running command due to timeout, or ^C from the keyboard. If
151 sudo is required, this user was verified previously.
154 do_sudo
= len(self
.user
) != 0
157 cmd
= [SUDO
, KILL
, signal
, str(proc
.pid
)]
167 def update_cmd_privs(self
, cmd
, user
):
169 If a user has been specified to run this Cmd and we're not already
170 running as that user, prepend the appropriate sudo command to run
173 me
= getpwuid(os
.getuid())
175 if not user
or user
is me
:
176 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
178 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
182 if not os
.path
.isfile(cmd
):
183 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
185 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
188 ret
= '%s -E -u %s %s' % (SUDO
, user
, cmd
)
189 return ret
.split(' ')
191 def collect_output(self
, proc
):
193 Read from stdout/stderr as data becomes available, until the
194 process is no longer running. Return the lines from the stdout and
195 stderr Output objects.
197 out
= Output(proc
.stdout
)
198 err
= Output(proc
.stderr
)
200 while proc
.returncode
is None:
202 res
= select([out
, err
], [], [], .1)
208 return out
.lines
, err
.lines
210 def run(self
, options
):
212 This is the main function that runs each individual test.
213 Determine whether or not the command requires sudo, and modify it
214 if needed. Run the command, and update the result object.
216 if options
.dryrun
is True:
220 privcmd
= self
.update_cmd_privs(self
.pathname
, self
.user
)
223 if not os
.path
.isdir(self
.outputdir
):
224 os
.makedirs(self
.outputdir
, mode
=0o777)
229 self
.result
.starttime
= time()
230 proc
= Popen(privcmd
, stdout
=PIPE
, stderr
=PIPE
)
231 # Allow a special timeout value of 0 to mean infinity
232 if int(self
.timeout
) == 0:
233 self
.timeout
= sys
.maxsize
234 t
= Timer(int(self
.timeout
), self
.kill_cmd
, [proc
])
238 self
.result
.stdout
, self
.result
.stderr
= self
.collect_output(proc
)
239 except KeyboardInterrupt:
241 fail('\nRun terminated at user request.')
245 self
.result
.done(proc
, self
.killed
)
249 Initialize enough of the test result that we can log a skipped
253 Result
.runresults
['SKIP'] += 1
254 self
.result
.stdout
= self
.result
.stderr
= []
255 self
.result
.starttime
= time()
256 m
, s
= divmod(time() - self
.result
.starttime
, 60)
257 self
.result
.runtime
= '%02d:%02d' % (m
, s
)
258 self
.result
.result
= 'SKIP'
260 def log(self
, options
):
262 This function is responsible for writing all output. This includes
263 the console output, the logfile of all results (with timestamped
264 merged stdout and stderr), and for each test, the unmodified
265 stdout/stderr/merged in it's own file.
268 logname
= getpwuid(os
.getuid()).pw_name
269 user
= ' (run as %s)' % (self
.user
if len(self
.user
) else logname
)
270 msga
= 'Test: %s%s ' % (self
.pathname
, user
)
271 msgb
= '[%s] [%s]\n' % (self
.result
.runtime
, self
.result
.result
)
272 pad
= ' ' * (80 - (len(msga
) + len(msgb
)))
273 result_line
= msga
+ pad
+ msgb
275 # The result line is always written to the log file. If -q was
276 # specified only failures are written to the console, otherwise
277 # the result line is written to the console.
278 write_log(bytearray(result_line
, encoding
='utf-8'), LOG_FILE
)
279 if not options
.quiet
:
280 write_log(result_line
, LOG_OUT
)
281 elif options
.quiet
and self
.result
.result
!= 'PASS':
282 write_log(result_line
, LOG_OUT
)
284 lines
= sorted(self
.result
.stdout
+ self
.result
.stderr
,
287 # Write timestamped output (stdout and stderr) to the logfile
288 for dt
, line
in lines
:
289 timestamp
= bytearray(dt
.strftime("%H:%M:%S.%f ")[:11],
291 write_log(b
'%s %s\n' % (timestamp
, line
), LOG_FILE
)
293 # Write the separate stdout/stderr/merged files, if the data exists
294 if len(self
.result
.stdout
):
295 with
open(os
.path
.join(self
.outputdir
, 'stdout'), 'wb') as out
:
296 for _
, line
in self
.result
.stdout
:
297 os
.write(out
.fileno(), b
'%s\n' % line
)
298 if len(self
.result
.stderr
):
299 with
open(os
.path
.join(self
.outputdir
, 'stderr'), 'wb') as err
:
300 for _
, line
in self
.result
.stderr
:
301 os
.write(err
.fileno(), b
'%s\n' % line
)
302 if len(self
.result
.stdout
) and len(self
.result
.stderr
):
303 with
open(os
.path
.join(self
.outputdir
, 'merged'), 'wb') as merged
:
304 for _
, line
in lines
:
305 os
.write(merged
.fileno(), b
'%s\n' % line
)
309 props
= ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
312 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
313 pre
=None, pre_user
=None, post
=None, post_user
=None,
315 super(Test
, self
).__init
__(pathname
, outputdir
, timeout
, user
)
317 self
.pre_user
= pre_user
or ''
318 self
.post
= post
or ''
319 self
.post_user
= post_user
or ''
320 self
.tags
= tags
or []
323 post_user
= pre_user
= ''
324 if len(self
.pre_user
):
325 pre_user
= ' (as %s)' % (self
.pre_user
)
326 if len(self
.post_user
):
327 post_user
= ' (as %s)' % (self
.post_user
)
328 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \
329 "%s%s\nUser: %s\nTags: %s\n" % \
330 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.pre
,
331 pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
335 Check the pre/post scripts, user and Test. Omit the Test from this
336 run if there are any problems.
338 files
= [self
.pre
, self
.pathname
, self
.post
]
339 users
= [self
.pre_user
, self
.user
, self
.post_user
]
341 for f
in [f
for f
in files
if len(f
)]:
342 if not verify_file(f
):
343 write_log("Warning: Test '%s' not added to this run because"
344 " it failed verification.\n" % f
, LOG_ERR
)
347 for user
in [user
for user
in users
if len(user
)]:
348 if not verify_user(user
):
349 write_log("Not adding Test '%s' to this run.\n" %
350 self
.pathname
, LOG_ERR
)
355 def run(self
, options
):
357 Create Cmd instances for the pre/post scripts. If the pre script
358 doesn't pass, skip this Test. Run the post script regardless.
360 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
361 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
363 test
= Cmd(self
.pathname
, outputdir
=self
.outputdir
,
364 timeout
=self
.timeout
, user
=self
.user
)
365 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
366 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
370 if len(pretest
.pathname
):
372 cont
= pretest
.result
.result
== 'PASS'
382 if len(posttest
.pathname
):
383 posttest
.run(options
)
384 posttest
.log(options
)
387 class TestGroup(Test
):
388 props
= Test
.props
+ ['tests']
390 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
391 pre
=None, pre_user
=None, post
=None, post_user
=None,
392 tests
=None, tags
=None):
393 super(TestGroup
, self
).__init
__(pathname
, outputdir
, timeout
, user
,
394 pre
, pre_user
, post
, post_user
, tags
)
395 self
.tests
= tests
or []
398 post_user
= pre_user
= ''
399 if len(self
.pre_user
):
400 pre_user
= ' (as %s)' % (self
.pre_user
)
401 if len(self
.post_user
):
402 post_user
= ' (as %s)' % (self
.post_user
)
403 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \
404 "Pre: %s%s\nPost: %s%s\nUser: %s\nTags: %s\n" % \
405 (self
.pathname
, self
.outputdir
, self
.tests
, self
.timeout
,
406 self
.pre
, pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
410 Check the pre/post scripts, user and tests in this TestGroup. Omit
411 the TestGroup entirely, or simply delete the relevant tests in the
412 group, if that's all that's required.
414 # If the pre or post scripts are relative pathnames, convert to
415 # absolute, so they stand a chance of passing verification.
416 if len(self
.pre
) and not os
.path
.isabs(self
.pre
):
417 self
.pre
= os
.path
.join(self
.pathname
, self
.pre
)
418 if len(self
.post
) and not os
.path
.isabs(self
.post
):
419 self
.post
= os
.path
.join(self
.pathname
, self
.post
)
421 auxfiles
= [self
.pre
, self
.post
]
422 users
= [self
.pre_user
, self
.user
, self
.post_user
]
424 for f
in [f
for f
in auxfiles
if len(f
)]:
425 if self
.pathname
!= os
.path
.dirname(f
):
426 write_log("Warning: TestGroup '%s' not added to this run. "
427 "Auxiliary script '%s' exists in a different "
428 "directory.\n" % (self
.pathname
, f
), LOG_ERR
)
431 if not verify_file(f
):
432 write_log("Warning: TestGroup '%s' not added to this run. "
433 "Auxiliary script '%s' failed verification.\n" %
434 (self
.pathname
, f
), LOG_ERR
)
437 for user
in [user
for user
in users
if len(user
)]:
438 if not verify_user(user
):
439 write_log("Not adding TestGroup '%s' to this run.\n" %
440 self
.pathname
, LOG_ERR
)
443 # If one of the tests is invalid, delete it, log it, and drive on.
444 for test
in self
.tests
:
445 if not verify_file(os
.path
.join(self
.pathname
, test
)):
446 del self
.tests
[self
.tests
.index(test
)]
447 write_log("Warning: Test '%s' removed from TestGroup '%s' "
448 "because it failed verification.\n" %
449 (test
, self
.pathname
), LOG_ERR
)
451 return len(self
.tests
) != 0
453 def run(self
, options
):
455 Create Cmd instances for the pre/post scripts. If the pre script
456 doesn't pass, skip all the tests in this TestGroup. Run the post
459 # tags assigned to this test group also include the test names
460 if options
.tags
and not set(self
.tags
).intersection(set(options
.tags
)):
463 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
464 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
466 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
467 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
471 if len(pretest
.pathname
):
473 cont
= pretest
.result
.result
== 'PASS'
476 for fname
in self
.tests
:
477 test
= Cmd(os
.path
.join(self
.pathname
, fname
),
478 outputdir
=os
.path
.join(self
.outputdir
, fname
),
479 timeout
=self
.timeout
, user
=self
.user
)
487 if len(posttest
.pathname
):
488 posttest
.run(options
)
489 posttest
.log(options
)
492 class TestRun(object):
493 props
= ['quiet', 'outputdir']
495 def __init__(self
, options
):
498 self
.starttime
= time()
499 self
.timestamp
= datetime
.now().strftime('%Y%m%dT%H%M%S')
500 self
.outputdir
= os
.path
.join(options
.outputdir
, self
.timestamp
)
501 self
.setup_logging(options
)
503 ('outputdir', BASEDIR
),
515 s
= 'TestRun:\n outputdir: %s\n' % self
.outputdir
517 for key
in sorted(self
.tests
.keys()):
518 s
+= '%s%s' % (self
.tests
[key
].__str
__(), '\n')
520 for key
in sorted(self
.testgroups
.keys()):
521 s
+= '%s%s' % (self
.testgroups
[key
].__str
__(), '\n')
524 def addtest(self
, pathname
, options
):
526 Create a new Test, and apply any properties that were passed in
527 from the command line. If it passes verification, add it to the
530 test
= Test(pathname
)
531 for prop
in Test
.props
:
532 setattr(test
, prop
, getattr(options
, prop
))
535 self
.tests
[pathname
] = test
537 def addtestgroup(self
, dirname
, filenames
, options
):
539 Create a new TestGroup, and apply any properties that were passed
540 in from the command line. If it passes verification, add it to the
543 if dirname
not in self
.testgroups
:
544 testgroup
= TestGroup(dirname
)
545 for prop
in Test
.props
:
546 setattr(testgroup
, prop
, getattr(options
, prop
))
548 # Prevent pre/post scripts from running as regular tests
549 for f
in [testgroup
.pre
, testgroup
.post
]:
551 del filenames
[filenames
.index(f
)]
553 self
.testgroups
[dirname
] = testgroup
554 self
.testgroups
[dirname
].tests
= sorted(filenames
)
558 def read(self
, options
):
560 Read in the specified runfile, and apply the TestRun properties
561 listed in the 'DEFAULT' section to our TestRun. Then read each
562 section, and apply the appropriate properties to the Test or
563 TestGroup. Properties from individual sections override those set
564 in the 'DEFAULT' section. If the Test or TestGroup passes
565 verification, add it to the TestRun.
567 config
= configparser
.RawConfigParser()
568 if not len(config
.read(options
.runfile
)):
569 fail("Coulnd't read config file %s" % options
.runfile
)
571 for opt
in TestRun
.props
:
572 if config
.has_option('DEFAULT', opt
):
573 setattr(self
, opt
, config
.get('DEFAULT', opt
))
574 self
.outputdir
= os
.path
.join(self
.outputdir
, self
.timestamp
)
576 for section
in config
.sections():
577 if 'tests' in config
.options(section
):
578 if os
.path
.isdir(section
):
580 elif os
.path
.isdir(os
.path
.join(options
.testdir
, section
)):
581 pathname
= os
.path
.join(options
.testdir
, section
)
585 testgroup
= TestGroup(os
.path
.abspath(pathname
))
586 for prop
in TestGroup
.props
:
587 for sect
in ['DEFAULT', section
]:
588 if config
.has_option(sect
, prop
):
590 setattr(testgroup
, prop
,
591 eval(config
.get(sect
, prop
)))
593 setattr(testgroup
, prop
,
594 config
.get(sect
, prop
))
596 # Repopulate tests using eval to convert the string to a list
597 testgroup
.tests
= eval(config
.get(section
, 'tests'))
599 if testgroup
.verify():
600 self
.testgroups
[section
] = testgroup
603 for prop
in Test
.props
:
604 for sect
in ['DEFAULT', section
]:
605 if config
.has_option(sect
, prop
):
606 setattr(test
, prop
, config
.get(sect
, prop
))
609 self
.tests
[section
] = test
611 def write(self
, options
):
613 Create a configuration file for editing and later use. The
614 'DEFAULT' section of the config file is created from the
615 properties that were specified on the command line. Tests are
616 simply added as sections that inherit everything from the
617 'DEFAULT' section. TestGroups are the same, except they get an
618 option including all the tests to run in that directory.
621 defaults
= dict([(prop
, getattr(options
, prop
)) for prop
, _
in
623 config
= configparser
.RawConfigParser(defaults
)
625 for test
in sorted(self
.tests
.keys()):
626 config
.add_section(test
)
628 for testgroup
in sorted(self
.testgroups
.keys()):
629 config
.add_section(testgroup
)
630 config
.set(testgroup
, 'tests', self
.testgroups
[testgroup
].tests
)
633 with
open(options
.template
, 'w') as f
:
634 return config
.write(f
)
636 fail('Could not open \'%s\' for writing.' % options
.template
)
638 def complete_outputdirs(self
):
640 Collect all the pathnames for Tests, and TestGroups. Work
641 backwards one pathname component at a time, to create a unique
642 directory name in which to deposit test output. Tests will be able
643 to write output files directly in the newly modified outputdir.
644 TestGroups will be able to create one subdirectory per test in the
645 outputdir, and are guaranteed uniqueness because a group can only
646 contain files in one directory. Pre and post tests will create a
647 directory rooted at the outputdir of the Test or TestGroup in
648 question for their output.
652 tmp_dict
= dict(list(self
.tests
.items()) +
653 list(self
.testgroups
.items()))
654 total
= len(tmp_dict
)
655 base
= self
.outputdir
660 for testfile
in list(tmp_dict
.keys()):
661 uniq
= '/'.join(testfile
.split('/')[components
:]).lstrip('/')
662 if uniq
not in paths
:
664 tmp_dict
[testfile
].outputdir
= os
.path
.join(base
, uniq
)
667 done
= total
== len(paths
)
669 def setup_logging(self
, options
):
671 This funtion creates the output directory and gets a file object
672 for the logfile. This function must be called before write_log()
675 if options
.dryrun
is True:
679 if options
.cmd
!= 'wrconfig':
682 os
.makedirs(self
.outputdir
, mode
=0o777)
684 filename
= os
.path
.join(self
.outputdir
, 'log')
685 LOG_FILE_OBJ
= open(filename
, buffering
=0, mode
='wb')
689 def run(self
, options
):
691 Walk through all the Tests and TestGroups, calling run().
694 os
.chdir(self
.outputdir
)
696 fail('Could not change to directory %s' % self
.outputdir
)
697 # make a symlink to the output for the currently running test
698 logsymlink
= os
.path
.join(self
.outputdir
, '../current')
699 if os
.path
.islink(logsymlink
):
700 os
.unlink(logsymlink
)
701 if not os
.path
.exists(logsymlink
):
702 os
.symlink(self
.outputdir
, logsymlink
)
704 write_log('Could not make a symlink to directory %s\n' %
705 self
.outputdir
, LOG_ERR
)
707 while iteration
< options
.iterations
:
708 for test
in sorted(self
.tests
.keys()):
709 self
.tests
[test
].run(options
)
710 for testgroup
in sorted(self
.testgroups
.keys()):
711 self
.testgroups
[testgroup
].run(options
)
715 if Result
.total
== 0:
718 print('\nResults Summary')
719 for key
in list(Result
.runresults
.keys()):
720 if Result
.runresults
[key
] != 0:
721 print('%s\t% 4d' % (key
, Result
.runresults
[key
]))
723 m
, s
= divmod(time() - self
.starttime
, 60)
725 print('\nRunning Time:\t%02d:%02d:%02d' % (h
, m
, s
))
726 print('Percent passed:\t%.1f%%' % ((float(Result
.runresults
['PASS']) /
727 float(Result
.total
)) * 100))
728 print('Log directory:\t%s' % self
.outputdir
)
730 if Result
.runresults
['FAIL'] > 0:
733 if Result
.runresults
['KILLED'] > 0:
739 def write_log(msg
, target
):
741 Write the provided message to standard out, standard error or
742 the logfile. If specifying LOG_FILE, then `msg` must be a bytes
743 like object. This way we can still handle output from tests that
744 may be in unexpected encodings.
746 if target
== LOG_OUT
:
747 os
.write(sys
.stdout
.fileno(), bytearray(msg
, encoding
='utf-8'))
748 elif target
== LOG_ERR
:
749 os
.write(sys
.stderr
.fileno(), bytearray(msg
, encoding
='utf-8'))
750 elif target
== LOG_FILE
:
751 os
.write(LOG_FILE_OBJ
.fileno(), msg
)
753 fail('log_msg called with unknown target "%s"' % target
)
756 def verify_file(pathname
):
758 Verify that the supplied pathname is an executable regular file.
760 if os
.path
.isdir(pathname
) or os
.path
.islink(pathname
):
763 for ext
in '', '.ksh', '.sh':
764 script_path
= pathname
+ ext
765 if os
.path
.isfile(script_path
) and os
.access(script_path
, os
.X_OK
):
771 def verify_user(user
):
773 Verify that the specified user exists on this system, and can execute
774 sudo without being prompted for a password.
776 testcmd
= [SUDO
, '-n', '-u', user
, TRUE
]
778 if user
in Cmd
.verified_users
:
784 write_log("Warning: user '%s' does not exist.\n" % user
,
790 if p
.returncode
!= 0:
791 write_log("Warning: user '%s' cannot use passwordless sudo.\n" % user
,
795 Cmd
.verified_users
.append(user
)
800 def find_tests(testrun
, options
):
802 For the given list of pathnames, add files as Tests. For directories,
803 if do_groups is True, add the directory as a TestGroup. If False,
804 recursively search for executable files.
807 for p
in sorted(options
.pathnames
):
809 for dirname
, _
, filenames
in os
.walk(p
):
810 if options
.do_groups
:
811 testrun
.addtestgroup(dirname
, filenames
, options
)
813 for f
in sorted(filenames
):
814 testrun
.addtest(os
.path
.join(dirname
, f
), options
)
816 testrun
.addtest(p
, options
)
819 def fail(retstr
, ret
=1):
820 print('%s: %s' % (sys
.argv
[0], retstr
))
824 def options_cb(option
, opt_str
, value
, parser
):
825 path_options
= ['runfile', 'outputdir', 'template', 'testdir']
827 if option
.dest
== 'runfile' and '-w' in parser
.rargs
or \
828 option
.dest
== 'template' and '-c' in parser
.rargs
:
829 fail('-c and -w are mutually exclusive.')
831 if opt_str
in parser
.rargs
:
832 fail('%s may only be specified once.' % opt_str
)
834 if option
.dest
== 'runfile':
835 parser
.values
.cmd
= 'rdconfig'
836 if option
.dest
== 'template':
837 parser
.values
.cmd
= 'wrconfig'
838 if option
.dest
== 'tags':
839 value
= [x
.strip() for x
in value
.split(',')]
841 setattr(parser
.values
, option
.dest
, value
)
842 if option
.dest
in path_options
:
843 setattr(parser
.values
, option
.dest
, os
.path
.abspath(value
))
847 parser
= OptionParser()
848 parser
.add_option('-c', action
='callback', callback
=options_cb
,
849 type='string', dest
='runfile', metavar
='runfile',
850 help='Specify tests to run via config file.')
851 parser
.add_option('-d', action
='store_true', default
=False, dest
='dryrun',
852 help='Dry run. Print tests, but take no other action.')
853 parser
.add_option('-g', action
='store_true', default
=False,
854 dest
='do_groups', help='Make directories TestGroups.')
855 parser
.add_option('-o', action
='callback', callback
=options_cb
,
856 default
=BASEDIR
, dest
='outputdir', type='string',
857 metavar
='outputdir', help='Specify an output directory.')
858 parser
.add_option('-i', action
='callback', callback
=options_cb
,
859 default
=TESTDIR
, dest
='testdir', type='string',
860 metavar
='testdir', help='Specify a test directory.')
861 parser
.add_option('-p', action
='callback', callback
=options_cb
,
862 default
='', dest
='pre', metavar
='script',
863 type='string', help='Specify a pre script.')
864 parser
.add_option('-P', action
='callback', callback
=options_cb
,
865 default
='', dest
='post', metavar
='script',
866 type='string', help='Specify a post script.')
867 parser
.add_option('-q', action
='store_true', default
=False, dest
='quiet',
868 help='Silence on the console during a test run.')
869 parser
.add_option('-t', action
='callback', callback
=options_cb
, default
=60,
870 dest
='timeout', metavar
='seconds', type='int',
871 help='Timeout (in seconds) for an individual test.')
872 parser
.add_option('-u', action
='callback', callback
=options_cb
,
873 default
='', dest
='user', metavar
='user', type='string',
874 help='Specify a different user name to run as.')
875 parser
.add_option('-w', action
='callback', callback
=options_cb
,
876 default
=None, dest
='template', metavar
='template',
877 type='string', help='Create a new config file.')
878 parser
.add_option('-x', action
='callback', callback
=options_cb
, default
='',
879 dest
='pre_user', metavar
='pre_user', type='string',
880 help='Specify a user to execute the pre script.')
881 parser
.add_option('-X', action
='callback', callback
=options_cb
, default
='',
882 dest
='post_user', metavar
='post_user', type='string',
883 help='Specify a user to execute the post script.')
884 parser
.add_option('-T', action
='callback', callback
=options_cb
, default
='',
885 dest
='tags', metavar
='tags', type='string',
886 help='Specify tags to execute specific test groups.')
887 parser
.add_option('-I', action
='callback', callback
=options_cb
, default
=1,
888 dest
='iterations', metavar
='iterations', type='int',
889 help='Number of times to run the test run.')
890 (options
, pathnames
) = parser
.parse_args()
892 if not options
.runfile
and not options
.template
:
893 options
.cmd
= 'runtests'
895 if options
.runfile
and len(pathnames
):
896 fail('Extraneous arguments.')
898 options
.pathnames
= [os
.path
.abspath(path
) for path
in pathnames
]
904 options
= parse_args()
905 testrun
= TestRun(options
)
907 if options
.cmd
== 'runtests':
908 find_tests(testrun
, options
)
909 elif options
.cmd
== 'rdconfig':
910 testrun
.read(options
)
911 elif options
.cmd
== 'wrconfig':
912 find_tests(testrun
, options
)
913 testrun
.write(options
)
916 fail('Unknown command specified')
918 testrun
.complete_outputdirs()
920 exit(testrun
.summary())
923 if __name__
== '__main__':