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