12fb4439dSAlexander Pyhalov#!@PYTHON@ 22fb4439dSAlexander Pyhalov 32fb4439dSAlexander Pyhalov# 42fb4439dSAlexander Pyhalov# This file and its contents are supplied under the terms of the 52fb4439dSAlexander Pyhalov# Common Development and Distribution License ("CDDL"), version 1.0. 62fb4439dSAlexander Pyhalov# You may only use this file in accordance with the terms of version 72fb4439dSAlexander Pyhalov# 1.0 of the CDDL. 82fb4439dSAlexander Pyhalov# 92fb4439dSAlexander Pyhalov# A full copy of the text of the CDDL should have accompanied this 102fb4439dSAlexander Pyhalov# source. A copy of the CDDL is also available via the Internet at 112fb4439dSAlexander Pyhalov# http://www.illumos.org/license/CDDL. 122fb4439dSAlexander Pyhalov# 132fb4439dSAlexander Pyhalov 142fb4439dSAlexander Pyhalov# 152fb4439dSAlexander Pyhalov# Copyright (c) 2012, 2015 by Delphix. All rights reserved. 162fb4439dSAlexander Pyhalov# 172fb4439dSAlexander Pyhalov 18*e16fe9a0SAlexander Pyhalovfrom __future__ import print_function 19*e16fe9a0SAlexander Pyhalovimport sys 20*e16fe9a0SAlexander PyhalovPY3 = sys.version_info[0] == 3 21*e16fe9a0SAlexander Pyhalov 22*e16fe9a0SAlexander Pyhalovif PY3: 23*e16fe9a0SAlexander Pyhalov import configparser 24*e16fe9a0SAlexander Pyhalovelse: 25*e16fe9a0SAlexander Pyhalov import ConfigParser as configparser 26*e16fe9a0SAlexander Pyhalov 272fb4439dSAlexander Pyhalovimport os 282fb4439dSAlexander Pyhalovimport logging 292fb4439dSAlexander Pyhalovfrom logging.handlers import WatchedFileHandler 302fb4439dSAlexander Pyhalovfrom datetime import datetime 312fb4439dSAlexander Pyhalovfrom optparse import OptionParser 322fb4439dSAlexander Pyhalovfrom pwd import getpwnam 332fb4439dSAlexander Pyhalovfrom pwd import getpwuid 342fb4439dSAlexander Pyhalovfrom select import select 352fb4439dSAlexander Pyhalovfrom subprocess import PIPE 362fb4439dSAlexander Pyhalovfrom subprocess import Popen 372fb4439dSAlexander Pyhalovfrom sys import argv 382fb4439dSAlexander Pyhalovfrom sys import exit 39*e16fe9a0SAlexander Pyhalovfrom sys import maxsize 402fb4439dSAlexander Pyhalovfrom threading import Timer 412fb4439dSAlexander Pyhalovfrom time import time 422fb4439dSAlexander Pyhalov 432fb4439dSAlexander PyhalovBASEDIR = '/var/tmp/test_results' 442fb4439dSAlexander PyhalovKILL = '/usr/bin/kill' 452fb4439dSAlexander PyhalovTRUE = '/usr/bin/true' 462fb4439dSAlexander PyhalovSUDO = '/usr/bin/sudo' 472fb4439dSAlexander Pyhalov 482fb4439dSAlexander Pyhalov# Custom class to reopen the log file in case it is forcibly closed by a test. 492fb4439dSAlexander Pyhalovclass WatchedFileHandlerClosed(WatchedFileHandler): 502fb4439dSAlexander Pyhalov """Watch files, including closed files. 512fb4439dSAlexander Pyhalov Similar to (and inherits from) logging.handler.WatchedFileHandler, 522fb4439dSAlexander Pyhalov except that IOErrors are handled by reopening the stream and retrying. 532fb4439dSAlexander Pyhalov This will be retried up to a configurable number of times before 542fb4439dSAlexander Pyhalov giving up, default 5. 552fb4439dSAlexander Pyhalov """ 562fb4439dSAlexander Pyhalov 572fb4439dSAlexander Pyhalov def __init__(self, filename, mode='a', encoding=None, delay=0, max_tries=5): 582fb4439dSAlexander Pyhalov self.max_tries = max_tries 592fb4439dSAlexander Pyhalov self.tries = 0 602fb4439dSAlexander Pyhalov WatchedFileHandler.__init__(self, filename, mode, encoding, delay) 612fb4439dSAlexander Pyhalov 622fb4439dSAlexander Pyhalov def emit(self, record): 632fb4439dSAlexander Pyhalov while True: 642fb4439dSAlexander Pyhalov try: 652fb4439dSAlexander Pyhalov WatchedFileHandler.emit(self, record) 662fb4439dSAlexander Pyhalov self.tries = 0 672fb4439dSAlexander Pyhalov return 682fb4439dSAlexander Pyhalov except IOError as err: 692fb4439dSAlexander Pyhalov if self.tries == self.max_tries: 702fb4439dSAlexander Pyhalov raise 712fb4439dSAlexander Pyhalov self.stream.close() 722fb4439dSAlexander Pyhalov self.stream = self._open() 732fb4439dSAlexander Pyhalov self.tries += 1 742fb4439dSAlexander Pyhalov 752fb4439dSAlexander Pyhalovclass Result(object): 762fb4439dSAlexander Pyhalov total = 0 772fb4439dSAlexander Pyhalov runresults = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0} 782fb4439dSAlexander Pyhalov 792fb4439dSAlexander Pyhalov def __init__(self): 802fb4439dSAlexander Pyhalov self.starttime = None 812fb4439dSAlexander Pyhalov self.returncode = None 822fb4439dSAlexander Pyhalov self.runtime = '' 832fb4439dSAlexander Pyhalov self.stdout = [] 842fb4439dSAlexander Pyhalov self.stderr = [] 852fb4439dSAlexander Pyhalov self.result = '' 862fb4439dSAlexander Pyhalov 872fb4439dSAlexander Pyhalov def done(self, proc, killed): 882fb4439dSAlexander Pyhalov """ 892fb4439dSAlexander Pyhalov Finalize the results of this Cmd. 902fb4439dSAlexander Pyhalov """ 912fb4439dSAlexander Pyhalov Result.total += 1 922fb4439dSAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 932fb4439dSAlexander Pyhalov self.runtime = '%02d:%02d' % (m, s) 942fb4439dSAlexander Pyhalov self.returncode = proc.returncode 952fb4439dSAlexander Pyhalov if killed: 962fb4439dSAlexander Pyhalov self.result = 'KILLED' 972fb4439dSAlexander Pyhalov Result.runresults['KILLED'] += 1 982fb4439dSAlexander Pyhalov elif self.returncode is 0: 992fb4439dSAlexander Pyhalov self.result = 'PASS' 1002fb4439dSAlexander Pyhalov Result.runresults['PASS'] += 1 1012fb4439dSAlexander Pyhalov elif self.returncode is not 0: 1022fb4439dSAlexander Pyhalov self.result = 'FAIL' 1032fb4439dSAlexander Pyhalov Result.runresults['FAIL'] += 1 1042fb4439dSAlexander Pyhalov 1052fb4439dSAlexander Pyhalov 1062fb4439dSAlexander Pyhalovclass Output(object): 1072fb4439dSAlexander Pyhalov """ 1082fb4439dSAlexander Pyhalov This class is a slightly modified version of the 'Stream' class found 1092fb4439dSAlexander Pyhalov here: http://goo.gl/aSGfv 1102fb4439dSAlexander Pyhalov """ 1112fb4439dSAlexander Pyhalov def __init__(self, stream): 1122fb4439dSAlexander Pyhalov self.stream = stream 1132fb4439dSAlexander Pyhalov self._buf = '' 1142fb4439dSAlexander Pyhalov self.lines = [] 1152fb4439dSAlexander Pyhalov 1162fb4439dSAlexander Pyhalov def fileno(self): 1172fb4439dSAlexander Pyhalov return self.stream.fileno() 1182fb4439dSAlexander Pyhalov 1192fb4439dSAlexander Pyhalov def read(self, drain=0): 1202fb4439dSAlexander Pyhalov """ 1212fb4439dSAlexander Pyhalov Read from the file descriptor. If 'drain' set, read until EOF. 1222fb4439dSAlexander Pyhalov """ 1232fb4439dSAlexander Pyhalov while self._read() is not None: 1242fb4439dSAlexander Pyhalov if not drain: 1252fb4439dSAlexander Pyhalov break 1262fb4439dSAlexander Pyhalov 1272fb4439dSAlexander Pyhalov def _read(self): 1282fb4439dSAlexander Pyhalov """ 1292fb4439dSAlexander Pyhalov Read up to 4k of data from this output stream. Collect the output 1302fb4439dSAlexander Pyhalov up to the last newline, and append it to any leftover data from a 1312fb4439dSAlexander Pyhalov previous call. The lines are stored as a (timestamp, data) tuple 1322fb4439dSAlexander Pyhalov for easy sorting/merging later. 1332fb4439dSAlexander Pyhalov """ 1342fb4439dSAlexander Pyhalov fd = self.fileno() 135*e16fe9a0SAlexander Pyhalov buf = os.read(fd, 4096).decode() 1362fb4439dSAlexander Pyhalov if not buf: 1372fb4439dSAlexander Pyhalov return None 1382fb4439dSAlexander Pyhalov if '\n' not in buf: 1392fb4439dSAlexander Pyhalov self._buf += buf 1402fb4439dSAlexander Pyhalov return [] 1412fb4439dSAlexander Pyhalov 1422fb4439dSAlexander Pyhalov buf = self._buf + buf 1432fb4439dSAlexander Pyhalov tmp, rest = buf.rsplit('\n', 1) 1442fb4439dSAlexander Pyhalov self._buf = rest 1452fb4439dSAlexander Pyhalov now = datetime.now() 1462fb4439dSAlexander Pyhalov rows = tmp.split('\n') 1472fb4439dSAlexander Pyhalov self.lines += [(now, r) for r in rows] 1482fb4439dSAlexander Pyhalov 1492fb4439dSAlexander Pyhalov 1502fb4439dSAlexander Pyhalovclass Cmd(object): 1512fb4439dSAlexander Pyhalov verified_users = [] 1522fb4439dSAlexander Pyhalov 1532fb4439dSAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None): 1542fb4439dSAlexander Pyhalov self.pathname = pathname 1552fb4439dSAlexander Pyhalov self.outputdir = outputdir or 'BASEDIR' 1562fb4439dSAlexander Pyhalov self.timeout = timeout or 60 1572fb4439dSAlexander Pyhalov self.user = user or '' 1582fb4439dSAlexander Pyhalov self.killed = False 1592fb4439dSAlexander Pyhalov self.result = Result() 1602fb4439dSAlexander Pyhalov 1612fb4439dSAlexander Pyhalov def __str__(self): 1622fb4439dSAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nUser: %s\n" % ( 1632fb4439dSAlexander Pyhalov self.pathname, self.outputdir, self.timeout, self.user) 1642fb4439dSAlexander Pyhalov 1652fb4439dSAlexander Pyhalov def kill_cmd(self, proc): 1662fb4439dSAlexander Pyhalov """ 1672fb4439dSAlexander Pyhalov Kill a running command due to timeout, or ^C from the keyboard. If 1682fb4439dSAlexander Pyhalov sudo is required, this user was verified previously. 1692fb4439dSAlexander Pyhalov """ 1702fb4439dSAlexander Pyhalov self.killed = True 1712fb4439dSAlexander Pyhalov do_sudo = len(self.user) != 0 1722fb4439dSAlexander Pyhalov signal = '-TERM' 1732fb4439dSAlexander Pyhalov 1742fb4439dSAlexander Pyhalov cmd = [SUDO, KILL, signal, str(proc.pid)] 1752fb4439dSAlexander Pyhalov if not do_sudo: 1762fb4439dSAlexander Pyhalov del cmd[0] 1772fb4439dSAlexander Pyhalov 1782fb4439dSAlexander Pyhalov try: 1792fb4439dSAlexander Pyhalov kp = Popen(cmd) 1802fb4439dSAlexander Pyhalov kp.wait() 1812fb4439dSAlexander Pyhalov except: 1822fb4439dSAlexander Pyhalov pass 1832fb4439dSAlexander Pyhalov 1842fb4439dSAlexander Pyhalov def update_cmd_privs(self, cmd, user): 1852fb4439dSAlexander Pyhalov """ 1862fb4439dSAlexander Pyhalov If a user has been specified to run this Cmd and we're not already 1872fb4439dSAlexander Pyhalov running as that user, prepend the appropriate sudo command to run 1882fb4439dSAlexander Pyhalov as that user. 1892fb4439dSAlexander Pyhalov """ 1902fb4439dSAlexander Pyhalov me = getpwuid(os.getuid()) 1912fb4439dSAlexander Pyhalov 1922fb4439dSAlexander Pyhalov if not user or user is me: 1932fb4439dSAlexander Pyhalov return cmd 1942fb4439dSAlexander Pyhalov 1952fb4439dSAlexander Pyhalov ret = '%s -E -u %s %s' % (SUDO, user, cmd) 1962fb4439dSAlexander Pyhalov return ret.split(' ') 1972fb4439dSAlexander Pyhalov 1982fb4439dSAlexander Pyhalov def collect_output(self, proc): 1992fb4439dSAlexander Pyhalov """ 2002fb4439dSAlexander Pyhalov Read from stdout/stderr as data becomes available, until the 2012fb4439dSAlexander Pyhalov process is no longer running. Return the lines from the stdout and 2022fb4439dSAlexander Pyhalov stderr Output objects. 2032fb4439dSAlexander Pyhalov """ 2042fb4439dSAlexander Pyhalov out = Output(proc.stdout) 2052fb4439dSAlexander Pyhalov err = Output(proc.stderr) 2062fb4439dSAlexander Pyhalov res = [] 2072fb4439dSAlexander Pyhalov while proc.returncode is None: 2082fb4439dSAlexander Pyhalov proc.poll() 2092fb4439dSAlexander Pyhalov res = select([out, err], [], [], .1) 2102fb4439dSAlexander Pyhalov for fd in res[0]: 2112fb4439dSAlexander Pyhalov fd.read() 2122fb4439dSAlexander Pyhalov for fd in res[0]: 2132fb4439dSAlexander Pyhalov fd.read(drain=1) 2142fb4439dSAlexander Pyhalov 2152fb4439dSAlexander Pyhalov return out.lines, err.lines 2162fb4439dSAlexander Pyhalov 2172fb4439dSAlexander Pyhalov def run(self, options): 2182fb4439dSAlexander Pyhalov """ 2192fb4439dSAlexander Pyhalov This is the main function that runs each individual test. 2202fb4439dSAlexander Pyhalov Determine whether or not the command requires sudo, and modify it 2212fb4439dSAlexander Pyhalov if needed. Run the command, and update the result object. 2222fb4439dSAlexander Pyhalov """ 2232fb4439dSAlexander Pyhalov if options.dryrun is True: 224*e16fe9a0SAlexander Pyhalov print(self) 2252fb4439dSAlexander Pyhalov return 2262fb4439dSAlexander Pyhalov 2272fb4439dSAlexander Pyhalov privcmd = self.update_cmd_privs(self.pathname, self.user) 2282fb4439dSAlexander Pyhalov try: 2292fb4439dSAlexander Pyhalov old = os.umask(0) 2302fb4439dSAlexander Pyhalov if not os.path.isdir(self.outputdir): 231*e16fe9a0SAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 2322fb4439dSAlexander Pyhalov os.umask(old) 233*e16fe9a0SAlexander Pyhalov except OSError as e: 2342fb4439dSAlexander Pyhalov fail('%s' % e) 2352fb4439dSAlexander Pyhalov 2362fb4439dSAlexander Pyhalov try: 2372fb4439dSAlexander Pyhalov self.result.starttime = time() 238*e16fe9a0SAlexander Pyhalov proc = Popen(privcmd, stdout=PIPE, stderr=PIPE, 239*e16fe9a0SAlexander Pyhalov universal_newlines=True) 2402fb4439dSAlexander Pyhalov t = Timer(int(self.timeout), self.kill_cmd, [proc]) 2412fb4439dSAlexander Pyhalov t.start() 2422fb4439dSAlexander Pyhalov self.result.stdout, self.result.stderr = self.collect_output(proc) 2432fb4439dSAlexander Pyhalov except KeyboardInterrupt: 2442fb4439dSAlexander Pyhalov self.kill_cmd(proc) 2452fb4439dSAlexander Pyhalov fail('\nRun terminated at user request.') 2462fb4439dSAlexander Pyhalov finally: 2472fb4439dSAlexander Pyhalov t.cancel() 2482fb4439dSAlexander Pyhalov 2492fb4439dSAlexander Pyhalov self.result.done(proc, self.killed) 2502fb4439dSAlexander Pyhalov 2512fb4439dSAlexander Pyhalov def skip(self): 2522fb4439dSAlexander Pyhalov """ 2532fb4439dSAlexander Pyhalov Initialize enough of the test result that we can log a skipped 2542fb4439dSAlexander Pyhalov command. 2552fb4439dSAlexander Pyhalov """ 2562fb4439dSAlexander Pyhalov Result.total += 1 2572fb4439dSAlexander Pyhalov Result.runresults['SKIP'] += 1 2582fb4439dSAlexander Pyhalov self.result.stdout = self.result.stderr = [] 2592fb4439dSAlexander Pyhalov self.result.starttime = time() 2602fb4439dSAlexander Pyhalov m, s = divmod(time() - self.result.starttime, 60) 2612fb4439dSAlexander Pyhalov self.result.runtime = '%02d:%02d' % (m, s) 2622fb4439dSAlexander Pyhalov self.result.result = 'SKIP' 2632fb4439dSAlexander Pyhalov 2642fb4439dSAlexander Pyhalov def log(self, logger, options): 2652fb4439dSAlexander Pyhalov """ 2662fb4439dSAlexander Pyhalov This function is responsible for writing all output. This includes 2672fb4439dSAlexander Pyhalov the console output, the logfile of all results (with timestamped 2682fb4439dSAlexander Pyhalov merged stdout and stderr), and for each test, the unmodified 2692fb4439dSAlexander Pyhalov stdout/stderr/merged in it's own file. 2702fb4439dSAlexander Pyhalov """ 2712fb4439dSAlexander Pyhalov if logger is None: 2722fb4439dSAlexander Pyhalov return 2732fb4439dSAlexander Pyhalov 2742fb4439dSAlexander Pyhalov logname = getpwuid(os.getuid()).pw_name 2752fb4439dSAlexander Pyhalov user = ' (run as %s)' % (self.user if len(self.user) else logname) 2762fb4439dSAlexander Pyhalov msga = 'Test: %s%s ' % (self.pathname, user) 2772fb4439dSAlexander Pyhalov msgb = '[%s] [%s]' % (self.result.runtime, self.result.result) 2782fb4439dSAlexander Pyhalov pad = ' ' * (80 - (len(msga) + len(msgb))) 2792fb4439dSAlexander Pyhalov 2802fb4439dSAlexander Pyhalov # If -q is specified, only print a line for tests that didn't pass. 2812fb4439dSAlexander Pyhalov # This means passing tests need to be logged as DEBUG, or the one 2822fb4439dSAlexander Pyhalov # line summary will only be printed in the logfile for failures. 2832fb4439dSAlexander Pyhalov if not options.quiet: 2842fb4439dSAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 2852fb4439dSAlexander Pyhalov elif self.result.result is not 'PASS': 2862fb4439dSAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 2872fb4439dSAlexander Pyhalov else: 2882fb4439dSAlexander Pyhalov logger.debug('%s%s%s' % (msga, pad, msgb)) 2892fb4439dSAlexander Pyhalov 2902fb4439dSAlexander Pyhalov lines = sorted(self.result.stdout + self.result.stderr, 291*e16fe9a0SAlexander Pyhalov key=lambda x: x[0]) 2922fb4439dSAlexander Pyhalov 2932fb4439dSAlexander Pyhalov for dt, line in lines: 2942fb4439dSAlexander Pyhalov logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line)) 2952fb4439dSAlexander Pyhalov 2962fb4439dSAlexander Pyhalov if len(self.result.stdout): 2972fb4439dSAlexander Pyhalov with open(os.path.join(self.outputdir, 'stdout'), 'w') as out: 2982fb4439dSAlexander Pyhalov for _, line in self.result.stdout: 299*e16fe9a0SAlexander Pyhalov out.write('%s\n' % line) 3002fb4439dSAlexander Pyhalov if len(self.result.stderr): 3012fb4439dSAlexander Pyhalov with open(os.path.join(self.outputdir, 'stderr'), 'w') as err: 3022fb4439dSAlexander Pyhalov for _, line in self.result.stderr: 303*e16fe9a0SAlexander Pyhalov err.write('%s\n' % line) 3042fb4439dSAlexander Pyhalov if len(self.result.stdout) and len(self.result.stderr): 3052fb4439dSAlexander Pyhalov with open(os.path.join(self.outputdir, 'merged'), 'w') as merged: 3062fb4439dSAlexander Pyhalov for _, line in lines: 307*e16fe9a0SAlexander Pyhalov merged.write('%s\n' % line) 3082fb4439dSAlexander Pyhalov 3092fb4439dSAlexander Pyhalov 3102fb4439dSAlexander Pyhalovclass Test(Cmd): 3112fb4439dSAlexander Pyhalov props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post', 3122fb4439dSAlexander Pyhalov 'post_user'] 3132fb4439dSAlexander Pyhalov 3142fb4439dSAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 3152fb4439dSAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None): 3162fb4439dSAlexander Pyhalov super(Test, self).__init__(pathname, outputdir, timeout, user) 3172fb4439dSAlexander Pyhalov self.pre = pre or '' 3182fb4439dSAlexander Pyhalov self.pre_user = pre_user or '' 3192fb4439dSAlexander Pyhalov self.post = post or '' 3202fb4439dSAlexander Pyhalov self.post_user = post_user or '' 3212fb4439dSAlexander Pyhalov 3222fb4439dSAlexander Pyhalov def __str__(self): 3232fb4439dSAlexander Pyhalov post_user = pre_user = '' 3242fb4439dSAlexander Pyhalov if len(self.pre_user): 3252fb4439dSAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 3262fb4439dSAlexander Pyhalov if len(self.post_user): 3272fb4439dSAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 3282fb4439dSAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nPre: %s%s\nPost: " \ 3292fb4439dSAlexander Pyhalov "%s%s\nUser: %s\n" % (self.pathname, self.outputdir, 3302fb4439dSAlexander Pyhalov self.timeout, self.pre, pre_user, self.post, post_user, 3312fb4439dSAlexander Pyhalov self.user) 3322fb4439dSAlexander Pyhalov 3332fb4439dSAlexander Pyhalov def verify(self, logger): 3342fb4439dSAlexander Pyhalov """ 3352fb4439dSAlexander Pyhalov Check the pre/post scripts, user and Test. Omit the Test from this 3362fb4439dSAlexander Pyhalov run if there are any problems. 3372fb4439dSAlexander Pyhalov """ 3382fb4439dSAlexander Pyhalov files = [self.pre, self.pathname, self.post] 3392fb4439dSAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 3402fb4439dSAlexander Pyhalov 3412fb4439dSAlexander Pyhalov for f in [f for f in files if len(f)]: 3422fb4439dSAlexander Pyhalov if not verify_file(f): 3432fb4439dSAlexander Pyhalov logger.info("Warning: Test '%s' not added to this run because" 3442fb4439dSAlexander Pyhalov " it failed verification." % f) 3452fb4439dSAlexander Pyhalov return False 3462fb4439dSAlexander Pyhalov 3472fb4439dSAlexander Pyhalov for user in [user for user in users if len(user)]: 3482fb4439dSAlexander Pyhalov if not verify_user(user, logger): 3492fb4439dSAlexander Pyhalov logger.info("Not adding Test '%s' to this run." % 3502fb4439dSAlexander Pyhalov self.pathname) 3512fb4439dSAlexander Pyhalov return False 3522fb4439dSAlexander Pyhalov 3532fb4439dSAlexander Pyhalov return True 3542fb4439dSAlexander Pyhalov 3552fb4439dSAlexander Pyhalov def run(self, logger, options): 3562fb4439dSAlexander Pyhalov """ 3572fb4439dSAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 3582fb4439dSAlexander Pyhalov doesn't pass, skip this Test. Run the post script regardless. 3592fb4439dSAlexander Pyhalov """ 3602fb4439dSAlexander Pyhalov pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir, 3612fb4439dSAlexander Pyhalov os.path.basename(self.pre)), timeout=self.timeout, 3622fb4439dSAlexander Pyhalov user=self.pre_user) 3632fb4439dSAlexander Pyhalov test = Cmd(self.pathname, outputdir=self.outputdir, 3642fb4439dSAlexander Pyhalov timeout=self.timeout, user=self.user) 3652fb4439dSAlexander Pyhalov posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir, 3662fb4439dSAlexander Pyhalov os.path.basename(self.post)), timeout=self.timeout, 3672fb4439dSAlexander Pyhalov user=self.post_user) 3682fb4439dSAlexander Pyhalov 3692fb4439dSAlexander Pyhalov cont = True 3702fb4439dSAlexander Pyhalov if len(pretest.pathname): 3712fb4439dSAlexander Pyhalov pretest.run(options) 3722fb4439dSAlexander Pyhalov cont = pretest.result.result is 'PASS' 3732fb4439dSAlexander Pyhalov pretest.log(logger, options) 3742fb4439dSAlexander Pyhalov 3752fb4439dSAlexander Pyhalov if cont: 3762fb4439dSAlexander Pyhalov test.run(options) 3772fb4439dSAlexander Pyhalov else: 3782fb4439dSAlexander Pyhalov test.skip() 3792fb4439dSAlexander Pyhalov 3802fb4439dSAlexander Pyhalov test.log(logger, options) 3812fb4439dSAlexander Pyhalov 3822fb4439dSAlexander Pyhalov if len(posttest.pathname): 3832fb4439dSAlexander Pyhalov posttest.run(options) 3842fb4439dSAlexander Pyhalov posttest.log(logger, options) 3852fb4439dSAlexander Pyhalov 3862fb4439dSAlexander Pyhalov 3872fb4439dSAlexander Pyhalovclass TestGroup(Test): 3882fb4439dSAlexander Pyhalov props = Test.props + ['tests'] 3892fb4439dSAlexander Pyhalov 3902fb4439dSAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 3912fb4439dSAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None, 3922fb4439dSAlexander Pyhalov tests=None): 3932fb4439dSAlexander Pyhalov super(TestGroup, self).__init__(pathname, outputdir, timeout, user, 3942fb4439dSAlexander Pyhalov pre, pre_user, post, post_user) 3952fb4439dSAlexander Pyhalov self.tests = tests or [] 3962fb4439dSAlexander Pyhalov 3972fb4439dSAlexander Pyhalov def __str__(self): 3982fb4439dSAlexander Pyhalov post_user = pre_user = '' 3992fb4439dSAlexander Pyhalov if len(self.pre_user): 4002fb4439dSAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 4012fb4439dSAlexander Pyhalov if len(self.post_user): 4022fb4439dSAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 4032fb4439dSAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \ 4042fb4439dSAlexander Pyhalov "Pre: %s%s\nPost: %s%s\nUser: %s\n" % (self.pathname, 4052fb4439dSAlexander Pyhalov self.outputdir, self.tests, self.timeout, self.pre, pre_user, 4062fb4439dSAlexander Pyhalov self.post, post_user, self.user) 4072fb4439dSAlexander Pyhalov 4082fb4439dSAlexander Pyhalov def verify(self, logger): 4092fb4439dSAlexander Pyhalov """ 4102fb4439dSAlexander Pyhalov Check the pre/post scripts, user and tests in this TestGroup. Omit 4112fb4439dSAlexander Pyhalov the TestGroup entirely, or simply delete the relevant tests in the 4122fb4439dSAlexander Pyhalov group, if that's all that's required. 4132fb4439dSAlexander Pyhalov """ 4142fb4439dSAlexander Pyhalov # If the pre or post scripts are relative pathnames, convert to 4152fb4439dSAlexander Pyhalov # absolute, so they stand a chance of passing verification. 4162fb4439dSAlexander Pyhalov if len(self.pre) and not os.path.isabs(self.pre): 4172fb4439dSAlexander Pyhalov self.pre = os.path.join(self.pathname, self.pre) 4182fb4439dSAlexander Pyhalov if len(self.post) and not os.path.isabs(self.post): 4192fb4439dSAlexander Pyhalov self.post = os.path.join(self.pathname, self.post) 4202fb4439dSAlexander Pyhalov 4212fb4439dSAlexander Pyhalov auxfiles = [self.pre, self.post] 4222fb4439dSAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 4232fb4439dSAlexander Pyhalov 4242fb4439dSAlexander Pyhalov for f in [f for f in auxfiles if len(f)]: 4252fb4439dSAlexander Pyhalov if self.pathname != os.path.dirname(f): 4262fb4439dSAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 4272fb4439dSAlexander Pyhalov "Auxiliary script '%s' exists in a different " 4282fb4439dSAlexander Pyhalov "directory." % (self.pathname, f)) 4292fb4439dSAlexander Pyhalov return False 4302fb4439dSAlexander Pyhalov 4312fb4439dSAlexander Pyhalov if not verify_file(f): 4322fb4439dSAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 4332fb4439dSAlexander Pyhalov "Auxiliary script '%s' failed verification." % 4342fb4439dSAlexander Pyhalov (self.pathname, f)) 4352fb4439dSAlexander Pyhalov return False 4362fb4439dSAlexander Pyhalov 4372fb4439dSAlexander Pyhalov for user in [user for user in users if len(user)]: 4382fb4439dSAlexander Pyhalov if not verify_user(user, logger): 4392fb4439dSAlexander Pyhalov logger.info("Not adding TestGroup '%s' to this run." % 4402fb4439dSAlexander Pyhalov self.pathname) 4412fb4439dSAlexander Pyhalov return False 4422fb4439dSAlexander Pyhalov 4432fb4439dSAlexander Pyhalov # If one of the tests is invalid, delete it, log it, and drive on. 4442fb4439dSAlexander Pyhalov for test in self.tests: 4452fb4439dSAlexander Pyhalov if not verify_file(os.path.join(self.pathname, test)): 4462fb4439dSAlexander Pyhalov del self.tests[self.tests.index(test)] 4472fb4439dSAlexander Pyhalov logger.info("Warning: Test '%s' removed from TestGroup '%s' " 4482fb4439dSAlexander Pyhalov "because it failed verification." % (test, 4492fb4439dSAlexander Pyhalov self.pathname)) 4502fb4439dSAlexander Pyhalov 4512fb4439dSAlexander Pyhalov return len(self.tests) is not 0 4522fb4439dSAlexander Pyhalov 4532fb4439dSAlexander Pyhalov def run(self, logger, options): 4542fb4439dSAlexander Pyhalov """ 4552fb4439dSAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 4562fb4439dSAlexander Pyhalov doesn't pass, skip all the tests in this TestGroup. Run the post 4572fb4439dSAlexander Pyhalov script regardless. 4582fb4439dSAlexander Pyhalov """ 4592fb4439dSAlexander Pyhalov pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir, 4602fb4439dSAlexander Pyhalov os.path.basename(self.pre)), timeout=self.timeout, 4612fb4439dSAlexander Pyhalov user=self.pre_user) 4622fb4439dSAlexander Pyhalov posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir, 4632fb4439dSAlexander Pyhalov os.path.basename(self.post)), timeout=self.timeout, 4642fb4439dSAlexander Pyhalov user=self.post_user) 4652fb4439dSAlexander Pyhalov 4662fb4439dSAlexander Pyhalov cont = True 4672fb4439dSAlexander Pyhalov if len(pretest.pathname): 4682fb4439dSAlexander Pyhalov pretest.run(options) 4692fb4439dSAlexander Pyhalov cont = pretest.result.result is 'PASS' 4702fb4439dSAlexander Pyhalov pretest.log(logger, options) 4712fb4439dSAlexander Pyhalov 4722fb4439dSAlexander Pyhalov for fname in self.tests: 4732fb4439dSAlexander Pyhalov test = Cmd(os.path.join(self.pathname, fname), 4742fb4439dSAlexander Pyhalov outputdir=os.path.join(self.outputdir, fname), 4752fb4439dSAlexander Pyhalov timeout=self.timeout, user=self.user) 4762fb4439dSAlexander Pyhalov if cont: 4772fb4439dSAlexander Pyhalov test.run(options) 4782fb4439dSAlexander Pyhalov else: 4792fb4439dSAlexander Pyhalov test.skip() 4802fb4439dSAlexander Pyhalov 4812fb4439dSAlexander Pyhalov test.log(logger, options) 4822fb4439dSAlexander Pyhalov 4832fb4439dSAlexander Pyhalov if len(posttest.pathname): 4842fb4439dSAlexander Pyhalov posttest.run(options) 4852fb4439dSAlexander Pyhalov posttest.log(logger, options) 4862fb4439dSAlexander Pyhalov 4872fb4439dSAlexander Pyhalov 4882fb4439dSAlexander Pyhalovclass TestRun(object): 4892fb4439dSAlexander Pyhalov props = ['quiet', 'outputdir'] 4902fb4439dSAlexander Pyhalov 4912fb4439dSAlexander Pyhalov def __init__(self, options): 4922fb4439dSAlexander Pyhalov self.tests = {} 4932fb4439dSAlexander Pyhalov self.testgroups = {} 4942fb4439dSAlexander Pyhalov self.starttime = time() 4952fb4439dSAlexander Pyhalov self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') 4962fb4439dSAlexander Pyhalov self.outputdir = os.path.join(options.outputdir, self.timestamp) 4972fb4439dSAlexander Pyhalov self.logger = self.setup_logging(options) 4982fb4439dSAlexander Pyhalov self.defaults = [ 4992fb4439dSAlexander Pyhalov ('outputdir', BASEDIR), 5002fb4439dSAlexander Pyhalov ('quiet', False), 5012fb4439dSAlexander Pyhalov ('timeout', 60), 5022fb4439dSAlexander Pyhalov ('user', ''), 5032fb4439dSAlexander Pyhalov ('pre', ''), 5042fb4439dSAlexander Pyhalov ('pre_user', ''), 5052fb4439dSAlexander Pyhalov ('post', ''), 5062fb4439dSAlexander Pyhalov ('post_user', '') 5072fb4439dSAlexander Pyhalov ] 5082fb4439dSAlexander Pyhalov 5092fb4439dSAlexander Pyhalov def __str__(self): 5102fb4439dSAlexander Pyhalov s = 'TestRun:\n outputdir: %s\n' % self.outputdir 5112fb4439dSAlexander Pyhalov s += 'TESTS:\n' 5122fb4439dSAlexander Pyhalov for key in sorted(self.tests.keys()): 5132fb4439dSAlexander Pyhalov s += '%s%s' % (self.tests[key].__str__(), '\n') 5142fb4439dSAlexander Pyhalov s += 'TESTGROUPS:\n' 5152fb4439dSAlexander Pyhalov for key in sorted(self.testgroups.keys()): 5162fb4439dSAlexander Pyhalov s += '%s%s' % (self.testgroups[key].__str__(), '\n') 5172fb4439dSAlexander Pyhalov return s 5182fb4439dSAlexander Pyhalov 5192fb4439dSAlexander Pyhalov def addtest(self, pathname, options): 5202fb4439dSAlexander Pyhalov """ 5212fb4439dSAlexander Pyhalov Create a new Test, and apply any properties that were passed in 5222fb4439dSAlexander Pyhalov from the command line. If it passes verification, add it to the 5232fb4439dSAlexander Pyhalov TestRun. 5242fb4439dSAlexander Pyhalov """ 5252fb4439dSAlexander Pyhalov test = Test(pathname) 5262fb4439dSAlexander Pyhalov for prop in Test.props: 5272fb4439dSAlexander Pyhalov setattr(test, prop, getattr(options, prop)) 5282fb4439dSAlexander Pyhalov 5292fb4439dSAlexander Pyhalov if test.verify(self.logger): 5302fb4439dSAlexander Pyhalov self.tests[pathname] = test 5312fb4439dSAlexander Pyhalov 5322fb4439dSAlexander Pyhalov def addtestgroup(self, dirname, filenames, options): 5332fb4439dSAlexander Pyhalov """ 5342fb4439dSAlexander Pyhalov Create a new TestGroup, and apply any properties that were passed 5352fb4439dSAlexander Pyhalov in from the command line. If it passes verification, add it to the 5362fb4439dSAlexander Pyhalov TestRun. 5372fb4439dSAlexander Pyhalov """ 5382fb4439dSAlexander Pyhalov if dirname not in self.testgroups: 5392fb4439dSAlexander Pyhalov testgroup = TestGroup(dirname) 5402fb4439dSAlexander Pyhalov for prop in Test.props: 5412fb4439dSAlexander Pyhalov setattr(testgroup, prop, getattr(options, prop)) 5422fb4439dSAlexander Pyhalov 5432fb4439dSAlexander Pyhalov # Prevent pre/post scripts from running as regular tests 5442fb4439dSAlexander Pyhalov for f in [testgroup.pre, testgroup.post]: 5452fb4439dSAlexander Pyhalov if f in filenames: 5462fb4439dSAlexander Pyhalov del filenames[filenames.index(f)] 5472fb4439dSAlexander Pyhalov 5482fb4439dSAlexander Pyhalov self.testgroups[dirname] = testgroup 5492fb4439dSAlexander Pyhalov self.testgroups[dirname].tests = sorted(filenames) 5502fb4439dSAlexander Pyhalov 5512fb4439dSAlexander Pyhalov testgroup.verify(self.logger) 5522fb4439dSAlexander Pyhalov 5532fb4439dSAlexander Pyhalov def read(self, logger, options): 5542fb4439dSAlexander Pyhalov """ 5552fb4439dSAlexander Pyhalov Read in the specified runfile, and apply the TestRun properties 5562fb4439dSAlexander Pyhalov listed in the 'DEFAULT' section to our TestRun. Then read each 5572fb4439dSAlexander Pyhalov section, and apply the appropriate properties to the Test or 5582fb4439dSAlexander Pyhalov TestGroup. Properties from individual sections override those set 5592fb4439dSAlexander Pyhalov in the 'DEFAULT' section. If the Test or TestGroup passes 5602fb4439dSAlexander Pyhalov verification, add it to the TestRun. 5612fb4439dSAlexander Pyhalov """ 562*e16fe9a0SAlexander Pyhalov config = configparser.RawConfigParser() 5632fb4439dSAlexander Pyhalov if not len(config.read(options.runfile)): 5642fb4439dSAlexander Pyhalov fail("Coulnd't read config file %s" % options.runfile) 5652fb4439dSAlexander Pyhalov 5662fb4439dSAlexander Pyhalov for opt in TestRun.props: 5672fb4439dSAlexander Pyhalov if config.has_option('DEFAULT', opt): 5682fb4439dSAlexander Pyhalov setattr(self, opt, config.get('DEFAULT', opt)) 5692fb4439dSAlexander Pyhalov self.outputdir = os.path.join(self.outputdir, self.timestamp) 5702fb4439dSAlexander Pyhalov 5712fb4439dSAlexander Pyhalov for section in config.sections(): 5722fb4439dSAlexander Pyhalov if 'tests' in config.options(section): 5732fb4439dSAlexander Pyhalov testgroup = TestGroup(section) 5742fb4439dSAlexander Pyhalov for prop in TestGroup.props: 5752fb4439dSAlexander Pyhalov try: 5762fb4439dSAlexander Pyhalov setattr(testgroup, prop, config.get('DEFAULT', prop)) 5772fb4439dSAlexander Pyhalov setattr(testgroup, prop, config.get(section, prop)) 5782fb4439dSAlexander Pyhalov except ConfigParser.NoOptionError: 5792fb4439dSAlexander Pyhalov pass 5802fb4439dSAlexander Pyhalov 5812fb4439dSAlexander Pyhalov # Repopulate tests using eval to convert the string to a list 5822fb4439dSAlexander Pyhalov testgroup.tests = eval(config.get(section, 'tests')) 5832fb4439dSAlexander Pyhalov 5842fb4439dSAlexander Pyhalov if testgroup.verify(logger): 5852fb4439dSAlexander Pyhalov self.testgroups[section] = testgroup 5862fb4439dSAlexander Pyhalov else: 5872fb4439dSAlexander Pyhalov test = Test(section) 5882fb4439dSAlexander Pyhalov for prop in Test.props: 5892fb4439dSAlexander Pyhalov try: 5902fb4439dSAlexander Pyhalov setattr(test, prop, config.get('DEFAULT', prop)) 5912fb4439dSAlexander Pyhalov setattr(test, prop, config.get(section, prop)) 5922fb4439dSAlexander Pyhalov except ConfigParser.NoOptionError: 5932fb4439dSAlexander Pyhalov pass 5942fb4439dSAlexander Pyhalov if test.verify(logger): 5952fb4439dSAlexander Pyhalov self.tests[section] = test 5962fb4439dSAlexander Pyhalov 5972fb4439dSAlexander Pyhalov def write(self, options): 5982fb4439dSAlexander Pyhalov """ 5992fb4439dSAlexander Pyhalov Create a configuration file for editing and later use. The 6002fb4439dSAlexander Pyhalov 'DEFAULT' section of the config file is created from the 6012fb4439dSAlexander Pyhalov properties that were specified on the command line. Tests are 6022fb4439dSAlexander Pyhalov simply added as sections that inherit everything from the 6032fb4439dSAlexander Pyhalov 'DEFAULT' section. TestGroups are the same, except they get an 6042fb4439dSAlexander Pyhalov option including all the tests to run in that directory. 6052fb4439dSAlexander Pyhalov """ 6062fb4439dSAlexander Pyhalov 6072fb4439dSAlexander Pyhalov defaults = dict([(prop, getattr(options, prop)) for prop, _ in 6082fb4439dSAlexander Pyhalov self.defaults]) 609*e16fe9a0SAlexander Pyhalov config = configparser.RawConfigParser(defaults) 6102fb4439dSAlexander Pyhalov 6112fb4439dSAlexander Pyhalov for test in sorted(self.tests.keys()): 6122fb4439dSAlexander Pyhalov config.add_section(test) 6132fb4439dSAlexander Pyhalov 6142fb4439dSAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 6152fb4439dSAlexander Pyhalov config.add_section(testgroup) 6162fb4439dSAlexander Pyhalov config.set(testgroup, 'tests', self.testgroups[testgroup].tests) 6172fb4439dSAlexander Pyhalov 6182fb4439dSAlexander Pyhalov try: 6192fb4439dSAlexander Pyhalov with open(options.template, 'w') as f: 6202fb4439dSAlexander Pyhalov return config.write(f) 6212fb4439dSAlexander Pyhalov except IOError: 6222fb4439dSAlexander Pyhalov fail('Could not open \'%s\' for writing.' % options.template) 6232fb4439dSAlexander Pyhalov 6242fb4439dSAlexander Pyhalov def complete_outputdirs(self, options): 6252fb4439dSAlexander Pyhalov """ 6262fb4439dSAlexander Pyhalov Collect all the pathnames for Tests, and TestGroups. Work 6272fb4439dSAlexander Pyhalov backwards one pathname component at a time, to create a unique 6282fb4439dSAlexander Pyhalov directory name in which to deposit test output. Tests will be able 6292fb4439dSAlexander Pyhalov to write output files directly in the newly modified outputdir. 6302fb4439dSAlexander Pyhalov TestGroups will be able to create one subdirectory per test in the 6312fb4439dSAlexander Pyhalov outputdir, and are guaranteed uniqueness because a group can only 6322fb4439dSAlexander Pyhalov contain files in one directory. Pre and post tests will create a 6332fb4439dSAlexander Pyhalov directory rooted at the outputdir of the Test or TestGroup in 6342fb4439dSAlexander Pyhalov question for their output. 6352fb4439dSAlexander Pyhalov """ 6362fb4439dSAlexander Pyhalov done = False 6372fb4439dSAlexander Pyhalov components = 0 638*e16fe9a0SAlexander Pyhalov tmp_dict = dict(list(self.tests.items()) + list(self.testgroups.items())) 6392fb4439dSAlexander Pyhalov total = len(tmp_dict) 6402fb4439dSAlexander Pyhalov base = self.outputdir 6412fb4439dSAlexander Pyhalov 6422fb4439dSAlexander Pyhalov while not done: 6432fb4439dSAlexander Pyhalov l = [] 6442fb4439dSAlexander Pyhalov components -= 1 645*e16fe9a0SAlexander Pyhalov for testfile in list(tmp_dict.keys()): 6462fb4439dSAlexander Pyhalov uniq = '/'.join(testfile.split('/')[components:]).lstrip('/') 6472fb4439dSAlexander Pyhalov if not uniq in l: 6482fb4439dSAlexander Pyhalov l.append(uniq) 6492fb4439dSAlexander Pyhalov tmp_dict[testfile].outputdir = os.path.join(base, uniq) 6502fb4439dSAlexander Pyhalov else: 6512fb4439dSAlexander Pyhalov break 6522fb4439dSAlexander Pyhalov done = total == len(l) 6532fb4439dSAlexander Pyhalov 6542fb4439dSAlexander Pyhalov def setup_logging(self, options): 6552fb4439dSAlexander Pyhalov """ 6562fb4439dSAlexander Pyhalov Two loggers are set up here. The first is for the logfile which 6572fb4439dSAlexander Pyhalov will contain one line summarizing the test, including the test 6582fb4439dSAlexander Pyhalov name, result, and running time. This logger will also capture the 6592fb4439dSAlexander Pyhalov timestamped combined stdout and stderr of each run. The second 6602fb4439dSAlexander Pyhalov logger is optional console output, which will contain only the one 6612fb4439dSAlexander Pyhalov line summary. The loggers are initialized at two different levels 6622fb4439dSAlexander Pyhalov to facilitate segregating the output. 6632fb4439dSAlexander Pyhalov """ 6642fb4439dSAlexander Pyhalov if options.dryrun is True: 6652fb4439dSAlexander Pyhalov return 6662fb4439dSAlexander Pyhalov 6672fb4439dSAlexander Pyhalov testlogger = logging.getLogger(__name__) 6682fb4439dSAlexander Pyhalov testlogger.setLevel(logging.DEBUG) 6692fb4439dSAlexander Pyhalov 6702fb4439dSAlexander Pyhalov if options.cmd is not 'wrconfig': 6712fb4439dSAlexander Pyhalov try: 6722fb4439dSAlexander Pyhalov old = os.umask(0) 673*e16fe9a0SAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 6742fb4439dSAlexander Pyhalov os.umask(old) 675*e16fe9a0SAlexander Pyhalov except OSError as e: 6762fb4439dSAlexander Pyhalov fail('%s' % e) 6772fb4439dSAlexander Pyhalov filename = os.path.join(self.outputdir, 'log') 6782fb4439dSAlexander Pyhalov 6792fb4439dSAlexander Pyhalov logfile = WatchedFileHandlerClosed(filename) 6802fb4439dSAlexander Pyhalov logfile.setLevel(logging.DEBUG) 6812fb4439dSAlexander Pyhalov logfilefmt = logging.Formatter('%(message)s') 6822fb4439dSAlexander Pyhalov logfile.setFormatter(logfilefmt) 6832fb4439dSAlexander Pyhalov testlogger.addHandler(logfile) 6842fb4439dSAlexander Pyhalov 6852fb4439dSAlexander Pyhalov cons = logging.StreamHandler() 6862fb4439dSAlexander Pyhalov cons.setLevel(logging.INFO) 6872fb4439dSAlexander Pyhalov consfmt = logging.Formatter('%(message)s') 6882fb4439dSAlexander Pyhalov cons.setFormatter(consfmt) 6892fb4439dSAlexander Pyhalov testlogger.addHandler(cons) 6902fb4439dSAlexander Pyhalov 6912fb4439dSAlexander Pyhalov return testlogger 6922fb4439dSAlexander Pyhalov 6932fb4439dSAlexander Pyhalov def run(self, options): 6942fb4439dSAlexander Pyhalov """ 6952fb4439dSAlexander Pyhalov Walk through all the Tests and TestGroups, calling run(). 6962fb4439dSAlexander Pyhalov """ 6972fb4439dSAlexander Pyhalov try: 6982fb4439dSAlexander Pyhalov os.chdir(self.outputdir) 6992fb4439dSAlexander Pyhalov except OSError: 7002fb4439dSAlexander Pyhalov fail('Could not change to directory %s' % self.outputdir) 7012fb4439dSAlexander Pyhalov for test in sorted(self.tests.keys()): 7022fb4439dSAlexander Pyhalov self.tests[test].run(self.logger, options) 7032fb4439dSAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 7042fb4439dSAlexander Pyhalov self.testgroups[testgroup].run(self.logger, options) 7052fb4439dSAlexander Pyhalov 7062fb4439dSAlexander Pyhalov def summary(self): 7072fb4439dSAlexander Pyhalov if Result.total is 0: 7082fb4439dSAlexander Pyhalov return 7092fb4439dSAlexander Pyhalov 710*e16fe9a0SAlexander Pyhalov print('\nResults Summary') 711*e16fe9a0SAlexander Pyhalov for key in list(Result.runresults.keys()): 7122fb4439dSAlexander Pyhalov if Result.runresults[key] is not 0: 713*e16fe9a0SAlexander Pyhalov print('%s\t% 4d' % (key, Result.runresults[key])) 7142fb4439dSAlexander Pyhalov 7152fb4439dSAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 7162fb4439dSAlexander Pyhalov h, m = divmod(m, 60) 717*e16fe9a0SAlexander Pyhalov print('\nRunning Time:\t%02d:%02d:%02d' % (h, m, s)) 718*e16fe9a0SAlexander Pyhalov print('Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) / 719*e16fe9a0SAlexander Pyhalov float(Result.total)) * 100)) 720*e16fe9a0SAlexander Pyhalov print('Log directory:\t%s' % self.outputdir) 7212fb4439dSAlexander Pyhalov 7222fb4439dSAlexander Pyhalov 7232fb4439dSAlexander Pyhalovdef verify_file(pathname): 7242fb4439dSAlexander Pyhalov """ 7252fb4439dSAlexander Pyhalov Verify that the supplied pathname is an executable regular file. 7262fb4439dSAlexander Pyhalov """ 7272fb4439dSAlexander Pyhalov if os.path.isdir(pathname) or os.path.islink(pathname): 7282fb4439dSAlexander Pyhalov return False 7292fb4439dSAlexander Pyhalov 7302fb4439dSAlexander Pyhalov if os.path.isfile(pathname) and os.access(pathname, os.X_OK): 7312fb4439dSAlexander Pyhalov return True 7322fb4439dSAlexander Pyhalov 7332fb4439dSAlexander Pyhalov return False 7342fb4439dSAlexander Pyhalov 7352fb4439dSAlexander Pyhalov 7362fb4439dSAlexander Pyhalovdef verify_user(user, logger): 7372fb4439dSAlexander Pyhalov """ 7382fb4439dSAlexander Pyhalov Verify that the specified user exists on this system, and can execute 7392fb4439dSAlexander Pyhalov sudo without being prompted for a password. 7402fb4439dSAlexander Pyhalov """ 7412fb4439dSAlexander Pyhalov testcmd = [SUDO, '-n', '-u', user, TRUE] 7422fb4439dSAlexander Pyhalov can_sudo = exists = True 7432fb4439dSAlexander Pyhalov 7442fb4439dSAlexander Pyhalov if user in Cmd.verified_users: 7452fb4439dSAlexander Pyhalov return True 7462fb4439dSAlexander Pyhalov 7472fb4439dSAlexander Pyhalov try: 7482fb4439dSAlexander Pyhalov _ = getpwnam(user) 7492fb4439dSAlexander Pyhalov except KeyError: 7502fb4439dSAlexander Pyhalov exists = False 7512fb4439dSAlexander Pyhalov logger.info("Warning: user '%s' does not exist.", user) 7522fb4439dSAlexander Pyhalov return False 7532fb4439dSAlexander Pyhalov 7542fb4439dSAlexander Pyhalov p = Popen(testcmd) 7552fb4439dSAlexander Pyhalov p.wait() 7562fb4439dSAlexander Pyhalov if p.returncode is not 0: 7572fb4439dSAlexander Pyhalov logger.info("Warning: user '%s' cannot use passwordless sudo.", user) 7582fb4439dSAlexander Pyhalov return False 7592fb4439dSAlexander Pyhalov else: 7602fb4439dSAlexander Pyhalov Cmd.verified_users.append(user) 7612fb4439dSAlexander Pyhalov 7622fb4439dSAlexander Pyhalov return True 7632fb4439dSAlexander Pyhalov 7642fb4439dSAlexander Pyhalov 7652fb4439dSAlexander Pyhalovdef find_tests(testrun, options): 7662fb4439dSAlexander Pyhalov """ 7672fb4439dSAlexander Pyhalov For the given list of pathnames, add files as Tests. For directories, 7682fb4439dSAlexander Pyhalov if do_groups is True, add the directory as a TestGroup. If False, 7692fb4439dSAlexander Pyhalov recursively search for executable files. 7702fb4439dSAlexander Pyhalov """ 7712fb4439dSAlexander Pyhalov 7722fb4439dSAlexander Pyhalov for p in sorted(options.pathnames): 7732fb4439dSAlexander Pyhalov if os.path.isdir(p): 7742fb4439dSAlexander Pyhalov for dirname, _, filenames in os.walk(p): 7752fb4439dSAlexander Pyhalov if options.do_groups: 7762fb4439dSAlexander Pyhalov testrun.addtestgroup(dirname, filenames, options) 7772fb4439dSAlexander Pyhalov else: 7782fb4439dSAlexander Pyhalov for f in sorted(filenames): 7792fb4439dSAlexander Pyhalov testrun.addtest(os.path.join(dirname, f), options) 7802fb4439dSAlexander Pyhalov else: 7812fb4439dSAlexander Pyhalov testrun.addtest(p, options) 7822fb4439dSAlexander Pyhalov 7832fb4439dSAlexander Pyhalov 7842fb4439dSAlexander Pyhalovdef fail(retstr, ret=1): 785*e16fe9a0SAlexander Pyhalov print('%s: %s' % (argv[0], retstr)) 7862fb4439dSAlexander Pyhalov exit(ret) 7872fb4439dSAlexander Pyhalov 7882fb4439dSAlexander Pyhalov 7892fb4439dSAlexander Pyhalovdef options_cb(option, opt_str, value, parser): 7902fb4439dSAlexander Pyhalov path_options = ['runfile', 'outputdir', 'template'] 7912fb4439dSAlexander Pyhalov 7922fb4439dSAlexander Pyhalov if option.dest is 'runfile' and '-w' in parser.rargs or \ 7932fb4439dSAlexander Pyhalov option.dest is 'template' and '-c' in parser.rargs: 7942fb4439dSAlexander Pyhalov fail('-c and -w are mutually exclusive.') 7952fb4439dSAlexander Pyhalov 7962fb4439dSAlexander Pyhalov if opt_str in parser.rargs: 7972fb4439dSAlexander Pyhalov fail('%s may only be specified once.' % opt_str) 7982fb4439dSAlexander Pyhalov 7992fb4439dSAlexander Pyhalov if option.dest is 'runfile': 8002fb4439dSAlexander Pyhalov parser.values.cmd = 'rdconfig' 8012fb4439dSAlexander Pyhalov if option.dest is 'template': 8022fb4439dSAlexander Pyhalov parser.values.cmd = 'wrconfig' 8032fb4439dSAlexander Pyhalov 8042fb4439dSAlexander Pyhalov setattr(parser.values, option.dest, value) 8052fb4439dSAlexander Pyhalov if option.dest in path_options: 8062fb4439dSAlexander Pyhalov setattr(parser.values, option.dest, os.path.abspath(value)) 8072fb4439dSAlexander Pyhalov 8082fb4439dSAlexander Pyhalov 8092fb4439dSAlexander Pyhalovdef parse_args(): 8102fb4439dSAlexander Pyhalov parser = OptionParser() 8112fb4439dSAlexander Pyhalov parser.add_option('-c', action='callback', callback=options_cb, 8122fb4439dSAlexander Pyhalov type='string', dest='runfile', metavar='runfile', 8132fb4439dSAlexander Pyhalov help='Specify tests to run via config file.') 8142fb4439dSAlexander Pyhalov parser.add_option('-d', action='store_true', default=False, dest='dryrun', 8152fb4439dSAlexander Pyhalov help='Dry run. Print tests, but take no other action.') 8162fb4439dSAlexander Pyhalov parser.add_option('-g', action='store_true', default=False, 8172fb4439dSAlexander Pyhalov dest='do_groups', help='Make directories TestGroups.') 8182fb4439dSAlexander Pyhalov parser.add_option('-o', action='callback', callback=options_cb, 8192fb4439dSAlexander Pyhalov default=BASEDIR, dest='outputdir', type='string', 8202fb4439dSAlexander Pyhalov metavar='outputdir', help='Specify an output directory.') 8212fb4439dSAlexander Pyhalov parser.add_option('-p', action='callback', callback=options_cb, 8222fb4439dSAlexander Pyhalov default='', dest='pre', metavar='script', 8232fb4439dSAlexander Pyhalov type='string', help='Specify a pre script.') 8242fb4439dSAlexander Pyhalov parser.add_option('-P', action='callback', callback=options_cb, 8252fb4439dSAlexander Pyhalov default='', dest='post', metavar='script', 8262fb4439dSAlexander Pyhalov type='string', help='Specify a post script.') 8272fb4439dSAlexander Pyhalov parser.add_option('-q', action='store_true', default=False, dest='quiet', 8282fb4439dSAlexander Pyhalov help='Silence on the console during a test run.') 8292fb4439dSAlexander Pyhalov parser.add_option('-t', action='callback', callback=options_cb, default=60, 8302fb4439dSAlexander Pyhalov dest='timeout', metavar='seconds', type='int', 8312fb4439dSAlexander Pyhalov help='Timeout (in seconds) for an individual test.') 8322fb4439dSAlexander Pyhalov parser.add_option('-u', action='callback', callback=options_cb, 8332fb4439dSAlexander Pyhalov default='', dest='user', metavar='user', type='string', 8342fb4439dSAlexander Pyhalov help='Specify a different user name to run as.') 8352fb4439dSAlexander Pyhalov parser.add_option('-w', action='callback', callback=options_cb, 8362fb4439dSAlexander Pyhalov default=None, dest='template', metavar='template', 8372fb4439dSAlexander Pyhalov type='string', help='Create a new config file.') 8382fb4439dSAlexander Pyhalov parser.add_option('-x', action='callback', callback=options_cb, default='', 8392fb4439dSAlexander Pyhalov dest='pre_user', metavar='pre_user', type='string', 8402fb4439dSAlexander Pyhalov help='Specify a user to execute the pre script.') 8412fb4439dSAlexander Pyhalov parser.add_option('-X', action='callback', callback=options_cb, default='', 8422fb4439dSAlexander Pyhalov dest='post_user', metavar='post_user', type='string', 8432fb4439dSAlexander Pyhalov help='Specify a user to execute the post script.') 8442fb4439dSAlexander Pyhalov (options, pathnames) = parser.parse_args() 8452fb4439dSAlexander Pyhalov 8462fb4439dSAlexander Pyhalov if not options.runfile and not options.template: 8472fb4439dSAlexander Pyhalov options.cmd = 'runtests' 8482fb4439dSAlexander Pyhalov 8492fb4439dSAlexander Pyhalov if options.runfile and len(pathnames): 8502fb4439dSAlexander Pyhalov fail('Extraneous arguments.') 8512fb4439dSAlexander Pyhalov 8522fb4439dSAlexander Pyhalov options.pathnames = [os.path.abspath(path) for path in pathnames] 8532fb4439dSAlexander Pyhalov 8542fb4439dSAlexander Pyhalov return options 8552fb4439dSAlexander Pyhalov 8562fb4439dSAlexander Pyhalov 8572fb4439dSAlexander Pyhalovdef main(args): 8582fb4439dSAlexander Pyhalov options = parse_args() 8592fb4439dSAlexander Pyhalov testrun = TestRun(options) 8602fb4439dSAlexander Pyhalov 8612fb4439dSAlexander Pyhalov if options.cmd is 'runtests': 8622fb4439dSAlexander Pyhalov find_tests(testrun, options) 8632fb4439dSAlexander Pyhalov elif options.cmd is 'rdconfig': 8642fb4439dSAlexander Pyhalov testrun.read(testrun.logger, options) 8652fb4439dSAlexander Pyhalov elif options.cmd is 'wrconfig': 8662fb4439dSAlexander Pyhalov find_tests(testrun, options) 8672fb4439dSAlexander Pyhalov testrun.write(options) 8682fb4439dSAlexander Pyhalov exit(0) 8692fb4439dSAlexander Pyhalov else: 8702fb4439dSAlexander Pyhalov fail('Unknown command specified') 8712fb4439dSAlexander Pyhalov 8722fb4439dSAlexander Pyhalov testrun.complete_outputdirs(options) 8732fb4439dSAlexander Pyhalov testrun.run(options) 8742fb4439dSAlexander Pyhalov testrun.summary() 8752fb4439dSAlexander Pyhalov exit(0) 8762fb4439dSAlexander Pyhalov 8772fb4439dSAlexander Pyhalov 8782fb4439dSAlexander Pyhalovif __name__ == '__main__': 8792fb4439dSAlexander Pyhalov main(argv[1:]) 880