]>
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, 2015 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
29 from datetime
import datetime
30 from optparse
import OptionParser
31 from pwd
import getpwnam
32 from pwd
import getpwuid
33 from select
import select
34 from subprocess
import PIPE
35 from subprocess
import Popen
37 from sys
import maxsize
38 from threading
import Timer
41 BASEDIR
= '/var/tmp/test_results'
42 TESTDIR
= '/usr/share/zfs/'
50 runresults
= {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0}
54 self
.returncode
= None
60 def done(self
, proc
, killed
):
62 Finalize the results of this Cmd.
65 m
, s
= divmod(time() - self
.starttime
, 60)
66 self
.runtime
= '%02d:%02d' % (m
, s
)
67 self
.returncode
= proc
.returncode
69 self
.result
= 'KILLED'
70 Result
.runresults
['KILLED'] += 1
71 elif self
.returncode
is 0:
73 Result
.runresults
['PASS'] += 1
74 elif self
.returncode
is 4:
76 Result
.runresults
['SKIP'] += 1
77 elif self
.returncode
is not 0:
79 Result
.runresults
['FAIL'] += 1
84 This class is a slightly modified version of the 'Stream' class found
85 here: http://goo.gl/aSGfv
87 def __init__(self
, stream
):
93 return self
.stream
.fileno()
95 def read(self
, drain
=0):
97 Read from the file descriptor. If 'drain' set, read until EOF.
99 while self
._read
() is not None:
105 Read up to 4k of data from this output stream. Collect the output
106 up to the last newline, and append it to any leftover data from a
107 previous call. The lines are stored as a (timestamp, data) tuple
108 for easy sorting/merging later.
111 buf
= os
.read(fd
, 4096)
118 buf
= self
._buf
+ buf
119 tmp
, rest
= buf
.rsplit('\n', 1)
122 rows
= tmp
.split('\n')
123 self
.lines
+= [(now
, r
) for r
in rows
]
129 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
131 self
.pathname
= pathname
132 self
.outputdir
= outputdir
or 'BASEDIR'
133 self
.timeout
= timeout
134 self
.user
= user
or ''
136 self
.result
= Result()
138 if self
.timeout
is None:
142 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nUser: %s\n" % \
143 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.user
)
145 def kill_cmd(self
, proc
):
147 Kill a running command due to timeout, or ^C from the keyboard. If
148 sudo is required, this user was verified previously.
151 do_sudo
= len(self
.user
) != 0
154 cmd
= [SUDO
, KILL
, signal
, str(proc
.pid
)]
164 def update_cmd_privs(self
, cmd
, user
):
166 If a user has been specified to run this Cmd and we're not already
167 running as that user, prepend the appropriate sudo command to run
170 me
= getpwuid(os
.getuid())
172 if not user
or user
is me
:
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
):
179 if not os
.path
.isfile(cmd
):
180 if os
.path
.isfile(cmd
+'.ksh') and os
.access(cmd
+'.ksh', os
.X_OK
):
182 if os
.path
.isfile(cmd
+'.sh') and os
.access(cmd
+'.sh', os
.X_OK
):
185 ret
= '%s -E -u %s %s' % (SUDO
, user
, cmd
)
186 return ret
.split(' ')
188 def collect_output(self
, proc
):
190 Read from stdout/stderr as data becomes available, until the
191 process is no longer running. Return the lines from the stdout and
192 stderr Output objects.
194 out
= Output(proc
.stdout
)
195 err
= Output(proc
.stderr
)
197 while proc
.returncode
is None:
199 res
= select([out
, err
], [], [], .1)
205 return out
.lines
, err
.lines
207 def run(self
, options
):
209 This is the main function that runs each individual test.
210 Determine whether or not the command requires sudo, and modify it
211 if needed. Run the command, and update the result object.
213 if options
.dryrun
is True:
217 privcmd
= self
.update_cmd_privs(self
.pathname
, self
.user
)
220 if not os
.path
.isdir(self
.outputdir
):
221 os
.makedirs(self
.outputdir
, mode
=0o777)
226 self
.result
.starttime
= time()
227 proc
= Popen(privcmd
, stdout
=PIPE
, stderr
=PIPE
)
228 # Allow a special timeout value of 0 to mean infinity
229 if int(self
.timeout
) == 0:
230 self
.timeout
= maxsize
231 t
= Timer(int(self
.timeout
), self
.kill_cmd
, [proc
])
235 self
.result
.stdout
, self
.result
.stderr
= self
.collect_output(proc
)
236 except KeyboardInterrupt:
238 fail('\nRun terminated at user request.')
242 self
.result
.done(proc
, self
.killed
)
246 Initialize enough of the test result that we can log a skipped
250 Result
.runresults
['SKIP'] += 1
251 self
.result
.stdout
= self
.result
.stderr
= []
252 self
.result
.starttime
= time()
253 m
, s
= divmod(time() - self
.result
.starttime
, 60)
254 self
.result
.runtime
= '%02d:%02d' % (m
, s
)
255 self
.result
.result
= 'SKIP'
257 def log(self
, logger
, options
):
259 This function is responsible for writing all output. This includes
260 the console output, the logfile of all results (with timestamped
261 merged stdout and stderr), and for each test, the unmodified
262 stdout/stderr/merged in it's own file.
267 logname
= getpwuid(os
.getuid()).pw_name
268 user
= ' (run as %s)' % (self
.user
if len(self
.user
) else logname
)
269 msga
= 'Test: %s%s ' % (self
.pathname
, user
)
270 msgb
= '[%s] [%s]' % (self
.result
.runtime
, self
.result
.result
)
271 pad
= ' ' * (80 - (len(msga
) + len(msgb
)))
273 # If -q is specified, only print a line for tests that didn't pass.
274 # This means passing tests need to be logged as DEBUG, or the one
275 # line summary will only be printed in the logfile for failures.
276 if not options
.quiet
:
277 logger
.info('%s%s%s' % (msga
, pad
, msgb
))
278 elif self
.result
.result
is not 'PASS':
279 logger
.info('%s%s%s' % (msga
, pad
, msgb
))
281 logger
.debug('%s%s%s' % (msga
, pad
, msgb
))
283 lines
= sorted(self
.result
.stdout
+ self
.result
.stderr
,
286 for dt
, line
in lines
:
287 logger
.debug('%s %s' % (dt
.strftime("%H:%M:%S.%f ")[:11], line
))
289 if len(self
.result
.stdout
):
290 with
open(os
.path
.join(self
.outputdir
, 'stdout'), 'w') as out
:
291 for _
, line
in self
.result
.stdout
:
292 os
.write(out
.fileno(), '%s\n' % line
)
293 if len(self
.result
.stderr
):
294 with
open(os
.path
.join(self
.outputdir
, 'stderr'), 'w') as err
:
295 for _
, line
in self
.result
.stderr
:
296 os
.write(err
.fileno(), '%s\n' % line
)
297 if len(self
.result
.stdout
) and len(self
.result
.stderr
):
298 with
open(os
.path
.join(self
.outputdir
, 'merged'), 'w') as merged
:
299 for _
, line
in lines
:
300 os
.write(merged
.fileno(), '%s\n' % line
)
304 props
= ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post',
307 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
308 pre
=None, pre_user
=None, post
=None, post_user
=None,
310 super(Test
, self
).__init
__(pathname
, outputdir
, timeout
, user
)
312 self
.pre_user
= pre_user
or ''
313 self
.post
= post
or ''
314 self
.post_user
= post_user
or ''
315 self
.tags
= tags
or []
318 post_user
= pre_user
= ''
319 if len(self
.pre_user
):
320 pre_user
= ' (as %s)' % (self
.pre_user
)
321 if len(self
.post_user
):
322 post_user
= ' (as %s)' % (self
.post_user
)
323 return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \
324 "%s%s\nUser: %s\nTags: %s\n" % \
325 (self
.pathname
, self
.outputdir
, self
.timeout
, self
.pre
,
326 pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
328 def verify(self
, logger
):
330 Check the pre/post scripts, user and Test. Omit the Test from this
331 run if there are any problems.
333 files
= [self
.pre
, self
.pathname
, self
.post
]
334 users
= [self
.pre_user
, self
.user
, self
.post_user
]
336 for f
in [f
for f
in files
if len(f
)]:
337 if not verify_file(f
):
338 logger
.info("Warning: Test '%s' not added to this run because"
339 " it failed verification." % f
)
342 for user
in [user
for user
in users
if len(user
)]:
343 if not verify_user(user
, logger
):
344 logger
.info("Not adding Test '%s' to this run." %
350 def run(self
, logger
, options
):
352 Create Cmd instances for the pre/post scripts. If the pre script
353 doesn't pass, skip this Test. Run the post script regardless.
355 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
356 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
358 test
= Cmd(self
.pathname
, outputdir
=self
.outputdir
,
359 timeout
=self
.timeout
, user
=self
.user
)
360 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
361 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
365 if len(pretest
.pathname
):
367 cont
= pretest
.result
.result
is 'PASS'
368 pretest
.log(logger
, options
)
375 test
.log(logger
, options
)
377 if len(posttest
.pathname
):
378 posttest
.run(options
)
379 posttest
.log(logger
, options
)
382 class TestGroup(Test
):
383 props
= Test
.props
+ ['tests']
385 def __init__(self
, pathname
, outputdir
=None, timeout
=None, user
=None,
386 pre
=None, pre_user
=None, post
=None, post_user
=None,
387 tests
=None, tags
=None):
388 super(TestGroup
, self
).__init
__(pathname
, outputdir
, timeout
, user
,
389 pre
, pre_user
, post
, post_user
, tags
)
390 self
.tests
= tests
or []
393 post_user
= pre_user
= ''
394 if len(self
.pre_user
):
395 pre_user
= ' (as %s)' % (self
.pre_user
)
396 if len(self
.post_user
):
397 post_user
= ' (as %s)' % (self
.post_user
)
398 return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \
399 "Pre: %s%s\nPost: %s%s\nUser: %s\nTags: %s\n" % \
400 (self
.pathname
, self
.outputdir
, self
.tests
, self
.timeout
,
401 self
.pre
, pre_user
, self
.post
, post_user
, self
.user
, self
.tags
)
403 def verify(self
, logger
):
405 Check the pre/post scripts, user and tests in this TestGroup. Omit
406 the TestGroup entirely, or simply delete the relevant tests in the
407 group, if that's all that's required.
409 # If the pre or post scripts are relative pathnames, convert to
410 # absolute, so they stand a chance of passing verification.
411 if len(self
.pre
) and not os
.path
.isabs(self
.pre
):
412 self
.pre
= os
.path
.join(self
.pathname
, self
.pre
)
413 if len(self
.post
) and not os
.path
.isabs(self
.post
):
414 self
.post
= os
.path
.join(self
.pathname
, self
.post
)
416 auxfiles
= [self
.pre
, self
.post
]
417 users
= [self
.pre_user
, self
.user
, self
.post_user
]
419 for f
in [f
for f
in auxfiles
if len(f
)]:
420 if self
.pathname
!= os
.path
.dirname(f
):
421 logger
.info("Warning: TestGroup '%s' not added to this run. "
422 "Auxiliary script '%s' exists in a different "
423 "directory." % (self
.pathname
, f
))
426 if not verify_file(f
):
427 logger
.info("Warning: TestGroup '%s' not added to this run. "
428 "Auxiliary script '%s' failed verification." %
432 for user
in [user
for user
in users
if len(user
)]:
433 if not verify_user(user
, logger
):
434 logger
.info("Not adding TestGroup '%s' to this run." %
438 # If one of the tests is invalid, delete it, log it, and drive on.
439 for test
in self
.tests
:
440 if not verify_file(os
.path
.join(self
.pathname
, test
)):
441 del self
.tests
[self
.tests
.index(test
)]
442 logger
.info("Warning: Test '%s' removed from TestGroup '%s' "
443 "because it failed verification." %
444 (test
, self
.pathname
))
446 return len(self
.tests
) is not 0
448 def run(self
, logger
, options
):
450 Create Cmd instances for the pre/post scripts. If the pre script
451 doesn't pass, skip all the tests in this TestGroup. Run the post
454 # tags assigned to this test group also include the test names
455 if options
.tags
and not set(self
.tags
).intersection(set(options
.tags
)):
458 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.pre
))
459 pretest
= Cmd(self
.pre
, outputdir
=odir
, timeout
=self
.timeout
,
461 odir
= os
.path
.join(self
.outputdir
, os
.path
.basename(self
.post
))
462 posttest
= Cmd(self
.post
, outputdir
=odir
, timeout
=self
.timeout
,
466 if len(pretest
.pathname
):
468 cont
= pretest
.result
.result
is 'PASS'
469 pretest
.log(logger
, options
)
471 for fname
in self
.tests
:
472 test
= Cmd(os
.path
.join(self
.pathname
, fname
),
473 outputdir
=os
.path
.join(self
.outputdir
, fname
),
474 timeout
=self
.timeout
, user
=self
.user
)
480 test
.log(logger
, options
)
482 if len(posttest
.pathname
):
483 posttest
.run(options
)
484 posttest
.log(logger
, options
)
487 class TestRun(object):
488 props
= ['quiet', 'outputdir']
490 def __init__(self
, options
):
493 self
.starttime
= time()
494 self
.timestamp
= datetime
.now().strftime('%Y%m%dT%H%M%S')
495 self
.outputdir
= os
.path
.join(options
.outputdir
, self
.timestamp
)
496 self
.logger
= self
.setup_logging(options
)
498 ('outputdir', BASEDIR
),
510 s
= 'TestRun:\n outputdir: %s\n' % self
.outputdir
512 for key
in sorted(self
.tests
.keys()):
513 s
+= '%s%s' % (self
.tests
[key
].__str
__(), '\n')
515 for key
in sorted(self
.testgroups
.keys()):
516 s
+= '%s%s' % (self
.testgroups
[key
].__str
__(), '\n')
519 def addtest(self
, pathname
, options
):
521 Create a new Test, and apply any properties that were passed in
522 from the command line. If it passes verification, add it to the
525 test
= Test(pathname
)
526 for prop
in Test
.props
:
527 setattr(test
, prop
, getattr(options
, prop
))
529 if test
.verify(self
.logger
):
530 self
.tests
[pathname
] = test
532 def addtestgroup(self
, dirname
, filenames
, options
):
534 Create a new TestGroup, and apply any properties that were passed
535 in from the command line. If it passes verification, add it to the
538 if dirname
not in self
.testgroups
:
539 testgroup
= TestGroup(dirname
)
540 for prop
in Test
.props
:
541 setattr(testgroup
, prop
, getattr(options
, prop
))
543 # Prevent pre/post scripts from running as regular tests
544 for f
in [testgroup
.pre
, testgroup
.post
]:
546 del filenames
[filenames
.index(f
)]
548 self
.testgroups
[dirname
] = testgroup
549 self
.testgroups
[dirname
].tests
= sorted(filenames
)
551 testgroup
.verify(self
.logger
)
553 def read(self
, logger
, options
):
555 Read in the specified runfile, and apply the TestRun properties
556 listed in the 'DEFAULT' section to our TestRun. Then read each
557 section, and apply the appropriate properties to the Test or
558 TestGroup. Properties from individual sections override those set
559 in the 'DEFAULT' section. If the Test or TestGroup passes
560 verification, add it to the TestRun.
562 config
= configparser
.RawConfigParser()
563 if not len(config
.read(options
.runfile
)):
564 fail("Coulnd't read config file %s" % options
.runfile
)
566 for opt
in TestRun
.props
:
567 if config
.has_option('DEFAULT', opt
):
568 setattr(self
, opt
, config
.get('DEFAULT', opt
))
569 self
.outputdir
= os
.path
.join(self
.outputdir
, self
.timestamp
)
571 for section
in config
.sections():
572 if 'tests' in config
.options(section
):
573 if os
.path
.isdir(section
):
575 elif os
.path
.isdir(os
.path
.join(options
.testdir
, section
)):
576 pathname
= os
.path
.join(options
.testdir
, section
)
580 testgroup
= TestGroup(os
.path
.abspath(pathname
))
581 for prop
in TestGroup
.props
:
582 for sect
in ['DEFAULT', section
]:
583 if config
.has_option(sect
, prop
):
585 setattr(testgroup
, prop
,
586 eval(config
.get(sect
, prop
)))
588 setattr(testgroup
, prop
,
589 config
.get(sect
, prop
))
591 # Repopulate tests using eval to convert the string to a list
592 testgroup
.tests
= eval(config
.get(section
, 'tests'))
594 if testgroup
.verify(logger
):
595 self
.testgroups
[section
] = testgroup
598 for prop
in Test
.props
:
599 for sect
in ['DEFAULT', section
]:
600 if config
.has_option(sect
, prop
):
601 setattr(test
, prop
, config
.get(sect
, prop
))
603 if test
.verify(logger
):
604 self
.tests
[section
] = test
606 def write(self
, options
):
608 Create a configuration file for editing and later use. The
609 'DEFAULT' section of the config file is created from the
610 properties that were specified on the command line. Tests are
611 simply added as sections that inherit everything from the
612 'DEFAULT' section. TestGroups are the same, except they get an
613 option including all the tests to run in that directory.
616 defaults
= dict([(prop
, getattr(options
, prop
)) for prop
, _
in
618 config
= configparser
.RawConfigParser(defaults
)
620 for test
in sorted(self
.tests
.keys()):
621 config
.add_section(test
)
623 for testgroup
in sorted(self
.testgroups
.keys()):
624 config
.add_section(testgroup
)
625 config
.set(testgroup
, 'tests', self
.testgroups
[testgroup
].tests
)
628 with
open(options
.template
, 'w') as f
:
629 return config
.write(f
)
631 fail('Could not open \'%s\' for writing.' % options
.template
)
633 def complete_outputdirs(self
):
635 Collect all the pathnames for Tests, and TestGroups. Work
636 backwards one pathname component at a time, to create a unique
637 directory name in which to deposit test output. Tests will be able
638 to write output files directly in the newly modified outputdir.
639 TestGroups will be able to create one subdirectory per test in the
640 outputdir, and are guaranteed uniqueness because a group can only
641 contain files in one directory. Pre and post tests will create a
642 directory rooted at the outputdir of the Test or TestGroup in
643 question for their output.
647 tmp_dict
= dict(list(self
.tests
.items()) +
648 list(self
.testgroups
.items()))
649 total
= len(tmp_dict
)
650 base
= self
.outputdir
655 for testfile
in list(tmp_dict
.keys()):
656 uniq
= '/'.join(testfile
.split('/')[components
:]).lstrip('/')
657 if uniq
not in paths
:
659 tmp_dict
[testfile
].outputdir
= os
.path
.join(base
, uniq
)
662 done
= total
== len(paths
)
664 def setup_logging(self
, options
):
666 Two loggers are set up here. The first is for the logfile which
667 will contain one line summarizing the test, including the test
668 name, result, and running time. This logger will also capture the
669 timestamped combined stdout and stderr of each run. The second
670 logger is optional console output, which will contain only the one
671 line summary. The loggers are initialized at two different levels
672 to facilitate segregating the output.
674 if options
.dryrun
is True:
677 testlogger
= logging
.getLogger(__name__
)
678 testlogger
.setLevel(logging
.DEBUG
)
680 if options
.cmd
is not 'wrconfig':
683 os
.makedirs(self
.outputdir
, mode
=0o777)
687 filename
= os
.path
.join(self
.outputdir
, 'log')
689 logfile
= logging
.FileHandler(filename
)
690 logfile
.setLevel(logging
.DEBUG
)
691 logfilefmt
= logging
.Formatter('%(message)s')
692 logfile
.setFormatter(logfilefmt
)
693 testlogger
.addHandler(logfile
)
695 cons
= logging
.StreamHandler()
696 cons
.setLevel(logging
.INFO
)
697 consfmt
= logging
.Formatter('%(message)s')
698 cons
.setFormatter(consfmt
)
699 testlogger
.addHandler(cons
)
703 def run(self
, options
):
705 Walk through all the Tests and TestGroups, calling run().
708 os
.chdir(self
.outputdir
)
710 fail('Could not change to directory %s' % self
.outputdir
)
711 # make a symlink to the output for the currently running test
712 logsymlink
= os
.path
.join(self
.outputdir
, '../current')
713 if os
.path
.islink(logsymlink
):
714 os
.unlink(logsymlink
)
715 if not os
.path
.exists(logsymlink
):
716 os
.symlink(self
.outputdir
, logsymlink
)
718 print('Could not make a symlink to directory %s' % (
721 while iteration
< options
.iterations
:
722 for test
in sorted(self
.tests
.keys()):
723 self
.tests
[test
].run(self
.logger
, options
)
724 for testgroup
in sorted(self
.testgroups
.keys()):
725 self
.testgroups
[testgroup
].run(self
.logger
, options
)
729 if Result
.total
is 0:
732 print('\nResults Summary')
733 for key
in list(Result
.runresults
.keys()):
734 if Result
.runresults
[key
] is not 0:
735 print('%s\t% 4d' % (key
, Result
.runresults
[key
]))
737 m
, s
= divmod(time() - self
.starttime
, 60)
739 print('\nRunning Time:\t%02d:%02d:%02d' % (h
, m
, s
))
740 print('Percent passed:\t%.1f%%' % ((float(Result
.runresults
['PASS']) /
741 float(Result
.total
)) * 100))
742 print('Log directory:\t%s' % self
.outputdir
)
744 if Result
.runresults
['FAIL'] > 0:
747 if Result
.runresults
['KILLED'] > 0:
753 def verify_file(pathname
):
755 Verify that the supplied pathname is an executable regular file.
757 if os
.path
.isdir(pathname
) or os
.path
.islink(pathname
):
760 for ext
in '', '.ksh', '.sh':
761 script_path
= pathname
+ ext
762 if os
.path
.isfile(script_path
) and os
.access(script_path
, os
.X_OK
):
768 def verify_user(user
, logger
):
770 Verify that the specified user exists on this system, and can execute
771 sudo without being prompted for a password.
773 testcmd
= [SUDO
, '-n', '-u', user
, TRUE
]
775 if user
in Cmd
.verified_users
:
781 logger
.info("Warning: user '%s' does not exist.", user
)
786 if p
.returncode
is not 0:
787 logger
.info("Warning: user '%s' cannot use passwordless sudo.", user
)
790 Cmd
.verified_users
.append(user
)
795 def find_tests(testrun
, options
):
797 For the given list of pathnames, add files as Tests. For directories,
798 if do_groups is True, add the directory as a TestGroup. If False,
799 recursively search for executable files.
802 for p
in sorted(options
.pathnames
):
804 for dirname
, _
, filenames
in os
.walk(p
):
805 if options
.do_groups
:
806 testrun
.addtestgroup(dirname
, filenames
, options
)
808 for f
in sorted(filenames
):
809 testrun
.addtest(os
.path
.join(dirname
, f
), options
)
811 testrun
.addtest(p
, options
)
814 def fail(retstr
, ret
=1):
815 print('%s: %s' % (argv
[0], retstr
))
819 def options_cb(option
, opt_str
, value
, parser
):
820 path_options
= ['runfile', 'outputdir', 'template', 'testdir']
822 if option
.dest
is 'runfile' and '-w' in parser
.rargs
or \
823 option
.dest
is 'template' and '-c' in parser
.rargs
:
824 fail('-c and -w are mutually exclusive.')
826 if opt_str
in parser
.rargs
:
827 fail('%s may only be specified once.' % opt_str
)
829 if option
.dest
is 'runfile':
830 parser
.values
.cmd
= 'rdconfig'
831 if option
.dest
is 'template':
832 parser
.values
.cmd
= 'wrconfig'
833 if option
.dest
is 'tags':
834 value
= [x
.strip() for x
in value
.split(',')]
836 setattr(parser
.values
, option
.dest
, value
)
837 if option
.dest
in path_options
:
838 setattr(parser
.values
, option
.dest
, os
.path
.abspath(value
))
842 parser
= OptionParser()
843 parser
.add_option('-c', action
='callback', callback
=options_cb
,
844 type='string', dest
='runfile', metavar
='runfile',
845 help='Specify tests to run via config file.')
846 parser
.add_option('-d', action
='store_true', default
=False, dest
='dryrun',
847 help='Dry run. Print tests, but take no other action.')
848 parser
.add_option('-g', action
='store_true', default
=False,
849 dest
='do_groups', help='Make directories TestGroups.')
850 parser
.add_option('-o', action
='callback', callback
=options_cb
,
851 default
=BASEDIR
, dest
='outputdir', type='string',
852 metavar
='outputdir', help='Specify an output directory.')
853 parser
.add_option('-i', action
='callback', callback
=options_cb
,
854 default
=TESTDIR
, dest
='testdir', type='string',
855 metavar
='testdir', help='Specify a test directory.')
856 parser
.add_option('-p', action
='callback', callback
=options_cb
,
857 default
='', dest
='pre', metavar
='script',
858 type='string', help='Specify a pre script.')
859 parser
.add_option('-P', action
='callback', callback
=options_cb
,
860 default
='', dest
='post', metavar
='script',
861 type='string', help='Specify a post script.')
862 parser
.add_option('-q', action
='store_true', default
=False, dest
='quiet',
863 help='Silence on the console during a test run.')
864 parser
.add_option('-t', action
='callback', callback
=options_cb
, default
=60,
865 dest
='timeout', metavar
='seconds', type='int',
866 help='Timeout (in seconds) for an individual test.')
867 parser
.add_option('-u', action
='callback', callback
=options_cb
,
868 default
='', dest
='user', metavar
='user', type='string',
869 help='Specify a different user name to run as.')
870 parser
.add_option('-w', action
='callback', callback
=options_cb
,
871 default
=None, dest
='template', metavar
='template',
872 type='string', help='Create a new config file.')
873 parser
.add_option('-x', action
='callback', callback
=options_cb
, default
='',
874 dest
='pre_user', metavar
='pre_user', type='string',
875 help='Specify a user to execute the pre script.')
876 parser
.add_option('-X', action
='callback', callback
=options_cb
, default
='',
877 dest
='post_user', metavar
='post_user', type='string',
878 help='Specify a user to execute the post script.')
879 parser
.add_option('-T', action
='callback', callback
=options_cb
, default
='',
880 dest
='tags', metavar
='tags', type='string',
881 help='Specify tags to execute specific test groups.')
882 parser
.add_option('-I', action
='callback', callback
=options_cb
, default
=1,
883 dest
='iterations', metavar
='iterations', type='int',
884 help='Number of times to run the test run.')
885 (options
, pathnames
) = parser
.parse_args()
887 if not options
.runfile
and not options
.template
:
888 options
.cmd
= 'runtests'
890 if options
.runfile
and len(pathnames
):
891 fail('Extraneous arguments.')
893 options
.pathnames
= [os
.path
.abspath(path
) for path
in pathnames
]
899 options
= parse_args()
900 testrun
= TestRun(options
)
902 if options
.cmd
is 'runtests':
903 find_tests(testrun
, options
)
904 elif options
.cmd
is 'rdconfig':
905 testrun
.read(testrun
.logger
, options
)
906 elif options
.cmd
is 'wrconfig':
907 find_tests(testrun
, options
)
908 testrun
.write(options
)
911 fail('Unknown command specified')
913 testrun
.complete_outputdirs()
915 exit(testrun
.summary())
918 if __name__
== '__main__':