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