124bb1048SAlexander Pyhalov#!@PYTHON@ 224bb1048SAlexander Pyhalov 324bb1048SAlexander Pyhalov# 424bb1048SAlexander Pyhalov# This file and its contents are supplied under the terms of the 524bb1048SAlexander Pyhalov# Common Development and Distribution License ("CDDL"), version 1.0. 624bb1048SAlexander Pyhalov# You may only use this file in accordance with the terms of version 724bb1048SAlexander Pyhalov# 1.0 of the CDDL. 824bb1048SAlexander Pyhalov# 924bb1048SAlexander Pyhalov# A full copy of the text of the CDDL should have accompanied this 1024bb1048SAlexander Pyhalov# source. A copy of the CDDL is also available via the Internet at 1124bb1048SAlexander Pyhalov# http://www.illumos.org/license/CDDL. 1224bb1048SAlexander Pyhalov# 1324bb1048SAlexander Pyhalov 1424bb1048SAlexander Pyhalov# 1524bb1048SAlexander Pyhalov# Copyright (c) 2012, 2015 by Delphix. All rights reserved. 1624bb1048SAlexander Pyhalov# 1724bb1048SAlexander Pyhalov 18*83d13c2eSAlexander Pyhalovfrom __future__ import print_function 19*83d13c2eSAlexander Pyhalovimport sys 20*83d13c2eSAlexander PyhalovPY3 = sys.version_info[0] == 3 21*83d13c2eSAlexander Pyhalov 22*83d13c2eSAlexander Pyhalovif PY3: 23*83d13c2eSAlexander Pyhalov import configparser 24*83d13c2eSAlexander Pyhalovelse: 25*83d13c2eSAlexander Pyhalov import ConfigParser as configparser 26*83d13c2eSAlexander Pyhalov 2724bb1048SAlexander Pyhalovimport os 2824bb1048SAlexander Pyhalovimport logging 2924bb1048SAlexander Pyhalovfrom logging.handlers import WatchedFileHandler 3024bb1048SAlexander Pyhalovfrom datetime import datetime 3124bb1048SAlexander Pyhalovfrom optparse import OptionParser 3224bb1048SAlexander Pyhalovfrom pwd import getpwnam 3324bb1048SAlexander Pyhalovfrom pwd import getpwuid 3424bb1048SAlexander Pyhalovfrom select import select 3524bb1048SAlexander Pyhalovfrom subprocess import PIPE 3624bb1048SAlexander Pyhalovfrom subprocess import Popen 3724bb1048SAlexander Pyhalovfrom sys import argv 3824bb1048SAlexander Pyhalovfrom sys import exit 39*83d13c2eSAlexander Pyhalovfrom sys import maxsize 4024bb1048SAlexander Pyhalovfrom threading import Timer 4124bb1048SAlexander Pyhalovfrom time import time 4224bb1048SAlexander Pyhalov 4324bb1048SAlexander PyhalovBASEDIR = '/var/tmp/test_results' 4424bb1048SAlexander PyhalovKILL = '/usr/bin/kill' 4524bb1048SAlexander PyhalovTRUE = '/usr/bin/true' 4624bb1048SAlexander PyhalovSUDO = '/usr/bin/sudo' 4724bb1048SAlexander Pyhalov 4824bb1048SAlexander Pyhalov# Custom class to reopen the log file in case it is forcibly closed by a test. 4924bb1048SAlexander Pyhalovclass WatchedFileHandlerClosed(WatchedFileHandler): 5024bb1048SAlexander Pyhalov """Watch files, including closed files. 5124bb1048SAlexander Pyhalov Similar to (and inherits from) logging.handler.WatchedFileHandler, 5224bb1048SAlexander Pyhalov except that IOErrors are handled by reopening the stream and retrying. 5324bb1048SAlexander Pyhalov This will be retried up to a configurable number of times before 5424bb1048SAlexander Pyhalov giving up, default 5. 5524bb1048SAlexander Pyhalov """ 5624bb1048SAlexander Pyhalov 5724bb1048SAlexander Pyhalov def __init__(self, filename, mode='a', encoding=None, delay=0, max_tries=5): 5824bb1048SAlexander Pyhalov self.max_tries = max_tries 5924bb1048SAlexander Pyhalov self.tries = 0 6024bb1048SAlexander Pyhalov WatchedFileHandler.__init__(self, filename, mode, encoding, delay) 6124bb1048SAlexander Pyhalov 6224bb1048SAlexander Pyhalov def emit(self, record): 6324bb1048SAlexander Pyhalov while True: 6424bb1048SAlexander Pyhalov try: 6524bb1048SAlexander Pyhalov WatchedFileHandler.emit(self, record) 6624bb1048SAlexander Pyhalov self.tries = 0 6724bb1048SAlexander Pyhalov return 6824bb1048SAlexander Pyhalov except IOError as err: 6924bb1048SAlexander Pyhalov if self.tries == self.max_tries: 7024bb1048SAlexander Pyhalov raise 7124bb1048SAlexander Pyhalov self.stream.close() 7224bb1048SAlexander Pyhalov self.stream = self._open() 7324bb1048SAlexander Pyhalov self.tries += 1 7424bb1048SAlexander Pyhalov 7524bb1048SAlexander Pyhalovclass Result(object): 7624bb1048SAlexander Pyhalov total = 0 7724bb1048SAlexander Pyhalov runresults = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0} 7824bb1048SAlexander Pyhalov 7924bb1048SAlexander Pyhalov def __init__(self): 8024bb1048SAlexander Pyhalov self.starttime = None 8124bb1048SAlexander Pyhalov self.returncode = None 8224bb1048SAlexander Pyhalov self.runtime = '' 8324bb1048SAlexander Pyhalov self.stdout = [] 8424bb1048SAlexander Pyhalov self.stderr = [] 8524bb1048SAlexander Pyhalov self.result = '' 8624bb1048SAlexander Pyhalov 8724bb1048SAlexander Pyhalov def done(self, proc, killed): 8824bb1048SAlexander Pyhalov """ 8924bb1048SAlexander Pyhalov Finalize the results of this Cmd. 9024bb1048SAlexander Pyhalov """ 9124bb1048SAlexander Pyhalov Result.total += 1 9224bb1048SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 9324bb1048SAlexander Pyhalov self.runtime = '%02d:%02d' % (m, s) 9424bb1048SAlexander Pyhalov self.returncode = proc.returncode 9524bb1048SAlexander Pyhalov if killed: 9624bb1048SAlexander Pyhalov self.result = 'KILLED' 9724bb1048SAlexander Pyhalov Result.runresults['KILLED'] += 1 9824bb1048SAlexander Pyhalov elif self.returncode is 0: 9924bb1048SAlexander Pyhalov self.result = 'PASS' 10024bb1048SAlexander Pyhalov Result.runresults['PASS'] += 1 10124bb1048SAlexander Pyhalov elif self.returncode is not 0: 10224bb1048SAlexander Pyhalov self.result = 'FAIL' 10324bb1048SAlexander Pyhalov Result.runresults['FAIL'] += 1 10424bb1048SAlexander Pyhalov 10524bb1048SAlexander Pyhalov 10624bb1048SAlexander Pyhalovclass Output(object): 10724bb1048SAlexander Pyhalov """ 10824bb1048SAlexander Pyhalov This class is a slightly modified version of the 'Stream' class found 10924bb1048SAlexander Pyhalov here: http://goo.gl/aSGfv 11024bb1048SAlexander Pyhalov """ 11124bb1048SAlexander Pyhalov def __init__(self, stream): 11224bb1048SAlexander Pyhalov self.stream = stream 11324bb1048SAlexander Pyhalov self._buf = '' 11424bb1048SAlexander Pyhalov self.lines = [] 11524bb1048SAlexander Pyhalov 11624bb1048SAlexander Pyhalov def fileno(self): 11724bb1048SAlexander Pyhalov return self.stream.fileno() 11824bb1048SAlexander Pyhalov 11924bb1048SAlexander Pyhalov def read(self, drain=0): 12024bb1048SAlexander Pyhalov """ 12124bb1048SAlexander Pyhalov Read from the file descriptor. If 'drain' set, read until EOF. 12224bb1048SAlexander Pyhalov """ 12324bb1048SAlexander Pyhalov while self._read() is not None: 12424bb1048SAlexander Pyhalov if not drain: 12524bb1048SAlexander Pyhalov break 12624bb1048SAlexander Pyhalov 12724bb1048SAlexander Pyhalov def _read(self): 12824bb1048SAlexander Pyhalov """ 12924bb1048SAlexander Pyhalov Read up to 4k of data from this output stream. Collect the output 13024bb1048SAlexander Pyhalov up to the last newline, and append it to any leftover data from a 13124bb1048SAlexander Pyhalov previous call. The lines are stored as a (timestamp, data) tuple 13224bb1048SAlexander Pyhalov for easy sorting/merging later. 13324bb1048SAlexander Pyhalov """ 13424bb1048SAlexander Pyhalov fd = self.fileno() 135*83d13c2eSAlexander Pyhalov buf = os.read(fd, 4096).decode() 13624bb1048SAlexander Pyhalov if not buf: 13724bb1048SAlexander Pyhalov return None 13824bb1048SAlexander Pyhalov if '\n' not in buf: 13924bb1048SAlexander Pyhalov self._buf += buf 14024bb1048SAlexander Pyhalov return [] 14124bb1048SAlexander Pyhalov 14224bb1048SAlexander Pyhalov buf = self._buf + buf 14324bb1048SAlexander Pyhalov tmp, rest = buf.rsplit('\n', 1) 14424bb1048SAlexander Pyhalov self._buf = rest 14524bb1048SAlexander Pyhalov now = datetime.now() 14624bb1048SAlexander Pyhalov rows = tmp.split('\n') 14724bb1048SAlexander Pyhalov self.lines += [(now, r) for r in rows] 14824bb1048SAlexander Pyhalov 14924bb1048SAlexander Pyhalov 15024bb1048SAlexander Pyhalovclass Cmd(object): 15124bb1048SAlexander Pyhalov verified_users = [] 15224bb1048SAlexander Pyhalov 15324bb1048SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None): 15424bb1048SAlexander Pyhalov self.pathname = pathname 15524bb1048SAlexander Pyhalov self.outputdir = outputdir or 'BASEDIR' 15624bb1048SAlexander Pyhalov self.timeout = timeout or 60 15724bb1048SAlexander Pyhalov self.user = user or '' 15824bb1048SAlexander Pyhalov self.killed = False 15924bb1048SAlexander Pyhalov self.result = Result() 16024bb1048SAlexander Pyhalov 16124bb1048SAlexander Pyhalov def __str__(self): 16224bb1048SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nUser: %s\n" % ( 16324bb1048SAlexander Pyhalov self.pathname, self.outputdir, self.timeout, self.user) 16424bb1048SAlexander Pyhalov 16524bb1048SAlexander Pyhalov def kill_cmd(self, proc): 16624bb1048SAlexander Pyhalov """ 16724bb1048SAlexander Pyhalov Kill a running command due to timeout, or ^C from the keyboard. If 16824bb1048SAlexander Pyhalov sudo is required, this user was verified previously. 16924bb1048SAlexander Pyhalov """ 17024bb1048SAlexander Pyhalov self.killed = True 17124bb1048SAlexander Pyhalov do_sudo = len(self.user) != 0 17224bb1048SAlexander Pyhalov signal = '-TERM' 17324bb1048SAlexander Pyhalov 17424bb1048SAlexander Pyhalov cmd = [SUDO, KILL, signal, str(proc.pid)] 17524bb1048SAlexander Pyhalov if not do_sudo: 17624bb1048SAlexander Pyhalov del cmd[0] 17724bb1048SAlexander Pyhalov 17824bb1048SAlexander Pyhalov try: 17924bb1048SAlexander Pyhalov kp = Popen(cmd) 18024bb1048SAlexander Pyhalov kp.wait() 18124bb1048SAlexander Pyhalov except: 18224bb1048SAlexander Pyhalov pass 18324bb1048SAlexander Pyhalov 18424bb1048SAlexander Pyhalov def update_cmd_privs(self, cmd, user): 18524bb1048SAlexander Pyhalov """ 18624bb1048SAlexander Pyhalov If a user has been specified to run this Cmd and we're not already 18724bb1048SAlexander Pyhalov running as that user, prepend the appropriate sudo command to run 18824bb1048SAlexander Pyhalov as that user. 18924bb1048SAlexander Pyhalov """ 19024bb1048SAlexander Pyhalov me = getpwuid(os.getuid()) 19124bb1048SAlexander Pyhalov 19224bb1048SAlexander Pyhalov if not user or user is me: 19324bb1048SAlexander Pyhalov return cmd 19424bb1048SAlexander Pyhalov 19524bb1048SAlexander Pyhalov ret = '%s -E -u %s %s' % (SUDO, user, cmd) 19624bb1048SAlexander Pyhalov return ret.split(' ') 19724bb1048SAlexander Pyhalov 19824bb1048SAlexander Pyhalov def collect_output(self, proc): 19924bb1048SAlexander Pyhalov """ 20024bb1048SAlexander Pyhalov Read from stdout/stderr as data becomes available, until the 20124bb1048SAlexander Pyhalov process is no longer running. Return the lines from the stdout and 20224bb1048SAlexander Pyhalov stderr Output objects. 20324bb1048SAlexander Pyhalov """ 20424bb1048SAlexander Pyhalov out = Output(proc.stdout) 20524bb1048SAlexander Pyhalov err = Output(proc.stderr) 20624bb1048SAlexander Pyhalov res = [] 20724bb1048SAlexander Pyhalov while proc.returncode is None: 20824bb1048SAlexander Pyhalov proc.poll() 20924bb1048SAlexander Pyhalov res = select([out, err], [], [], .1) 21024bb1048SAlexander Pyhalov for fd in res[0]: 21124bb1048SAlexander Pyhalov fd.read() 21224bb1048SAlexander Pyhalov for fd in res[0]: 21324bb1048SAlexander Pyhalov fd.read(drain=1) 21424bb1048SAlexander Pyhalov 21524bb1048SAlexander Pyhalov return out.lines, err.lines 21624bb1048SAlexander Pyhalov 21724bb1048SAlexander Pyhalov def run(self, options): 21824bb1048SAlexander Pyhalov """ 21924bb1048SAlexander Pyhalov This is the main function that runs each individual test. 22024bb1048SAlexander Pyhalov Determine whether or not the command requires sudo, and modify it 22124bb1048SAlexander Pyhalov if needed. Run the command, and update the result object. 22224bb1048SAlexander Pyhalov """ 22324bb1048SAlexander Pyhalov if options.dryrun is True: 224*83d13c2eSAlexander Pyhalov print(self) 22524bb1048SAlexander Pyhalov return 22624bb1048SAlexander Pyhalov 22724bb1048SAlexander Pyhalov privcmd = self.update_cmd_privs(self.pathname, self.user) 22824bb1048SAlexander Pyhalov try: 22924bb1048SAlexander Pyhalov old = os.umask(0) 23024bb1048SAlexander Pyhalov if not os.path.isdir(self.outputdir): 231*83d13c2eSAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 23224bb1048SAlexander Pyhalov os.umask(old) 233*83d13c2eSAlexander Pyhalov except OSError as e: 23424bb1048SAlexander Pyhalov fail('%s' % e) 23524bb1048SAlexander Pyhalov 23624bb1048SAlexander Pyhalov try: 23724bb1048SAlexander Pyhalov self.result.starttime = time() 238*83d13c2eSAlexander Pyhalov proc = Popen(privcmd, stdout=PIPE, stderr=PIPE, 239*83d13c2eSAlexander Pyhalov universal_newlines=True) 24024bb1048SAlexander Pyhalov t = Timer(int(self.timeout), self.kill_cmd, [proc]) 24124bb1048SAlexander Pyhalov t.start() 24224bb1048SAlexander Pyhalov self.result.stdout, self.result.stderr = self.collect_output(proc) 24324bb1048SAlexander Pyhalov except KeyboardInterrupt: 24424bb1048SAlexander Pyhalov self.kill_cmd(proc) 24524bb1048SAlexander Pyhalov fail('\nRun terminated at user request.') 24624bb1048SAlexander Pyhalov finally: 24724bb1048SAlexander Pyhalov t.cancel() 24824bb1048SAlexander Pyhalov 24924bb1048SAlexander Pyhalov self.result.done(proc, self.killed) 25024bb1048SAlexander Pyhalov 25124bb1048SAlexander Pyhalov def skip(self): 25224bb1048SAlexander Pyhalov """ 25324bb1048SAlexander Pyhalov Initialize enough of the test result that we can log a skipped 25424bb1048SAlexander Pyhalov command. 25524bb1048SAlexander Pyhalov """ 25624bb1048SAlexander Pyhalov Result.total += 1 25724bb1048SAlexander Pyhalov Result.runresults['SKIP'] += 1 25824bb1048SAlexander Pyhalov self.result.stdout = self.result.stderr = [] 25924bb1048SAlexander Pyhalov self.result.starttime = time() 26024bb1048SAlexander Pyhalov m, s = divmod(time() - self.result.starttime, 60) 26124bb1048SAlexander Pyhalov self.result.runtime = '%02d:%02d' % (m, s) 26224bb1048SAlexander Pyhalov self.result.result = 'SKIP' 26324bb1048SAlexander Pyhalov 26424bb1048SAlexander Pyhalov def log(self, logger, options): 26524bb1048SAlexander Pyhalov """ 26624bb1048SAlexander Pyhalov This function is responsible for writing all output. This includes 26724bb1048SAlexander Pyhalov the console output, the logfile of all results (with timestamped 26824bb1048SAlexander Pyhalov merged stdout and stderr), and for each test, the unmodified 26924bb1048SAlexander Pyhalov stdout/stderr/merged in it's own file. 27024bb1048SAlexander Pyhalov """ 27124bb1048SAlexander Pyhalov if logger is None: 27224bb1048SAlexander Pyhalov return 27324bb1048SAlexander Pyhalov 27424bb1048SAlexander Pyhalov logname = getpwuid(os.getuid()).pw_name 27524bb1048SAlexander Pyhalov user = ' (run as %s)' % (self.user if len(self.user) else logname) 27624bb1048SAlexander Pyhalov msga = 'Test: %s%s ' % (self.pathname, user) 27724bb1048SAlexander Pyhalov msgb = '[%s] [%s]' % (self.result.runtime, self.result.result) 27824bb1048SAlexander Pyhalov pad = ' ' * (80 - (len(msga) + len(msgb))) 27924bb1048SAlexander Pyhalov 28024bb1048SAlexander Pyhalov # If -q is specified, only print a line for tests that didn't pass. 28124bb1048SAlexander Pyhalov # This means passing tests need to be logged as DEBUG, or the one 28224bb1048SAlexander Pyhalov # line summary will only be printed in the logfile for failures. 28324bb1048SAlexander Pyhalov if not options.quiet: 28424bb1048SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 28524bb1048SAlexander Pyhalov elif self.result.result is not 'PASS': 28624bb1048SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 28724bb1048SAlexander Pyhalov else: 28824bb1048SAlexander Pyhalov logger.debug('%s%s%s' % (msga, pad, msgb)) 28924bb1048SAlexander Pyhalov 29024bb1048SAlexander Pyhalov lines = sorted(self.result.stdout + self.result.stderr, 291*83d13c2eSAlexander Pyhalov key=lambda x: x[0]) 29224bb1048SAlexander Pyhalov 29324bb1048SAlexander Pyhalov for dt, line in lines: 29424bb1048SAlexander Pyhalov logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line)) 29524bb1048SAlexander Pyhalov 29624bb1048SAlexander Pyhalov if len(self.result.stdout): 29724bb1048SAlexander Pyhalov with open(os.path.join(self.outputdir, 'stdout'), 'w') as out: 29824bb1048SAlexander Pyhalov for _, line in self.result.stdout: 299*83d13c2eSAlexander Pyhalov out.write('%s\n' % line) 30024bb1048SAlexander Pyhalov if len(self.result.stderr): 30124bb1048SAlexander Pyhalov with open(os.path.join(self.outputdir, 'stderr'), 'w') as err: 30224bb1048SAlexander Pyhalov for _, line in self.result.stderr: 303*83d13c2eSAlexander Pyhalov err.write('%s\n' % line) 30424bb1048SAlexander Pyhalov if len(self.result.stdout) and len(self.result.stderr): 30524bb1048SAlexander Pyhalov with open(os.path.join(self.outputdir, 'merged'), 'w') as merged: 30624bb1048SAlexander Pyhalov for _, line in lines: 307*83d13c2eSAlexander Pyhalov merged.write('%s\n' % line) 30824bb1048SAlexander Pyhalov 30924bb1048SAlexander Pyhalov 31024bb1048SAlexander Pyhalovclass Test(Cmd): 31124bb1048SAlexander Pyhalov props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post', 31224bb1048SAlexander Pyhalov 'post_user'] 31324bb1048SAlexander Pyhalov 31424bb1048SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 31524bb1048SAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None): 31624bb1048SAlexander Pyhalov super(Test, self).__init__(pathname, outputdir, timeout, user) 31724bb1048SAlexander Pyhalov self.pre = pre or '' 31824bb1048SAlexander Pyhalov self.pre_user = pre_user or '' 31924bb1048SAlexander Pyhalov self.post = post or '' 32024bb1048SAlexander Pyhalov self.post_user = post_user or '' 32124bb1048SAlexander Pyhalov 32224bb1048SAlexander Pyhalov def __str__(self): 32324bb1048SAlexander Pyhalov post_user = pre_user = '' 32424bb1048SAlexander Pyhalov if len(self.pre_user): 32524bb1048SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 32624bb1048SAlexander Pyhalov if len(self.post_user): 32724bb1048SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 32824bb1048SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nPre: %s%s\nPost: " \ 32924bb1048SAlexander Pyhalov "%s%s\nUser: %s\n" % (self.pathname, self.outputdir, 33024bb1048SAlexander Pyhalov self.timeout, self.pre, pre_user, self.post, post_user, 33124bb1048SAlexander Pyhalov self.user) 33224bb1048SAlexander Pyhalov 33324bb1048SAlexander Pyhalov def verify(self, logger): 33424bb1048SAlexander Pyhalov """ 33524bb1048SAlexander Pyhalov Check the pre/post scripts, user and Test. Omit the Test from this 33624bb1048SAlexander Pyhalov run if there are any problems. 33724bb1048SAlexander Pyhalov """ 33824bb1048SAlexander Pyhalov files = [self.pre, self.pathname, self.post] 33924bb1048SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 34024bb1048SAlexander Pyhalov 34124bb1048SAlexander Pyhalov for f in [f for f in files if len(f)]: 34224bb1048SAlexander Pyhalov if not verify_file(f): 34324bb1048SAlexander Pyhalov logger.info("Warning: Test '%s' not added to this run because" 34424bb1048SAlexander Pyhalov " it failed verification." % f) 34524bb1048SAlexander Pyhalov return False 34624bb1048SAlexander Pyhalov 34724bb1048SAlexander Pyhalov for user in [user for user in users if len(user)]: 34824bb1048SAlexander Pyhalov if not verify_user(user, logger): 34924bb1048SAlexander Pyhalov logger.info("Not adding Test '%s' to this run." % 35024bb1048SAlexander Pyhalov self.pathname) 35124bb1048SAlexander Pyhalov return False 35224bb1048SAlexander Pyhalov 35324bb1048SAlexander Pyhalov return True 35424bb1048SAlexander Pyhalov 35524bb1048SAlexander Pyhalov def run(self, logger, options): 35624bb1048SAlexander Pyhalov """ 35724bb1048SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 35824bb1048SAlexander Pyhalov doesn't pass, skip this Test. Run the post script regardless. 35924bb1048SAlexander Pyhalov """ 36024bb1048SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir, 36124bb1048SAlexander Pyhalov os.path.basename(self.pre)), timeout=self.timeout, 36224bb1048SAlexander Pyhalov user=self.pre_user) 36324bb1048SAlexander Pyhalov test = Cmd(self.pathname, outputdir=self.outputdir, 36424bb1048SAlexander Pyhalov timeout=self.timeout, user=self.user) 36524bb1048SAlexander Pyhalov posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir, 36624bb1048SAlexander Pyhalov os.path.basename(self.post)), timeout=self.timeout, 36724bb1048SAlexander Pyhalov user=self.post_user) 36824bb1048SAlexander Pyhalov 36924bb1048SAlexander Pyhalov cont = True 37024bb1048SAlexander Pyhalov if len(pretest.pathname): 37124bb1048SAlexander Pyhalov pretest.run(options) 37224bb1048SAlexander Pyhalov cont = pretest.result.result is 'PASS' 37324bb1048SAlexander Pyhalov pretest.log(logger, options) 37424bb1048SAlexander Pyhalov 37524bb1048SAlexander Pyhalov if cont: 37624bb1048SAlexander Pyhalov test.run(options) 37724bb1048SAlexander Pyhalov else: 37824bb1048SAlexander Pyhalov test.skip() 37924bb1048SAlexander Pyhalov 38024bb1048SAlexander Pyhalov test.log(logger, options) 38124bb1048SAlexander Pyhalov 38224bb1048SAlexander Pyhalov if len(posttest.pathname): 38324bb1048SAlexander Pyhalov posttest.run(options) 38424bb1048SAlexander Pyhalov posttest.log(logger, options) 38524bb1048SAlexander Pyhalov 38624bb1048SAlexander Pyhalov 38724bb1048SAlexander Pyhalovclass TestGroup(Test): 38824bb1048SAlexander Pyhalov props = Test.props + ['tests'] 38924bb1048SAlexander Pyhalov 39024bb1048SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 39124bb1048SAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None, 39224bb1048SAlexander Pyhalov tests=None): 39324bb1048SAlexander Pyhalov super(TestGroup, self).__init__(pathname, outputdir, timeout, user, 39424bb1048SAlexander Pyhalov pre, pre_user, post, post_user) 39524bb1048SAlexander Pyhalov self.tests = tests or [] 39624bb1048SAlexander Pyhalov 39724bb1048SAlexander Pyhalov def __str__(self): 39824bb1048SAlexander Pyhalov post_user = pre_user = '' 39924bb1048SAlexander Pyhalov if len(self.pre_user): 40024bb1048SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 40124bb1048SAlexander Pyhalov if len(self.post_user): 40224bb1048SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 40324bb1048SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \ 40424bb1048SAlexander Pyhalov "Pre: %s%s\nPost: %s%s\nUser: %s\n" % (self.pathname, 40524bb1048SAlexander Pyhalov self.outputdir, self.tests, self.timeout, self.pre, pre_user, 40624bb1048SAlexander Pyhalov self.post, post_user, self.user) 40724bb1048SAlexander Pyhalov 40824bb1048SAlexander Pyhalov def verify(self, logger): 40924bb1048SAlexander Pyhalov """ 41024bb1048SAlexander Pyhalov Check the pre/post scripts, user and tests in this TestGroup. Omit 41124bb1048SAlexander Pyhalov the TestGroup entirely, or simply delete the relevant tests in the 41224bb1048SAlexander Pyhalov group, if that's all that's required. 41324bb1048SAlexander Pyhalov """ 41424bb1048SAlexander Pyhalov # If the pre or post scripts are relative pathnames, convert to 41524bb1048SAlexander Pyhalov # absolute, so they stand a chance of passing verification. 41624bb1048SAlexander Pyhalov if len(self.pre) and not os.path.isabs(self.pre): 41724bb1048SAlexander Pyhalov self.pre = os.path.join(self.pathname, self.pre) 41824bb1048SAlexander Pyhalov if len(self.post) and not os.path.isabs(self.post): 41924bb1048SAlexander Pyhalov self.post = os.path.join(self.pathname, self.post) 42024bb1048SAlexander Pyhalov 42124bb1048SAlexander Pyhalov auxfiles = [self.pre, self.post] 42224bb1048SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 42324bb1048SAlexander Pyhalov 42424bb1048SAlexander Pyhalov for f in [f for f in auxfiles if len(f)]: 42524bb1048SAlexander Pyhalov if self.pathname != os.path.dirname(f): 42624bb1048SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 42724bb1048SAlexander Pyhalov "Auxiliary script '%s' exists in a different " 42824bb1048SAlexander Pyhalov "directory." % (self.pathname, f)) 42924bb1048SAlexander Pyhalov return False 43024bb1048SAlexander Pyhalov 43124bb1048SAlexander Pyhalov if not verify_file(f): 43224bb1048SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 43324bb1048SAlexander Pyhalov "Auxiliary script '%s' failed verification." % 43424bb1048SAlexander Pyhalov (self.pathname, f)) 43524bb1048SAlexander Pyhalov return False 43624bb1048SAlexander Pyhalov 43724bb1048SAlexander Pyhalov for user in [user for user in users if len(user)]: 43824bb1048SAlexander Pyhalov if not verify_user(user, logger): 43924bb1048SAlexander Pyhalov logger.info("Not adding TestGroup '%s' to this run." % 44024bb1048SAlexander Pyhalov self.pathname) 44124bb1048SAlexander Pyhalov return False 44224bb1048SAlexander Pyhalov 44324bb1048SAlexander Pyhalov # If one of the tests is invalid, delete it, log it, and drive on. 44424bb1048SAlexander Pyhalov for test in self.tests: 44524bb1048SAlexander Pyhalov if not verify_file(os.path.join(self.pathname, test)): 44624bb1048SAlexander Pyhalov del self.tests[self.tests.index(test)] 44724bb1048SAlexander Pyhalov logger.info("Warning: Test '%s' removed from TestGroup '%s' " 44824bb1048SAlexander Pyhalov "because it failed verification." % (test, 44924bb1048SAlexander Pyhalov self.pathname)) 45024bb1048SAlexander Pyhalov 45124bb1048SAlexander Pyhalov return len(self.tests) is not 0 45224bb1048SAlexander Pyhalov 45324bb1048SAlexander Pyhalov def run(self, logger, options): 45424bb1048SAlexander Pyhalov """ 45524bb1048SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 45624bb1048SAlexander Pyhalov doesn't pass, skip all the tests in this TestGroup. Run the post 45724bb1048SAlexander Pyhalov script regardless. 45824bb1048SAlexander Pyhalov """ 45924bb1048SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir, 46024bb1048SAlexander Pyhalov os.path.basename(self.pre)), timeout=self.timeout, 46124bb1048SAlexander Pyhalov user=self.pre_user) 46224bb1048SAlexander Pyhalov posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir, 46324bb1048SAlexander Pyhalov os.path.basename(self.post)), timeout=self.timeout, 46424bb1048SAlexander Pyhalov user=self.post_user) 46524bb1048SAlexander Pyhalov 46624bb1048SAlexander Pyhalov cont = True 46724bb1048SAlexander Pyhalov if len(pretest.pathname): 46824bb1048SAlexander Pyhalov pretest.run(options) 46924bb1048SAlexander Pyhalov cont = pretest.result.result is 'PASS' 47024bb1048SAlexander Pyhalov pretest.log(logger, options) 47124bb1048SAlexander Pyhalov 47224bb1048SAlexander Pyhalov for fname in self.tests: 47324bb1048SAlexander Pyhalov test = Cmd(os.path.join(self.pathname, fname), 47424bb1048SAlexander Pyhalov outputdir=os.path.join(self.outputdir, fname), 47524bb1048SAlexander Pyhalov timeout=self.timeout, user=self.user) 47624bb1048SAlexander Pyhalov if cont: 47724bb1048SAlexander Pyhalov test.run(options) 47824bb1048SAlexander Pyhalov else: 47924bb1048SAlexander Pyhalov test.skip() 48024bb1048SAlexander Pyhalov 48124bb1048SAlexander Pyhalov test.log(logger, options) 48224bb1048SAlexander Pyhalov 48324bb1048SAlexander Pyhalov if len(posttest.pathname): 48424bb1048SAlexander Pyhalov posttest.run(options) 48524bb1048SAlexander Pyhalov posttest.log(logger, options) 48624bb1048SAlexander Pyhalov 48724bb1048SAlexander Pyhalov 48824bb1048SAlexander Pyhalovclass TestRun(object): 48924bb1048SAlexander Pyhalov props = ['quiet', 'outputdir'] 49024bb1048SAlexander Pyhalov 49124bb1048SAlexander Pyhalov def __init__(self, options): 49224bb1048SAlexander Pyhalov self.tests = {} 49324bb1048SAlexander Pyhalov self.testgroups = {} 49424bb1048SAlexander Pyhalov self.starttime = time() 49524bb1048SAlexander Pyhalov self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') 49624bb1048SAlexander Pyhalov self.outputdir = os.path.join(options.outputdir, self.timestamp) 49724bb1048SAlexander Pyhalov self.logger = self.setup_logging(options) 49824bb1048SAlexander Pyhalov self.defaults = [ 49924bb1048SAlexander Pyhalov ('outputdir', BASEDIR), 50024bb1048SAlexander Pyhalov ('quiet', False), 50124bb1048SAlexander Pyhalov ('timeout', 60), 50224bb1048SAlexander Pyhalov ('user', ''), 50324bb1048SAlexander Pyhalov ('pre', ''), 50424bb1048SAlexander Pyhalov ('pre_user', ''), 50524bb1048SAlexander Pyhalov ('post', ''), 50624bb1048SAlexander Pyhalov ('post_user', '') 50724bb1048SAlexander Pyhalov ] 50824bb1048SAlexander Pyhalov 50924bb1048SAlexander Pyhalov def __str__(self): 51024bb1048SAlexander Pyhalov s = 'TestRun:\n outputdir: %s\n' % self.outputdir 51124bb1048SAlexander Pyhalov s += 'TESTS:\n' 51224bb1048SAlexander Pyhalov for key in sorted(self.tests.keys()): 51324bb1048SAlexander Pyhalov s += '%s%s' % (self.tests[key].__str__(), '\n') 51424bb1048SAlexander Pyhalov s += 'TESTGROUPS:\n' 51524bb1048SAlexander Pyhalov for key in sorted(self.testgroups.keys()): 51624bb1048SAlexander Pyhalov s += '%s%s' % (self.testgroups[key].__str__(), '\n') 51724bb1048SAlexander Pyhalov return s 51824bb1048SAlexander Pyhalov 51924bb1048SAlexander Pyhalov def addtest(self, pathname, options): 52024bb1048SAlexander Pyhalov """ 52124bb1048SAlexander Pyhalov Create a new Test, and apply any properties that were passed in 52224bb1048SAlexander Pyhalov from the command line. If it passes verification, add it to the 52324bb1048SAlexander Pyhalov TestRun. 52424bb1048SAlexander Pyhalov """ 52524bb1048SAlexander Pyhalov test = Test(pathname) 52624bb1048SAlexander Pyhalov for prop in Test.props: 52724bb1048SAlexander Pyhalov setattr(test, prop, getattr(options, prop)) 52824bb1048SAlexander Pyhalov 52924bb1048SAlexander Pyhalov if test.verify(self.logger): 53024bb1048SAlexander Pyhalov self.tests[pathname] = test 53124bb1048SAlexander Pyhalov 53224bb1048SAlexander Pyhalov def addtestgroup(self, dirname, filenames, options): 53324bb1048SAlexander Pyhalov """ 53424bb1048SAlexander Pyhalov Create a new TestGroup, and apply any properties that were passed 53524bb1048SAlexander Pyhalov in from the command line. If it passes verification, add it to the 53624bb1048SAlexander Pyhalov TestRun. 53724bb1048SAlexander Pyhalov """ 53824bb1048SAlexander Pyhalov if dirname not in self.testgroups: 53924bb1048SAlexander Pyhalov testgroup = TestGroup(dirname) 54024bb1048SAlexander Pyhalov for prop in Test.props: 54124bb1048SAlexander Pyhalov setattr(testgroup, prop, getattr(options, prop)) 54224bb1048SAlexander Pyhalov 54324bb1048SAlexander Pyhalov # Prevent pre/post scripts from running as regular tests 54424bb1048SAlexander Pyhalov for f in [testgroup.pre, testgroup.post]: 54524bb1048SAlexander Pyhalov if f in filenames: 54624bb1048SAlexander Pyhalov del filenames[filenames.index(f)] 54724bb1048SAlexander Pyhalov 54824bb1048SAlexander Pyhalov self.testgroups[dirname] = testgroup 54924bb1048SAlexander Pyhalov self.testgroups[dirname].tests = sorted(filenames) 55024bb1048SAlexander Pyhalov 55124bb1048SAlexander Pyhalov testgroup.verify(self.logger) 55224bb1048SAlexander Pyhalov 55324bb1048SAlexander Pyhalov def read(self, logger, options): 55424bb1048SAlexander Pyhalov """ 55524bb1048SAlexander Pyhalov Read in the specified runfile, and apply the TestRun properties 55624bb1048SAlexander Pyhalov listed in the 'DEFAULT' section to our TestRun. Then read each 55724bb1048SAlexander Pyhalov section, and apply the appropriate properties to the Test or 55824bb1048SAlexander Pyhalov TestGroup. Properties from individual sections override those set 55924bb1048SAlexander Pyhalov in the 'DEFAULT' section. If the Test or TestGroup passes 56024bb1048SAlexander Pyhalov verification, add it to the TestRun. 56124bb1048SAlexander Pyhalov """ 562*83d13c2eSAlexander Pyhalov config = configparser.RawConfigParser() 56324bb1048SAlexander Pyhalov if not len(config.read(options.runfile)): 56424bb1048SAlexander Pyhalov fail("Coulnd't read config file %s" % options.runfile) 56524bb1048SAlexander Pyhalov 56624bb1048SAlexander Pyhalov for opt in TestRun.props: 56724bb1048SAlexander Pyhalov if config.has_option('DEFAULT', opt): 56824bb1048SAlexander Pyhalov setattr(self, opt, config.get('DEFAULT', opt)) 56924bb1048SAlexander Pyhalov self.outputdir = os.path.join(self.outputdir, self.timestamp) 57024bb1048SAlexander Pyhalov 57124bb1048SAlexander Pyhalov for section in config.sections(): 57224bb1048SAlexander Pyhalov if 'tests' in config.options(section): 57324bb1048SAlexander Pyhalov testgroup = TestGroup(section) 57424bb1048SAlexander Pyhalov for prop in TestGroup.props: 57524bb1048SAlexander Pyhalov try: 57624bb1048SAlexander Pyhalov setattr(testgroup, prop, config.get('DEFAULT', prop)) 57724bb1048SAlexander Pyhalov setattr(testgroup, prop, config.get(section, prop)) 57824bb1048SAlexander Pyhalov except ConfigParser.NoOptionError: 57924bb1048SAlexander Pyhalov pass 58024bb1048SAlexander Pyhalov 58124bb1048SAlexander Pyhalov # Repopulate tests using eval to convert the string to a list 58224bb1048SAlexander Pyhalov testgroup.tests = eval(config.get(section, 'tests')) 58324bb1048SAlexander Pyhalov 58424bb1048SAlexander Pyhalov if testgroup.verify(logger): 58524bb1048SAlexander Pyhalov self.testgroups[section] = testgroup 58624bb1048SAlexander Pyhalov else: 58724bb1048SAlexander Pyhalov test = Test(section) 58824bb1048SAlexander Pyhalov for prop in Test.props: 58924bb1048SAlexander Pyhalov try: 59024bb1048SAlexander Pyhalov setattr(test, prop, config.get('DEFAULT', prop)) 59124bb1048SAlexander Pyhalov setattr(test, prop, config.get(section, prop)) 59224bb1048SAlexander Pyhalov except ConfigParser.NoOptionError: 59324bb1048SAlexander Pyhalov pass 59424bb1048SAlexander Pyhalov if test.verify(logger): 59524bb1048SAlexander Pyhalov self.tests[section] = test 59624bb1048SAlexander Pyhalov 59724bb1048SAlexander Pyhalov def write(self, options): 59824bb1048SAlexander Pyhalov """ 59924bb1048SAlexander Pyhalov Create a configuration file for editing and later use. The 60024bb1048SAlexander Pyhalov 'DEFAULT' section of the config file is created from the 60124bb1048SAlexander Pyhalov properties that were specified on the command line. Tests are 60224bb1048SAlexander Pyhalov simply added as sections that inherit everything from the 60324bb1048SAlexander Pyhalov 'DEFAULT' section. TestGroups are the same, except they get an 60424bb1048SAlexander Pyhalov option including all the tests to run in that directory. 60524bb1048SAlexander Pyhalov """ 60624bb1048SAlexander Pyhalov 60724bb1048SAlexander Pyhalov defaults = dict([(prop, getattr(options, prop)) for prop, _ in 60824bb1048SAlexander Pyhalov self.defaults]) 609*83d13c2eSAlexander Pyhalov config = configparser.RawConfigParser(defaults) 61024bb1048SAlexander Pyhalov 61124bb1048SAlexander Pyhalov for test in sorted(self.tests.keys()): 61224bb1048SAlexander Pyhalov config.add_section(test) 61324bb1048SAlexander Pyhalov 61424bb1048SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 61524bb1048SAlexander Pyhalov config.add_section(testgroup) 61624bb1048SAlexander Pyhalov config.set(testgroup, 'tests', self.testgroups[testgroup].tests) 61724bb1048SAlexander Pyhalov 61824bb1048SAlexander Pyhalov try: 61924bb1048SAlexander Pyhalov with open(options.template, 'w') as f: 62024bb1048SAlexander Pyhalov return config.write(f) 62124bb1048SAlexander Pyhalov except IOError: 62224bb1048SAlexander Pyhalov fail('Could not open \'%s\' for writing.' % options.template) 62324bb1048SAlexander Pyhalov 62424bb1048SAlexander Pyhalov def complete_outputdirs(self, options): 62524bb1048SAlexander Pyhalov """ 62624bb1048SAlexander Pyhalov Collect all the pathnames for Tests, and TestGroups. Work 62724bb1048SAlexander Pyhalov backwards one pathname component at a time, to create a unique 62824bb1048SAlexander Pyhalov directory name in which to deposit test output. Tests will be able 62924bb1048SAlexander Pyhalov to write output files directly in the newly modified outputdir. 63024bb1048SAlexander Pyhalov TestGroups will be able to create one subdirectory per test in the 63124bb1048SAlexander Pyhalov outputdir, and are guaranteed uniqueness because a group can only 63224bb1048SAlexander Pyhalov contain files in one directory. Pre and post tests will create a 63324bb1048SAlexander Pyhalov directory rooted at the outputdir of the Test or TestGroup in 63424bb1048SAlexander Pyhalov question for their output. 63524bb1048SAlexander Pyhalov """ 63624bb1048SAlexander Pyhalov done = False 63724bb1048SAlexander Pyhalov components = 0 638*83d13c2eSAlexander Pyhalov tmp_dict = dict(list(self.tests.items()) + list(self.testgroups.items())) 63924bb1048SAlexander Pyhalov total = len(tmp_dict) 64024bb1048SAlexander Pyhalov base = self.outputdir 64124bb1048SAlexander Pyhalov 64224bb1048SAlexander Pyhalov while not done: 64324bb1048SAlexander Pyhalov l = [] 64424bb1048SAlexander Pyhalov components -= 1 645*83d13c2eSAlexander Pyhalov for testfile in list(tmp_dict.keys()): 64624bb1048SAlexander Pyhalov uniq = '/'.join(testfile.split('/')[components:]).lstrip('/') 64724bb1048SAlexander Pyhalov if not uniq in l: 64824bb1048SAlexander Pyhalov l.append(uniq) 64924bb1048SAlexander Pyhalov tmp_dict[testfile].outputdir = os.path.join(base, uniq) 65024bb1048SAlexander Pyhalov else: 65124bb1048SAlexander Pyhalov break 65224bb1048SAlexander Pyhalov done = total == len(l) 65324bb1048SAlexander Pyhalov 65424bb1048SAlexander Pyhalov def setup_logging(self, options): 65524bb1048SAlexander Pyhalov """ 65624bb1048SAlexander Pyhalov Two loggers are set up here. The first is for the logfile which 65724bb1048SAlexander Pyhalov will contain one line summarizing the test, including the test 65824bb1048SAlexander Pyhalov name, result, and running time. This logger will also capture the 65924bb1048SAlexander Pyhalov timestamped combined stdout and stderr of each run. The second 66024bb1048SAlexander Pyhalov logger is optional console output, which will contain only the one 66124bb1048SAlexander Pyhalov line summary. The loggers are initialized at two different levels 66224bb1048SAlexander Pyhalov to facilitate segregating the output. 66324bb1048SAlexander Pyhalov """ 66424bb1048SAlexander Pyhalov if options.dryrun is True: 66524bb1048SAlexander Pyhalov return 66624bb1048SAlexander Pyhalov 66724bb1048SAlexander Pyhalov testlogger = logging.getLogger(__name__) 66824bb1048SAlexander Pyhalov testlogger.setLevel(logging.DEBUG) 66924bb1048SAlexander Pyhalov 67024bb1048SAlexander Pyhalov if options.cmd is not 'wrconfig': 67124bb1048SAlexander Pyhalov try: 67224bb1048SAlexander Pyhalov old = os.umask(0) 673*83d13c2eSAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 67424bb1048SAlexander Pyhalov os.umask(old) 675*83d13c2eSAlexander Pyhalov except OSError as e: 67624bb1048SAlexander Pyhalov fail('%s' % e) 67724bb1048SAlexander Pyhalov filename = os.path.join(self.outputdir, 'log') 67824bb1048SAlexander Pyhalov 67924bb1048SAlexander Pyhalov logfile = WatchedFileHandlerClosed(filename) 68024bb1048SAlexander Pyhalov logfile.setLevel(logging.DEBUG) 68124bb1048SAlexander Pyhalov logfilefmt = logging.Formatter('%(message)s') 68224bb1048SAlexander Pyhalov logfile.setFormatter(logfilefmt) 68324bb1048SAlexander Pyhalov testlogger.addHandler(logfile) 68424bb1048SAlexander Pyhalov 68524bb1048SAlexander Pyhalov cons = logging.StreamHandler() 68624bb1048SAlexander Pyhalov cons.setLevel(logging.INFO) 68724bb1048SAlexander Pyhalov consfmt = logging.Formatter('%(message)s') 68824bb1048SAlexander Pyhalov cons.setFormatter(consfmt) 68924bb1048SAlexander Pyhalov testlogger.addHandler(cons) 69024bb1048SAlexander Pyhalov 69124bb1048SAlexander Pyhalov return testlogger 69224bb1048SAlexander Pyhalov 69324bb1048SAlexander Pyhalov def run(self, options): 69424bb1048SAlexander Pyhalov """ 69524bb1048SAlexander Pyhalov Walk through all the Tests and TestGroups, calling run(). 69624bb1048SAlexander Pyhalov """ 69724bb1048SAlexander Pyhalov try: 69824bb1048SAlexander Pyhalov os.chdir(self.outputdir) 69924bb1048SAlexander Pyhalov except OSError: 70024bb1048SAlexander Pyhalov fail('Could not change to directory %s' % self.outputdir) 70124bb1048SAlexander Pyhalov for test in sorted(self.tests.keys()): 70224bb1048SAlexander Pyhalov self.tests[test].run(self.logger, options) 70324bb1048SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 70424bb1048SAlexander Pyhalov self.testgroups[testgroup].run(self.logger, options) 70524bb1048SAlexander Pyhalov 70624bb1048SAlexander Pyhalov def summary(self): 70724bb1048SAlexander Pyhalov if Result.total is 0: 70824bb1048SAlexander Pyhalov return 70924bb1048SAlexander Pyhalov 710*83d13c2eSAlexander Pyhalov print('\nResults Summary') 711*83d13c2eSAlexander Pyhalov for key in list(Result.runresults.keys()): 71224bb1048SAlexander Pyhalov if Result.runresults[key] is not 0: 713*83d13c2eSAlexander Pyhalov print('%s\t% 4d' % (key, Result.runresults[key])) 71424bb1048SAlexander Pyhalov 71524bb1048SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 71624bb1048SAlexander Pyhalov h, m = divmod(m, 60) 717*83d13c2eSAlexander Pyhalov print('\nRunning Time:\t%02d:%02d:%02d' % (h, m, s)) 718*83d13c2eSAlexander Pyhalov print('Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) / 719*83d13c2eSAlexander Pyhalov float(Result.total)) * 100)) 720*83d13c2eSAlexander Pyhalov print('Log directory:\t%s' % self.outputdir) 72124bb1048SAlexander Pyhalov 72224bb1048SAlexander Pyhalov 72324bb1048SAlexander Pyhalovdef verify_file(pathname): 72424bb1048SAlexander Pyhalov """ 72524bb1048SAlexander Pyhalov Verify that the supplied pathname is an executable regular file. 72624bb1048SAlexander Pyhalov """ 72724bb1048SAlexander Pyhalov if os.path.isdir(pathname) or os.path.islink(pathname): 72824bb1048SAlexander Pyhalov return False 72924bb1048SAlexander Pyhalov 73024bb1048SAlexander Pyhalov if os.path.isfile(pathname) and os.access(pathname, os.X_OK): 73124bb1048SAlexander Pyhalov return True 73224bb1048SAlexander Pyhalov 73324bb1048SAlexander Pyhalov return False 73424bb1048SAlexander Pyhalov 73524bb1048SAlexander Pyhalov 73624bb1048SAlexander Pyhalovdef verify_user(user, logger): 73724bb1048SAlexander Pyhalov """ 73824bb1048SAlexander Pyhalov Verify that the specified user exists on this system, and can execute 73924bb1048SAlexander Pyhalov sudo without being prompted for a password. 74024bb1048SAlexander Pyhalov """ 74124bb1048SAlexander Pyhalov testcmd = [SUDO, '-n', '-u', user, TRUE] 74224bb1048SAlexander Pyhalov can_sudo = exists = True 74324bb1048SAlexander Pyhalov 74424bb1048SAlexander Pyhalov if user in Cmd.verified_users: 74524bb1048SAlexander Pyhalov return True 74624bb1048SAlexander Pyhalov 74724bb1048SAlexander Pyhalov try: 74824bb1048SAlexander Pyhalov _ = getpwnam(user) 74924bb1048SAlexander Pyhalov except KeyError: 75024bb1048SAlexander Pyhalov exists = False 75124bb1048SAlexander Pyhalov logger.info("Warning: user '%s' does not exist.", user) 75224bb1048SAlexander Pyhalov return False 75324bb1048SAlexander Pyhalov 75424bb1048SAlexander Pyhalov p = Popen(testcmd) 75524bb1048SAlexander Pyhalov p.wait() 75624bb1048SAlexander Pyhalov if p.returncode is not 0: 75724bb1048SAlexander Pyhalov logger.info("Warning: user '%s' cannot use passwordless sudo.", user) 75824bb1048SAlexander Pyhalov return False 75924bb1048SAlexander Pyhalov else: 76024bb1048SAlexander Pyhalov Cmd.verified_users.append(user) 76124bb1048SAlexander Pyhalov 76224bb1048SAlexander Pyhalov return True 76324bb1048SAlexander Pyhalov 76424bb1048SAlexander Pyhalov 76524bb1048SAlexander Pyhalovdef find_tests(testrun, options): 76624bb1048SAlexander Pyhalov """ 76724bb1048SAlexander Pyhalov For the given list of pathnames, add files as Tests. For directories, 76824bb1048SAlexander Pyhalov if do_groups is True, add the directory as a TestGroup. If False, 76924bb1048SAlexander Pyhalov recursively search for executable files. 77024bb1048SAlexander Pyhalov """ 77124bb1048SAlexander Pyhalov 77224bb1048SAlexander Pyhalov for p in sorted(options.pathnames): 77324bb1048SAlexander Pyhalov if os.path.isdir(p): 77424bb1048SAlexander Pyhalov for dirname, _, filenames in os.walk(p): 77524bb1048SAlexander Pyhalov if options.do_groups: 77624bb1048SAlexander Pyhalov testrun.addtestgroup(dirname, filenames, options) 77724bb1048SAlexander Pyhalov else: 77824bb1048SAlexander Pyhalov for f in sorted(filenames): 77924bb1048SAlexander Pyhalov testrun.addtest(os.path.join(dirname, f), options) 78024bb1048SAlexander Pyhalov else: 78124bb1048SAlexander Pyhalov testrun.addtest(p, options) 78224bb1048SAlexander Pyhalov 78324bb1048SAlexander Pyhalov 78424bb1048SAlexander Pyhalovdef fail(retstr, ret=1): 785*83d13c2eSAlexander Pyhalov print('%s: %s' % (argv[0], retstr)) 78624bb1048SAlexander Pyhalov exit(ret) 78724bb1048SAlexander Pyhalov 78824bb1048SAlexander Pyhalov 78924bb1048SAlexander Pyhalovdef options_cb(option, opt_str, value, parser): 79024bb1048SAlexander Pyhalov path_options = ['runfile', 'outputdir', 'template'] 79124bb1048SAlexander Pyhalov 79224bb1048SAlexander Pyhalov if option.dest is 'runfile' and '-w' in parser.rargs or \ 79324bb1048SAlexander Pyhalov option.dest is 'template' and '-c' in parser.rargs: 79424bb1048SAlexander Pyhalov fail('-c and -w are mutually exclusive.') 79524bb1048SAlexander Pyhalov 79624bb1048SAlexander Pyhalov if opt_str in parser.rargs: 79724bb1048SAlexander Pyhalov fail('%s may only be specified once.' % opt_str) 79824bb1048SAlexander Pyhalov 79924bb1048SAlexander Pyhalov if option.dest is 'runfile': 80024bb1048SAlexander Pyhalov parser.values.cmd = 'rdconfig' 80124bb1048SAlexander Pyhalov if option.dest is 'template': 80224bb1048SAlexander Pyhalov parser.values.cmd = 'wrconfig' 80324bb1048SAlexander Pyhalov 80424bb1048SAlexander Pyhalov setattr(parser.values, option.dest, value) 80524bb1048SAlexander Pyhalov if option.dest in path_options: 80624bb1048SAlexander Pyhalov setattr(parser.values, option.dest, os.path.abspath(value)) 80724bb1048SAlexander Pyhalov 80824bb1048SAlexander Pyhalov 80924bb1048SAlexander Pyhalovdef parse_args(): 81024bb1048SAlexander Pyhalov parser = OptionParser() 81124bb1048SAlexander Pyhalov parser.add_option('-c', action='callback', callback=options_cb, 81224bb1048SAlexander Pyhalov type='string', dest='runfile', metavar='runfile', 81324bb1048SAlexander Pyhalov help='Specify tests to run via config file.') 81424bb1048SAlexander Pyhalov parser.add_option('-d', action='store_true', default=False, dest='dryrun', 81524bb1048SAlexander Pyhalov help='Dry run. Print tests, but take no other action.') 81624bb1048SAlexander Pyhalov parser.add_option('-g', action='store_true', default=False, 81724bb1048SAlexander Pyhalov dest='do_groups', help='Make directories TestGroups.') 81824bb1048SAlexander Pyhalov parser.add_option('-o', action='callback', callback=options_cb, 81924bb1048SAlexander Pyhalov default=BASEDIR, dest='outputdir', type='string', 82024bb1048SAlexander Pyhalov metavar='outputdir', help='Specify an output directory.') 82124bb1048SAlexander Pyhalov parser.add_option('-p', action='callback', callback=options_cb, 82224bb1048SAlexander Pyhalov default='', dest='pre', metavar='script', 82324bb1048SAlexander Pyhalov type='string', help='Specify a pre script.') 82424bb1048SAlexander Pyhalov parser.add_option('-P', action='callback', callback=options_cb, 82524bb1048SAlexander Pyhalov default='', dest='post', metavar='script', 82624bb1048SAlexander Pyhalov type='string', help='Specify a post script.') 82724bb1048SAlexander Pyhalov parser.add_option('-q', action='store_true', default=False, dest='quiet', 82824bb1048SAlexander Pyhalov help='Silence on the console during a test run.') 82924bb1048SAlexander Pyhalov parser.add_option('-t', action='callback', callback=options_cb, default=60, 83024bb1048SAlexander Pyhalov dest='timeout', metavar='seconds', type='int', 83124bb1048SAlexander Pyhalov help='Timeout (in seconds) for an individual test.') 83224bb1048SAlexander Pyhalov parser.add_option('-u', action='callback', callback=options_cb, 83324bb1048SAlexander Pyhalov default='', dest='user', metavar='user', type='string', 83424bb1048SAlexander Pyhalov help='Specify a different user name to run as.') 83524bb1048SAlexander Pyhalov parser.add_option('-w', action='callback', callback=options_cb, 83624bb1048SAlexander Pyhalov default=None, dest='template', metavar='template', 83724bb1048SAlexander Pyhalov type='string', help='Create a new config file.') 83824bb1048SAlexander Pyhalov parser.add_option('-x', action='callback', callback=options_cb, default='', 83924bb1048SAlexander Pyhalov dest='pre_user', metavar='pre_user', type='string', 84024bb1048SAlexander Pyhalov help='Specify a user to execute the pre script.') 84124bb1048SAlexander Pyhalov parser.add_option('-X', action='callback', callback=options_cb, default='', 84224bb1048SAlexander Pyhalov dest='post_user', metavar='post_user', type='string', 84324bb1048SAlexander Pyhalov help='Specify a user to execute the post script.') 84424bb1048SAlexander Pyhalov (options, pathnames) = parser.parse_args() 84524bb1048SAlexander Pyhalov 84624bb1048SAlexander Pyhalov if not options.runfile and not options.template: 84724bb1048SAlexander Pyhalov options.cmd = 'runtests' 84824bb1048SAlexander Pyhalov 84924bb1048SAlexander Pyhalov if options.runfile and len(pathnames): 85024bb1048SAlexander Pyhalov fail('Extraneous arguments.') 85124bb1048SAlexander Pyhalov 85224bb1048SAlexander Pyhalov options.pathnames = [os.path.abspath(path) for path in pathnames] 85324bb1048SAlexander Pyhalov 85424bb1048SAlexander Pyhalov return options 85524bb1048SAlexander Pyhalov 85624bb1048SAlexander Pyhalov 85724bb1048SAlexander Pyhalovdef main(args): 85824bb1048SAlexander Pyhalov options = parse_args() 85924bb1048SAlexander Pyhalov testrun = TestRun(options) 86024bb1048SAlexander Pyhalov 86124bb1048SAlexander Pyhalov if options.cmd is 'runtests': 86224bb1048SAlexander Pyhalov find_tests(testrun, options) 86324bb1048SAlexander Pyhalov elif options.cmd is 'rdconfig': 86424bb1048SAlexander Pyhalov testrun.read(testrun.logger, options) 86524bb1048SAlexander Pyhalov elif options.cmd is 'wrconfig': 86624bb1048SAlexander Pyhalov find_tests(testrun, options) 86724bb1048SAlexander Pyhalov testrun.write(options) 86824bb1048SAlexander Pyhalov exit(0) 86924bb1048SAlexander Pyhalov else: 87024bb1048SAlexander Pyhalov fail('Unknown command specified') 87124bb1048SAlexander Pyhalov 87224bb1048SAlexander Pyhalov testrun.complete_outputdirs(options) 87324bb1048SAlexander Pyhalov testrun.run(options) 87424bb1048SAlexander Pyhalov testrun.summary() 87524bb1048SAlexander Pyhalov exit(0) 87624bb1048SAlexander Pyhalov 87724bb1048SAlexander Pyhalov 87824bb1048SAlexander Pyhalovif __name__ == '__main__': 87924bb1048SAlexander Pyhalov main(argv[1:]) 880