1*9f923083SAlexander Pyhalov#!@PYTHON@ 2*9f923083SAlexander Pyhalov 3*9f923083SAlexander Pyhalov# 4*9f923083SAlexander Pyhalov# This file and its contents are supplied under the terms of the 5*9f923083SAlexander Pyhalov# Common Development and Distribution License ("CDDL"), version 1.0. 6*9f923083SAlexander Pyhalov# You may only use this file in accordance with the terms of version 7*9f923083SAlexander Pyhalov# 1.0 of the CDDL. 8*9f923083SAlexander Pyhalov# 9*9f923083SAlexander Pyhalov# A full copy of the text of the CDDL should have accompanied this 10*9f923083SAlexander Pyhalov# source. A copy of the CDDL is also available via the Internet at 11*9f923083SAlexander Pyhalov# http://www.illumos.org/license/CDDL. 12*9f923083SAlexander Pyhalov# 13*9f923083SAlexander Pyhalov 14*9f923083SAlexander Pyhalov# 15*9f923083SAlexander Pyhalov# Copyright (c) 2012, 2015 by Delphix. All rights reserved. 16*9f923083SAlexander Pyhalov# 17*9f923083SAlexander Pyhalov 18*9f923083SAlexander Pyhalovimport ConfigParser 19*9f923083SAlexander Pyhalovimport os 20*9f923083SAlexander Pyhalovimport logging 21*9f923083SAlexander Pyhalovfrom logging.handlers import WatchedFileHandler 22*9f923083SAlexander Pyhalovfrom datetime import datetime 23*9f923083SAlexander Pyhalovfrom optparse import OptionParser 24*9f923083SAlexander Pyhalovfrom pwd import getpwnam 25*9f923083SAlexander Pyhalovfrom pwd import getpwuid 26*9f923083SAlexander Pyhalovfrom select import select 27*9f923083SAlexander Pyhalovfrom subprocess import PIPE 28*9f923083SAlexander Pyhalovfrom subprocess import Popen 29*9f923083SAlexander Pyhalovfrom sys import argv 30*9f923083SAlexander Pyhalovfrom sys import maxint 31*9f923083SAlexander Pyhalovfrom threading import Timer 32*9f923083SAlexander Pyhalovfrom time import time 33*9f923083SAlexander Pyhalov 34*9f923083SAlexander PyhalovBASEDIR = '/var/tmp/test_results' 35*9f923083SAlexander PyhalovKILL = '/usr/bin/kill' 36*9f923083SAlexander PyhalovTRUE = '/usr/bin/true' 37*9f923083SAlexander PyhalovSUDO = '/usr/bin/sudo' 38*9f923083SAlexander Pyhalov 39*9f923083SAlexander Pyhalov# Custom class to reopen the log file in case it is forcibly closed by a test. 40*9f923083SAlexander Pyhalovclass WatchedFileHandlerClosed(WatchedFileHandler): 41*9f923083SAlexander Pyhalov """Watch files, including closed files. 42*9f923083SAlexander Pyhalov Similar to (and inherits from) logging.handler.WatchedFileHandler, 43*9f923083SAlexander Pyhalov except that IOErrors are handled by reopening the stream and retrying. 44*9f923083SAlexander Pyhalov This will be retried up to a configurable number of times before 45*9f923083SAlexander Pyhalov giving up, default 5. 46*9f923083SAlexander Pyhalov """ 47*9f923083SAlexander Pyhalov 48*9f923083SAlexander Pyhalov def __init__(self, filename, mode='a', encoding=None, delay=0, max_tries=5): 49*9f923083SAlexander Pyhalov self.max_tries = max_tries 50*9f923083SAlexander Pyhalov self.tries = 0 51*9f923083SAlexander Pyhalov WatchedFileHandler.__init__(self, filename, mode, encoding, delay) 52*9f923083SAlexander Pyhalov 53*9f923083SAlexander Pyhalov def emit(self, record): 54*9f923083SAlexander Pyhalov while True: 55*9f923083SAlexander Pyhalov try: 56*9f923083SAlexander Pyhalov WatchedFileHandler.emit(self, record) 57*9f923083SAlexander Pyhalov self.tries = 0 58*9f923083SAlexander Pyhalov return 59*9f923083SAlexander Pyhalov except IOError as err: 60*9f923083SAlexander Pyhalov if self.tries == self.max_tries: 61*9f923083SAlexander Pyhalov raise 62*9f923083SAlexander Pyhalov self.stream.close() 63*9f923083SAlexander Pyhalov self.stream = self._open() 64*9f923083SAlexander Pyhalov self.tries += 1 65*9f923083SAlexander Pyhalov 66*9f923083SAlexander Pyhalovclass Result(object): 67*9f923083SAlexander Pyhalov total = 0 68*9f923083SAlexander Pyhalov runresults = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0} 69*9f923083SAlexander Pyhalov 70*9f923083SAlexander Pyhalov def __init__(self): 71*9f923083SAlexander Pyhalov self.starttime = None 72*9f923083SAlexander Pyhalov self.returncode = None 73*9f923083SAlexander Pyhalov self.runtime = '' 74*9f923083SAlexander Pyhalov self.stdout = [] 75*9f923083SAlexander Pyhalov self.stderr = [] 76*9f923083SAlexander Pyhalov self.result = '' 77*9f923083SAlexander Pyhalov 78*9f923083SAlexander Pyhalov def done(self, proc, killed): 79*9f923083SAlexander Pyhalov """ 80*9f923083SAlexander Pyhalov Finalize the results of this Cmd. 81*9f923083SAlexander Pyhalov """ 82*9f923083SAlexander Pyhalov Result.total += 1 83*9f923083SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 84*9f923083SAlexander Pyhalov self.runtime = '%02d:%02d' % (m, s) 85*9f923083SAlexander Pyhalov self.returncode = proc.returncode 86*9f923083SAlexander Pyhalov if killed: 87*9f923083SAlexander Pyhalov self.result = 'KILLED' 88*9f923083SAlexander Pyhalov Result.runresults['KILLED'] += 1 89*9f923083SAlexander Pyhalov elif self.returncode is 0: 90*9f923083SAlexander Pyhalov self.result = 'PASS' 91*9f923083SAlexander Pyhalov Result.runresults['PASS'] += 1 92*9f923083SAlexander Pyhalov elif self.returncode is not 0: 93*9f923083SAlexander Pyhalov self.result = 'FAIL' 94*9f923083SAlexander Pyhalov Result.runresults['FAIL'] += 1 95*9f923083SAlexander Pyhalov 96*9f923083SAlexander Pyhalov 97*9f923083SAlexander Pyhalovclass Output(object): 98*9f923083SAlexander Pyhalov """ 99*9f923083SAlexander Pyhalov This class is a slightly modified version of the 'Stream' class found 100*9f923083SAlexander Pyhalov here: http://goo.gl/aSGfv 101*9f923083SAlexander Pyhalov """ 102*9f923083SAlexander Pyhalov def __init__(self, stream): 103*9f923083SAlexander Pyhalov self.stream = stream 104*9f923083SAlexander Pyhalov self._buf = '' 105*9f923083SAlexander Pyhalov self.lines = [] 106*9f923083SAlexander Pyhalov 107*9f923083SAlexander Pyhalov def fileno(self): 108*9f923083SAlexander Pyhalov return self.stream.fileno() 109*9f923083SAlexander Pyhalov 110*9f923083SAlexander Pyhalov def read(self, drain=0): 111*9f923083SAlexander Pyhalov """ 112*9f923083SAlexander Pyhalov Read from the file descriptor. If 'drain' set, read until EOF. 113*9f923083SAlexander Pyhalov """ 114*9f923083SAlexander Pyhalov while self._read() is not None: 115*9f923083SAlexander Pyhalov if not drain: 116*9f923083SAlexander Pyhalov break 117*9f923083SAlexander Pyhalov 118*9f923083SAlexander Pyhalov def _read(self): 119*9f923083SAlexander Pyhalov """ 120*9f923083SAlexander Pyhalov Read up to 4k of data from this output stream. Collect the output 121*9f923083SAlexander Pyhalov up to the last newline, and append it to any leftover data from a 122*9f923083SAlexander Pyhalov previous call. The lines are stored as a (timestamp, data) tuple 123*9f923083SAlexander Pyhalov for easy sorting/merging later. 124*9f923083SAlexander Pyhalov """ 125*9f923083SAlexander Pyhalov fd = self.fileno() 126*9f923083SAlexander Pyhalov buf = os.read(fd, 4096) 127*9f923083SAlexander Pyhalov if not buf: 128*9f923083SAlexander Pyhalov return None 129*9f923083SAlexander Pyhalov if '\n' not in buf: 130*9f923083SAlexander Pyhalov self._buf += buf 131*9f923083SAlexander Pyhalov return [] 132*9f923083SAlexander Pyhalov 133*9f923083SAlexander Pyhalov buf = self._buf + buf 134*9f923083SAlexander Pyhalov tmp, rest = buf.rsplit('\n', 1) 135*9f923083SAlexander Pyhalov self._buf = rest 136*9f923083SAlexander Pyhalov now = datetime.now() 137*9f923083SAlexander Pyhalov rows = tmp.split('\n') 138*9f923083SAlexander Pyhalov self.lines += [(now, r) for r in rows] 139*9f923083SAlexander Pyhalov 140*9f923083SAlexander Pyhalov 141*9f923083SAlexander Pyhalovclass Cmd(object): 142*9f923083SAlexander Pyhalov verified_users = [] 143*9f923083SAlexander Pyhalov 144*9f923083SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None): 145*9f923083SAlexander Pyhalov self.pathname = pathname 146*9f923083SAlexander Pyhalov self.outputdir = outputdir or 'BASEDIR' 147*9f923083SAlexander Pyhalov self.timeout = timeout 148*9f923083SAlexander Pyhalov self.user = user or '' 149*9f923083SAlexander Pyhalov self.killed = False 150*9f923083SAlexander Pyhalov self.result = Result() 151*9f923083SAlexander Pyhalov 152*9f923083SAlexander Pyhalov if self.timeout is None: 153*9f923083SAlexander Pyhalov self.timeout = 60 154*9f923083SAlexander Pyhalov 155*9f923083SAlexander Pyhalov def __str__(self): 156*9f923083SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nUser: %s\n" % \ 157*9f923083SAlexander Pyhalov (self.pathname, self.outputdir, self.timeout, self.user) 158*9f923083SAlexander Pyhalov 159*9f923083SAlexander Pyhalov def kill_cmd(self, proc): 160*9f923083SAlexander Pyhalov """ 161*9f923083SAlexander Pyhalov Kill a running command due to timeout, or ^C from the keyboard. If 162*9f923083SAlexander Pyhalov sudo is required, this user was verified previously. 163*9f923083SAlexander Pyhalov """ 164*9f923083SAlexander Pyhalov self.killed = True 165*9f923083SAlexander Pyhalov do_sudo = len(self.user) != 0 166*9f923083SAlexander Pyhalov signal = '-TERM' 167*9f923083SAlexander Pyhalov 168*9f923083SAlexander Pyhalov cmd = [SUDO, KILL, signal, str(proc.pid)] 169*9f923083SAlexander Pyhalov if not do_sudo: 170*9f923083SAlexander Pyhalov del cmd[0] 171*9f923083SAlexander Pyhalov 172*9f923083SAlexander Pyhalov try: 173*9f923083SAlexander Pyhalov kp = Popen(cmd) 174*9f923083SAlexander Pyhalov kp.wait() 175*9f923083SAlexander Pyhalov except: 176*9f923083SAlexander Pyhalov pass 177*9f923083SAlexander Pyhalov 178*9f923083SAlexander Pyhalov def update_cmd_privs(self, cmd, user): 179*9f923083SAlexander Pyhalov """ 180*9f923083SAlexander Pyhalov If a user has been specified to run this Cmd and we're not already 181*9f923083SAlexander Pyhalov running as that user, prepend the appropriate sudo command to run 182*9f923083SAlexander Pyhalov as that user. 183*9f923083SAlexander Pyhalov """ 184*9f923083SAlexander Pyhalov me = getpwuid(os.getuid()) 185*9f923083SAlexander Pyhalov 186*9f923083SAlexander Pyhalov if not user or user is me: 187*9f923083SAlexander Pyhalov return cmd 188*9f923083SAlexander Pyhalov 189*9f923083SAlexander Pyhalov ret = '%s -E -u %s %s' % (SUDO, user, cmd) 190*9f923083SAlexander Pyhalov return ret.split(' ') 191*9f923083SAlexander Pyhalov 192*9f923083SAlexander Pyhalov def collect_output(self, proc): 193*9f923083SAlexander Pyhalov """ 194*9f923083SAlexander Pyhalov Read from stdout/stderr as data becomes available, until the 195*9f923083SAlexander Pyhalov process is no longer running. Return the lines from the stdout and 196*9f923083SAlexander Pyhalov stderr Output objects. 197*9f923083SAlexander Pyhalov """ 198*9f923083SAlexander Pyhalov out = Output(proc.stdout) 199*9f923083SAlexander Pyhalov err = Output(proc.stderr) 200*9f923083SAlexander Pyhalov res = [] 201*9f923083SAlexander Pyhalov while proc.returncode is None: 202*9f923083SAlexander Pyhalov proc.poll() 203*9f923083SAlexander Pyhalov res = select([out, err], [], [], .1) 204*9f923083SAlexander Pyhalov for fd in res[0]: 205*9f923083SAlexander Pyhalov fd.read() 206*9f923083SAlexander Pyhalov for fd in res[0]: 207*9f923083SAlexander Pyhalov fd.read(drain=1) 208*9f923083SAlexander Pyhalov 209*9f923083SAlexander Pyhalov return out.lines, err.lines 210*9f923083SAlexander Pyhalov 211*9f923083SAlexander Pyhalov def run(self, options): 212*9f923083SAlexander Pyhalov """ 213*9f923083SAlexander Pyhalov This is the main function that runs each individual test. 214*9f923083SAlexander Pyhalov Determine whether or not the command requires sudo, and modify it 215*9f923083SAlexander Pyhalov if needed. Run the command, and update the result object. 216*9f923083SAlexander Pyhalov """ 217*9f923083SAlexander Pyhalov if options.dryrun is True: 218*9f923083SAlexander Pyhalov print self 219*9f923083SAlexander Pyhalov return 220*9f923083SAlexander Pyhalov 221*9f923083SAlexander Pyhalov privcmd = self.update_cmd_privs(self.pathname, self.user) 222*9f923083SAlexander Pyhalov try: 223*9f923083SAlexander Pyhalov old = os.umask(0) 224*9f923083SAlexander Pyhalov if not os.path.isdir(self.outputdir): 225*9f923083SAlexander Pyhalov os.makedirs(self.outputdir, mode=0777) 226*9f923083SAlexander Pyhalov os.umask(old) 227*9f923083SAlexander Pyhalov except OSError, e: 228*9f923083SAlexander Pyhalov fail('%s' % e) 229*9f923083SAlexander Pyhalov 230*9f923083SAlexander Pyhalov try: 231*9f923083SAlexander Pyhalov self.result.starttime = time() 232*9f923083SAlexander Pyhalov proc = Popen(privcmd, stdout=PIPE, stderr=PIPE) 233*9f923083SAlexander Pyhalov 234*9f923083SAlexander Pyhalov # Allow a special timeout value of 0 to mean infinity 235*9f923083SAlexander Pyhalov if int(self.timeout) == 0: 236*9f923083SAlexander Pyhalov self.timeout = maxint 237*9f923083SAlexander Pyhalov t = Timer(int(self.timeout), self.kill_cmd, [proc]) 238*9f923083SAlexander Pyhalov t.start() 239*9f923083SAlexander Pyhalov self.result.stdout, self.result.stderr = self.collect_output(proc) 240*9f923083SAlexander Pyhalov except KeyboardInterrupt: 241*9f923083SAlexander Pyhalov self.kill_cmd(proc) 242*9f923083SAlexander Pyhalov fail('\nRun terminated at user request.') 243*9f923083SAlexander Pyhalov finally: 244*9f923083SAlexander Pyhalov t.cancel() 245*9f923083SAlexander Pyhalov 246*9f923083SAlexander Pyhalov self.result.done(proc, self.killed) 247*9f923083SAlexander Pyhalov 248*9f923083SAlexander Pyhalov def skip(self): 249*9f923083SAlexander Pyhalov """ 250*9f923083SAlexander Pyhalov Initialize enough of the test result that we can log a skipped 251*9f923083SAlexander Pyhalov command. 252*9f923083SAlexander Pyhalov """ 253*9f923083SAlexander Pyhalov Result.total += 1 254*9f923083SAlexander Pyhalov Result.runresults['SKIP'] += 1 255*9f923083SAlexander Pyhalov self.result.stdout = self.result.stderr = [] 256*9f923083SAlexander Pyhalov self.result.starttime = time() 257*9f923083SAlexander Pyhalov m, s = divmod(time() - self.result.starttime, 60) 258*9f923083SAlexander Pyhalov self.result.runtime = '%02d:%02d' % (m, s) 259*9f923083SAlexander Pyhalov self.result.result = 'SKIP' 260*9f923083SAlexander Pyhalov 261*9f923083SAlexander Pyhalov def log(self, logger, options): 262*9f923083SAlexander Pyhalov """ 263*9f923083SAlexander Pyhalov This function is responsible for writing all output. This includes 264*9f923083SAlexander Pyhalov the console output, the logfile of all results (with timestamped 265*9f923083SAlexander Pyhalov merged stdout and stderr), and for each test, the unmodified 266*9f923083SAlexander Pyhalov stdout/stderr/merged in it's own file. 267*9f923083SAlexander Pyhalov """ 268*9f923083SAlexander Pyhalov if logger is None: 269*9f923083SAlexander Pyhalov return 270*9f923083SAlexander Pyhalov 271*9f923083SAlexander Pyhalov logname = getpwuid(os.getuid()).pw_name 272*9f923083SAlexander Pyhalov user = ' (run as %s)' % (self.user if len(self.user) else logname) 273*9f923083SAlexander Pyhalov msga = 'Test: %s%s ' % (self.pathname, user) 274*9f923083SAlexander Pyhalov msgb = '[%s] [%s]' % (self.result.runtime, self.result.result) 275*9f923083SAlexander Pyhalov pad = ' ' * (80 - (len(msga) + len(msgb))) 276*9f923083SAlexander Pyhalov 277*9f923083SAlexander Pyhalov # If -q is specified, only print a line for tests that didn't pass. 278*9f923083SAlexander Pyhalov # This means passing tests need to be logged as DEBUG, or the one 279*9f923083SAlexander Pyhalov # line summary will only be printed in the logfile for failures. 280*9f923083SAlexander Pyhalov if not options.quiet: 281*9f923083SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 282*9f923083SAlexander Pyhalov elif self.result.result is not 'PASS': 283*9f923083SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 284*9f923083SAlexander Pyhalov else: 285*9f923083SAlexander Pyhalov logger.debug('%s%s%s' % (msga, pad, msgb)) 286*9f923083SAlexander Pyhalov 287*9f923083SAlexander Pyhalov lines = sorted(self.result.stdout + self.result.stderr, 288*9f923083SAlexander Pyhalov cmp=lambda x, y: cmp(x[0], y[0])) 289*9f923083SAlexander Pyhalov 290*9f923083SAlexander Pyhalov for dt, line in lines: 291*9f923083SAlexander Pyhalov logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line)) 292*9f923083SAlexander Pyhalov 293*9f923083SAlexander Pyhalov if len(self.result.stdout): 294*9f923083SAlexander Pyhalov with open(os.path.join(self.outputdir, 'stdout'), 'w') as out: 295*9f923083SAlexander Pyhalov for _, line in self.result.stdout: 296*9f923083SAlexander Pyhalov os.write(out.fileno(), '%s\n' % line) 297*9f923083SAlexander Pyhalov if len(self.result.stderr): 298*9f923083SAlexander Pyhalov with open(os.path.join(self.outputdir, 'stderr'), 'w') as err: 299*9f923083SAlexander Pyhalov for _, line in self.result.stderr: 300*9f923083SAlexander Pyhalov os.write(err.fileno(), '%s\n' % line) 301*9f923083SAlexander Pyhalov if len(self.result.stdout) and len(self.result.stderr): 302*9f923083SAlexander Pyhalov with open(os.path.join(self.outputdir, 'merged'), 'w') as merged: 303*9f923083SAlexander Pyhalov for _, line in lines: 304*9f923083SAlexander Pyhalov os.write(merged.fileno(), '%s\n' % line) 305*9f923083SAlexander Pyhalov 306*9f923083SAlexander Pyhalov 307*9f923083SAlexander Pyhalovclass Test(Cmd): 308*9f923083SAlexander Pyhalov props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post', 309*9f923083SAlexander Pyhalov 'post_user'] 310*9f923083SAlexander Pyhalov 311*9f923083SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 312*9f923083SAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None): 313*9f923083SAlexander Pyhalov super(Test, self).__init__(pathname, outputdir, timeout, user) 314*9f923083SAlexander Pyhalov self.pre = pre or '' 315*9f923083SAlexander Pyhalov self.pre_user = pre_user or '' 316*9f923083SAlexander Pyhalov self.post = post or '' 317*9f923083SAlexander Pyhalov self.post_user = post_user or '' 318*9f923083SAlexander Pyhalov 319*9f923083SAlexander Pyhalov def __str__(self): 320*9f923083SAlexander Pyhalov post_user = pre_user = '' 321*9f923083SAlexander Pyhalov if len(self.pre_user): 322*9f923083SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 323*9f923083SAlexander Pyhalov if len(self.post_user): 324*9f923083SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 325*9f923083SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \ 326*9f923083SAlexander Pyhalov "%s%s\nUser: %s\n" % \ 327*9f923083SAlexander Pyhalov (self.pathname, self.outputdir, self.timeout, self.pre, 328*9f923083SAlexander Pyhalov pre_user, self.post, post_user, self.user) 329*9f923083SAlexander Pyhalov 330*9f923083SAlexander Pyhalov def verify(self, logger): 331*9f923083SAlexander Pyhalov """ 332*9f923083SAlexander Pyhalov Check the pre/post scripts, user and Test. Omit the Test from this 333*9f923083SAlexander Pyhalov run if there are any problems. 334*9f923083SAlexander Pyhalov """ 335*9f923083SAlexander Pyhalov files = [self.pre, self.pathname, self.post] 336*9f923083SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 337*9f923083SAlexander Pyhalov 338*9f923083SAlexander Pyhalov for f in [f for f in files if len(f)]: 339*9f923083SAlexander Pyhalov if not verify_file(f): 340*9f923083SAlexander Pyhalov logger.info("Warning: Test '%s' not added to this run because" 341*9f923083SAlexander Pyhalov " it failed verification." % f) 342*9f923083SAlexander Pyhalov return False 343*9f923083SAlexander Pyhalov 344*9f923083SAlexander Pyhalov for user in [user for user in users if len(user)]: 345*9f923083SAlexander Pyhalov if not verify_user(user, logger): 346*9f923083SAlexander Pyhalov logger.info("Not adding Test '%s' to this run." % 347*9f923083SAlexander Pyhalov self.pathname) 348*9f923083SAlexander Pyhalov return False 349*9f923083SAlexander Pyhalov 350*9f923083SAlexander Pyhalov return True 351*9f923083SAlexander Pyhalov 352*9f923083SAlexander Pyhalov def run(self, logger, options): 353*9f923083SAlexander Pyhalov """ 354*9f923083SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 355*9f923083SAlexander Pyhalov doesn't pass, skip this Test. Run the post script regardless. 356*9f923083SAlexander Pyhalov """ 357*9f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.pre)) 358*9f923083SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=odir, timeout=self.timeout, 359*9f923083SAlexander Pyhalov user=self.pre_user) 360*9f923083SAlexander Pyhalov test = Cmd(self.pathname, outputdir=self.outputdir, 361*9f923083SAlexander Pyhalov timeout=self.timeout, user=self.user) 362*9f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.post)) 363*9f923083SAlexander Pyhalov posttest = Cmd(self.post, outputdir=odir, timeout=self.timeout, 364*9f923083SAlexander Pyhalov user=self.post_user) 365*9f923083SAlexander Pyhalov 366*9f923083SAlexander Pyhalov cont = True 367*9f923083SAlexander Pyhalov if len(pretest.pathname): 368*9f923083SAlexander Pyhalov pretest.run(options) 369*9f923083SAlexander Pyhalov cont = pretest.result.result is 'PASS' 370*9f923083SAlexander Pyhalov pretest.log(logger, options) 371*9f923083SAlexander Pyhalov 372*9f923083SAlexander Pyhalov if cont: 373*9f923083SAlexander Pyhalov test.run(options) 374*9f923083SAlexander Pyhalov else: 375*9f923083SAlexander Pyhalov test.skip() 376*9f923083SAlexander Pyhalov 377*9f923083SAlexander Pyhalov test.log(logger, options) 378*9f923083SAlexander Pyhalov 379*9f923083SAlexander Pyhalov if len(posttest.pathname): 380*9f923083SAlexander Pyhalov posttest.run(options) 381*9f923083SAlexander Pyhalov posttest.log(logger, options) 382*9f923083SAlexander Pyhalov 383*9f923083SAlexander Pyhalov 384*9f923083SAlexander Pyhalovclass TestGroup(Test): 385*9f923083SAlexander Pyhalov props = Test.props + ['tests'] 386*9f923083SAlexander Pyhalov 387*9f923083SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 388*9f923083SAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None, 389*9f923083SAlexander Pyhalov tests=None): 390*9f923083SAlexander Pyhalov super(TestGroup, self).__init__(pathname, outputdir, timeout, user, 391*9f923083SAlexander Pyhalov pre, pre_user, post, post_user) 392*9f923083SAlexander Pyhalov self.tests = tests or [] 393*9f923083SAlexander Pyhalov 394*9f923083SAlexander Pyhalov def __str__(self): 395*9f923083SAlexander Pyhalov post_user = pre_user = '' 396*9f923083SAlexander Pyhalov if len(self.pre_user): 397*9f923083SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 398*9f923083SAlexander Pyhalov if len(self.post_user): 399*9f923083SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 400*9f923083SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %d\n" \ 401*9f923083SAlexander Pyhalov "Pre: %s%s\nPost: %s%s\nUser: %s\n" % \ 402*9f923083SAlexander Pyhalov (self.pathname, self.outputdir, self.tests, self.timeout, 403*9f923083SAlexander Pyhalov self.pre, pre_user, self.post, post_user, self.user) 404*9f923083SAlexander Pyhalov 405*9f923083SAlexander Pyhalov def verify(self, logger): 406*9f923083SAlexander Pyhalov """ 407*9f923083SAlexander Pyhalov Check the pre/post scripts, user and tests in this TestGroup. Omit 408*9f923083SAlexander Pyhalov the TestGroup entirely, or simply delete the relevant tests in the 409*9f923083SAlexander Pyhalov group, if that's all that's required. 410*9f923083SAlexander Pyhalov """ 411*9f923083SAlexander Pyhalov # If the pre or post scripts are relative pathnames, convert to 412*9f923083SAlexander Pyhalov # absolute, so they stand a chance of passing verification. 413*9f923083SAlexander Pyhalov if len(self.pre) and not os.path.isabs(self.pre): 414*9f923083SAlexander Pyhalov self.pre = os.path.join(self.pathname, self.pre) 415*9f923083SAlexander Pyhalov if len(self.post) and not os.path.isabs(self.post): 416*9f923083SAlexander Pyhalov self.post = os.path.join(self.pathname, self.post) 417*9f923083SAlexander Pyhalov 418*9f923083SAlexander Pyhalov auxfiles = [self.pre, self.post] 419*9f923083SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 420*9f923083SAlexander Pyhalov 421*9f923083SAlexander Pyhalov for f in [f for f in auxfiles if len(f)]: 422*9f923083SAlexander Pyhalov if self.pathname != os.path.dirname(f): 423*9f923083SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 424*9f923083SAlexander Pyhalov "Auxiliary script '%s' exists in a different " 425*9f923083SAlexander Pyhalov "directory." % (self.pathname, f)) 426*9f923083SAlexander Pyhalov return False 427*9f923083SAlexander Pyhalov 428*9f923083SAlexander Pyhalov if not verify_file(f): 429*9f923083SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 430*9f923083SAlexander Pyhalov "Auxiliary script '%s' failed verification." % 431*9f923083SAlexander Pyhalov (self.pathname, f)) 432*9f923083SAlexander Pyhalov return False 433*9f923083SAlexander Pyhalov 434*9f923083SAlexander Pyhalov for user in [user for user in users if len(user)]: 435*9f923083SAlexander Pyhalov if not verify_user(user, logger): 436*9f923083SAlexander Pyhalov logger.info("Not adding TestGroup '%s' to this run." % 437*9f923083SAlexander Pyhalov self.pathname) 438*9f923083SAlexander Pyhalov return False 439*9f923083SAlexander Pyhalov 440*9f923083SAlexander Pyhalov # If one of the tests is invalid, delete it, log it, and drive on. 441*9f923083SAlexander Pyhalov for test in self.tests: 442*9f923083SAlexander Pyhalov if not verify_file(os.path.join(self.pathname, test)): 443*9f923083SAlexander Pyhalov del self.tests[self.tests.index(test)] 444*9f923083SAlexander Pyhalov logger.info("Warning: Test '%s' removed from TestGroup '%s' " 445*9f923083SAlexander Pyhalov "because it failed verification." % 446*9f923083SAlexander Pyhalov (test, self.pathname)) 447*9f923083SAlexander Pyhalov 448*9f923083SAlexander Pyhalov return len(self.tests) is not 0 449*9f923083SAlexander Pyhalov 450*9f923083SAlexander Pyhalov def run(self, logger, options): 451*9f923083SAlexander Pyhalov """ 452*9f923083SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 453*9f923083SAlexander Pyhalov doesn't pass, skip all the tests in this TestGroup. Run the post 454*9f923083SAlexander Pyhalov script regardless. 455*9f923083SAlexander Pyhalov """ 456*9f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.pre)) 457*9f923083SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=odir, timeout=self.timeout, 458*9f923083SAlexander Pyhalov user=self.pre_user) 459*9f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.post)) 460*9f923083SAlexander Pyhalov posttest = Cmd(self.post, outputdir=odir, timeout=self.timeout, 461*9f923083SAlexander Pyhalov user=self.post_user) 462*9f923083SAlexander Pyhalov 463*9f923083SAlexander Pyhalov cont = True 464*9f923083SAlexander Pyhalov if len(pretest.pathname): 465*9f923083SAlexander Pyhalov pretest.run(options) 466*9f923083SAlexander Pyhalov cont = pretest.result.result is 'PASS' 467*9f923083SAlexander Pyhalov pretest.log(logger, options) 468*9f923083SAlexander Pyhalov 469*9f923083SAlexander Pyhalov for fname in self.tests: 470*9f923083SAlexander Pyhalov test = Cmd(os.path.join(self.pathname, fname), 471*9f923083SAlexander Pyhalov outputdir=os.path.join(self.outputdir, fname), 472*9f923083SAlexander Pyhalov timeout=self.timeout, user=self.user) 473*9f923083SAlexander Pyhalov if cont: 474*9f923083SAlexander Pyhalov test.run(options) 475*9f923083SAlexander Pyhalov else: 476*9f923083SAlexander Pyhalov test.skip() 477*9f923083SAlexander Pyhalov 478*9f923083SAlexander Pyhalov test.log(logger, options) 479*9f923083SAlexander Pyhalov 480*9f923083SAlexander Pyhalov if len(posttest.pathname): 481*9f923083SAlexander Pyhalov posttest.run(options) 482*9f923083SAlexander Pyhalov posttest.log(logger, options) 483*9f923083SAlexander Pyhalov 484*9f923083SAlexander Pyhalov 485*9f923083SAlexander Pyhalovclass TestRun(object): 486*9f923083SAlexander Pyhalov props = ['quiet', 'outputdir'] 487*9f923083SAlexander Pyhalov 488*9f923083SAlexander Pyhalov def __init__(self, options): 489*9f923083SAlexander Pyhalov self.tests = {} 490*9f923083SAlexander Pyhalov self.testgroups = {} 491*9f923083SAlexander Pyhalov self.starttime = time() 492*9f923083SAlexander Pyhalov self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') 493*9f923083SAlexander Pyhalov self.outputdir = os.path.join(options.outputdir, self.timestamp) 494*9f923083SAlexander Pyhalov self.logger = self.setup_logging(options) 495*9f923083SAlexander Pyhalov self.defaults = [ 496*9f923083SAlexander Pyhalov ('outputdir', BASEDIR), 497*9f923083SAlexander Pyhalov ('quiet', False), 498*9f923083SAlexander Pyhalov ('timeout', 60), 499*9f923083SAlexander Pyhalov ('user', ''), 500*9f923083SAlexander Pyhalov ('pre', ''), 501*9f923083SAlexander Pyhalov ('pre_user', ''), 502*9f923083SAlexander Pyhalov ('post', ''), 503*9f923083SAlexander Pyhalov ('post_user', '') 504*9f923083SAlexander Pyhalov ] 505*9f923083SAlexander Pyhalov 506*9f923083SAlexander Pyhalov def __str__(self): 507*9f923083SAlexander Pyhalov s = 'TestRun:\n outputdir: %s\n' % self.outputdir 508*9f923083SAlexander Pyhalov s += 'TESTS:\n' 509*9f923083SAlexander Pyhalov for key in sorted(self.tests.keys()): 510*9f923083SAlexander Pyhalov s += '%s%s' % (self.tests[key].__str__(), '\n') 511*9f923083SAlexander Pyhalov s += 'TESTGROUPS:\n' 512*9f923083SAlexander Pyhalov for key in sorted(self.testgroups.keys()): 513*9f923083SAlexander Pyhalov s += '%s%s' % (self.testgroups[key].__str__(), '\n') 514*9f923083SAlexander Pyhalov return s 515*9f923083SAlexander Pyhalov 516*9f923083SAlexander Pyhalov def addtest(self, pathname, options): 517*9f923083SAlexander Pyhalov """ 518*9f923083SAlexander Pyhalov Create a new Test, and apply any properties that were passed in 519*9f923083SAlexander Pyhalov from the command line. If it passes verification, add it to the 520*9f923083SAlexander Pyhalov TestRun. 521*9f923083SAlexander Pyhalov """ 522*9f923083SAlexander Pyhalov test = Test(pathname) 523*9f923083SAlexander Pyhalov for prop in Test.props: 524*9f923083SAlexander Pyhalov setattr(test, prop, getattr(options, prop)) 525*9f923083SAlexander Pyhalov 526*9f923083SAlexander Pyhalov if test.verify(self.logger): 527*9f923083SAlexander Pyhalov self.tests[pathname] = test 528*9f923083SAlexander Pyhalov 529*9f923083SAlexander Pyhalov def addtestgroup(self, dirname, filenames, options): 530*9f923083SAlexander Pyhalov """ 531*9f923083SAlexander Pyhalov Create a new TestGroup, and apply any properties that were passed 532*9f923083SAlexander Pyhalov in from the command line. If it passes verification, add it to the 533*9f923083SAlexander Pyhalov TestRun. 534*9f923083SAlexander Pyhalov """ 535*9f923083SAlexander Pyhalov if dirname not in self.testgroups: 536*9f923083SAlexander Pyhalov testgroup = TestGroup(dirname) 537*9f923083SAlexander Pyhalov for prop in Test.props: 538*9f923083SAlexander Pyhalov setattr(testgroup, prop, getattr(options, prop)) 539*9f923083SAlexander Pyhalov 540*9f923083SAlexander Pyhalov # Prevent pre/post scripts from running as regular tests 541*9f923083SAlexander Pyhalov for f in [testgroup.pre, testgroup.post]: 542*9f923083SAlexander Pyhalov if f in filenames: 543*9f923083SAlexander Pyhalov del filenames[filenames.index(f)] 544*9f923083SAlexander Pyhalov 545*9f923083SAlexander Pyhalov self.testgroups[dirname] = testgroup 546*9f923083SAlexander Pyhalov self.testgroups[dirname].tests = sorted(filenames) 547*9f923083SAlexander Pyhalov 548*9f923083SAlexander Pyhalov testgroup.verify(self.logger) 549*9f923083SAlexander Pyhalov 550*9f923083SAlexander Pyhalov def read(self, logger, options): 551*9f923083SAlexander Pyhalov """ 552*9f923083SAlexander Pyhalov Read in the specified runfile, and apply the TestRun properties 553*9f923083SAlexander Pyhalov listed in the 'DEFAULT' section to our TestRun. Then read each 554*9f923083SAlexander Pyhalov section, and apply the appropriate properties to the Test or 555*9f923083SAlexander Pyhalov TestGroup. Properties from individual sections override those set 556*9f923083SAlexander Pyhalov in the 'DEFAULT' section. If the Test or TestGroup passes 557*9f923083SAlexander Pyhalov verification, add it to the TestRun. 558*9f923083SAlexander Pyhalov """ 559*9f923083SAlexander Pyhalov config = ConfigParser.RawConfigParser() 560*9f923083SAlexander Pyhalov if not len(config.read(options.runfile)): 561*9f923083SAlexander Pyhalov fail("Coulnd't read config file %s" % options.runfile) 562*9f923083SAlexander Pyhalov 563*9f923083SAlexander Pyhalov for opt in TestRun.props: 564*9f923083SAlexander Pyhalov if config.has_option('DEFAULT', opt): 565*9f923083SAlexander Pyhalov setattr(self, opt, config.get('DEFAULT', opt)) 566*9f923083SAlexander Pyhalov self.outputdir = os.path.join(self.outputdir, self.timestamp) 567*9f923083SAlexander Pyhalov 568*9f923083SAlexander Pyhalov for section in config.sections(): 569*9f923083SAlexander Pyhalov if 'tests' in config.options(section): 570*9f923083SAlexander Pyhalov testgroup = TestGroup(section) 571*9f923083SAlexander Pyhalov for prop in TestGroup.props: 572*9f923083SAlexander Pyhalov for sect in ['DEFAULT', section]: 573*9f923083SAlexander Pyhalov if config.has_option(sect, prop): 574*9f923083SAlexander Pyhalov setattr(testgroup, prop, config.get(sect, prop)) 575*9f923083SAlexander Pyhalov 576*9f923083SAlexander Pyhalov # Repopulate tests using eval to convert the string to a list 577*9f923083SAlexander Pyhalov testgroup.tests = eval(config.get(section, 'tests')) 578*9f923083SAlexander Pyhalov 579*9f923083SAlexander Pyhalov if testgroup.verify(logger): 580*9f923083SAlexander Pyhalov self.testgroups[section] = testgroup 581*9f923083SAlexander Pyhalov else: 582*9f923083SAlexander Pyhalov test = Test(section) 583*9f923083SAlexander Pyhalov for prop in Test.props: 584*9f923083SAlexander Pyhalov for sect in ['DEFAULT', section]: 585*9f923083SAlexander Pyhalov if config.has_option(sect, prop): 586*9f923083SAlexander Pyhalov setattr(test, prop, config.get(sect, prop)) 587*9f923083SAlexander Pyhalov 588*9f923083SAlexander Pyhalov if test.verify(logger): 589*9f923083SAlexander Pyhalov self.tests[section] = test 590*9f923083SAlexander Pyhalov 591*9f923083SAlexander Pyhalov def write(self, options): 592*9f923083SAlexander Pyhalov """ 593*9f923083SAlexander Pyhalov Create a configuration file for editing and later use. The 594*9f923083SAlexander Pyhalov 'DEFAULT' section of the config file is created from the 595*9f923083SAlexander Pyhalov properties that were specified on the command line. Tests are 596*9f923083SAlexander Pyhalov simply added as sections that inherit everything from the 597*9f923083SAlexander Pyhalov 'DEFAULT' section. TestGroups are the same, except they get an 598*9f923083SAlexander Pyhalov option including all the tests to run in that directory. 599*9f923083SAlexander Pyhalov """ 600*9f923083SAlexander Pyhalov 601*9f923083SAlexander Pyhalov defaults = dict([(prop, getattr(options, prop)) for prop, _ in 602*9f923083SAlexander Pyhalov self.defaults]) 603*9f923083SAlexander Pyhalov config = ConfigParser.RawConfigParser(defaults) 604*9f923083SAlexander Pyhalov 605*9f923083SAlexander Pyhalov for test in sorted(self.tests.keys()): 606*9f923083SAlexander Pyhalov config.add_section(test) 607*9f923083SAlexander Pyhalov 608*9f923083SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 609*9f923083SAlexander Pyhalov config.add_section(testgroup) 610*9f923083SAlexander Pyhalov config.set(testgroup, 'tests', self.testgroups[testgroup].tests) 611*9f923083SAlexander Pyhalov 612*9f923083SAlexander Pyhalov try: 613*9f923083SAlexander Pyhalov with open(options.template, 'w') as f: 614*9f923083SAlexander Pyhalov return config.write(f) 615*9f923083SAlexander Pyhalov except IOError: 616*9f923083SAlexander Pyhalov fail('Could not open \'%s\' for writing.' % options.template) 617*9f923083SAlexander Pyhalov 618*9f923083SAlexander Pyhalov def complete_outputdirs(self): 619*9f923083SAlexander Pyhalov """ 620*9f923083SAlexander Pyhalov Collect all the pathnames for Tests, and TestGroups. Work 621*9f923083SAlexander Pyhalov backwards one pathname component at a time, to create a unique 622*9f923083SAlexander Pyhalov directory name in which to deposit test output. Tests will be able 623*9f923083SAlexander Pyhalov to write output files directly in the newly modified outputdir. 624*9f923083SAlexander Pyhalov TestGroups will be able to create one subdirectory per test in the 625*9f923083SAlexander Pyhalov outputdir, and are guaranteed uniqueness because a group can only 626*9f923083SAlexander Pyhalov contain files in one directory. Pre and post tests will create a 627*9f923083SAlexander Pyhalov directory rooted at the outputdir of the Test or TestGroup in 628*9f923083SAlexander Pyhalov question for their output. 629*9f923083SAlexander Pyhalov """ 630*9f923083SAlexander Pyhalov done = False 631*9f923083SAlexander Pyhalov components = 0 632*9f923083SAlexander Pyhalov tmp_dict = dict(self.tests.items() + self.testgroups.items()) 633*9f923083SAlexander Pyhalov total = len(tmp_dict) 634*9f923083SAlexander Pyhalov base = self.outputdir 635*9f923083SAlexander Pyhalov 636*9f923083SAlexander Pyhalov while not done: 637*9f923083SAlexander Pyhalov l = [] 638*9f923083SAlexander Pyhalov components -= 1 639*9f923083SAlexander Pyhalov for testfile in tmp_dict.keys(): 640*9f923083SAlexander Pyhalov uniq = '/'.join(testfile.split('/')[components:]).lstrip('/') 641*9f923083SAlexander Pyhalov if uniq not in l: 642*9f923083SAlexander Pyhalov l.append(uniq) 643*9f923083SAlexander Pyhalov tmp_dict[testfile].outputdir = os.path.join(base, uniq) 644*9f923083SAlexander Pyhalov else: 645*9f923083SAlexander Pyhalov break 646*9f923083SAlexander Pyhalov done = total == len(l) 647*9f923083SAlexander Pyhalov 648*9f923083SAlexander Pyhalov def setup_logging(self, options): 649*9f923083SAlexander Pyhalov """ 650*9f923083SAlexander Pyhalov Two loggers are set up here. The first is for the logfile which 651*9f923083SAlexander Pyhalov will contain one line summarizing the test, including the test 652*9f923083SAlexander Pyhalov name, result, and running time. This logger will also capture the 653*9f923083SAlexander Pyhalov timestamped combined stdout and stderr of each run. The second 654*9f923083SAlexander Pyhalov logger is optional console output, which will contain only the one 655*9f923083SAlexander Pyhalov line summary. The loggers are initialized at two different levels 656*9f923083SAlexander Pyhalov to facilitate segregating the output. 657*9f923083SAlexander Pyhalov """ 658*9f923083SAlexander Pyhalov if options.dryrun is True: 659*9f923083SAlexander Pyhalov return 660*9f923083SAlexander Pyhalov 661*9f923083SAlexander Pyhalov testlogger = logging.getLogger(__name__) 662*9f923083SAlexander Pyhalov testlogger.setLevel(logging.DEBUG) 663*9f923083SAlexander Pyhalov 664*9f923083SAlexander Pyhalov if options.cmd is not 'wrconfig': 665*9f923083SAlexander Pyhalov try: 666*9f923083SAlexander Pyhalov old = os.umask(0) 667*9f923083SAlexander Pyhalov os.makedirs(self.outputdir, mode=0777) 668*9f923083SAlexander Pyhalov os.umask(old) 669*9f923083SAlexander Pyhalov except OSError, e: 670*9f923083SAlexander Pyhalov fail('%s' % e) 671*9f923083SAlexander Pyhalov filename = os.path.join(self.outputdir, 'log') 672*9f923083SAlexander Pyhalov 673*9f923083SAlexander Pyhalov logfile = WatchedFileHandlerClosed(filename) 674*9f923083SAlexander Pyhalov logfile.setLevel(logging.DEBUG) 675*9f923083SAlexander Pyhalov logfilefmt = logging.Formatter('%(message)s') 676*9f923083SAlexander Pyhalov logfile.setFormatter(logfilefmt) 677*9f923083SAlexander Pyhalov testlogger.addHandler(logfile) 678*9f923083SAlexander Pyhalov 679*9f923083SAlexander Pyhalov cons = logging.StreamHandler() 680*9f923083SAlexander Pyhalov cons.setLevel(logging.INFO) 681*9f923083SAlexander Pyhalov consfmt = logging.Formatter('%(message)s') 682*9f923083SAlexander Pyhalov cons.setFormatter(consfmt) 683*9f923083SAlexander Pyhalov testlogger.addHandler(cons) 684*9f923083SAlexander Pyhalov 685*9f923083SAlexander Pyhalov return testlogger 686*9f923083SAlexander Pyhalov 687*9f923083SAlexander Pyhalov def run(self, options): 688*9f923083SAlexander Pyhalov """ 689*9f923083SAlexander Pyhalov Walk through all the Tests and TestGroups, calling run(). 690*9f923083SAlexander Pyhalov """ 691*9f923083SAlexander Pyhalov try: 692*9f923083SAlexander Pyhalov os.chdir(self.outputdir) 693*9f923083SAlexander Pyhalov except OSError: 694*9f923083SAlexander Pyhalov fail('Could not change to directory %s' % self.outputdir) 695*9f923083SAlexander Pyhalov for test in sorted(self.tests.keys()): 696*9f923083SAlexander Pyhalov self.tests[test].run(self.logger, options) 697*9f923083SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 698*9f923083SAlexander Pyhalov self.testgroups[testgroup].run(self.logger, options) 699*9f923083SAlexander Pyhalov 700*9f923083SAlexander Pyhalov def summary(self): 701*9f923083SAlexander Pyhalov if Result.total is 0: 702*9f923083SAlexander Pyhalov return 703*9f923083SAlexander Pyhalov 704*9f923083SAlexander Pyhalov print '\nResults Summary' 705*9f923083SAlexander Pyhalov for key in Result.runresults.keys(): 706*9f923083SAlexander Pyhalov if Result.runresults[key] is not 0: 707*9f923083SAlexander Pyhalov print '%s\t% 4d' % (key, Result.runresults[key]) 708*9f923083SAlexander Pyhalov 709*9f923083SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 710*9f923083SAlexander Pyhalov h, m = divmod(m, 60) 711*9f923083SAlexander Pyhalov print '\nRunning Time:\t%02d:%02d:%02d' % (h, m, s) 712*9f923083SAlexander Pyhalov print 'Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) / 713*9f923083SAlexander Pyhalov float(Result.total)) * 100) 714*9f923083SAlexander Pyhalov print 'Log directory:\t%s' % self.outputdir 715*9f923083SAlexander Pyhalov 716*9f923083SAlexander Pyhalov 717*9f923083SAlexander Pyhalovdef verify_file(pathname): 718*9f923083SAlexander Pyhalov """ 719*9f923083SAlexander Pyhalov Verify that the supplied pathname is an executable regular file. 720*9f923083SAlexander Pyhalov """ 721*9f923083SAlexander Pyhalov if os.path.isdir(pathname) or os.path.islink(pathname): 722*9f923083SAlexander Pyhalov return False 723*9f923083SAlexander Pyhalov 724*9f923083SAlexander Pyhalov if os.path.isfile(pathname) and os.access(pathname, os.X_OK): 725*9f923083SAlexander Pyhalov return True 726*9f923083SAlexander Pyhalov 727*9f923083SAlexander Pyhalov return False 728*9f923083SAlexander Pyhalov 729*9f923083SAlexander Pyhalov 730*9f923083SAlexander Pyhalovdef verify_user(user, logger): 731*9f923083SAlexander Pyhalov """ 732*9f923083SAlexander Pyhalov Verify that the specified user exists on this system, and can execute 733*9f923083SAlexander Pyhalov sudo without being prompted for a password. 734*9f923083SAlexander Pyhalov """ 735*9f923083SAlexander Pyhalov testcmd = [SUDO, '-n', '-u', user, TRUE] 736*9f923083SAlexander Pyhalov 737*9f923083SAlexander Pyhalov if user in Cmd.verified_users: 738*9f923083SAlexander Pyhalov return True 739*9f923083SAlexander Pyhalov 740*9f923083SAlexander Pyhalov try: 741*9f923083SAlexander Pyhalov _ = getpwnam(user) 742*9f923083SAlexander Pyhalov except KeyError: 743*9f923083SAlexander Pyhalov logger.info("Warning: user '%s' does not exist.", user) 744*9f923083SAlexander Pyhalov return False 745*9f923083SAlexander Pyhalov 746*9f923083SAlexander Pyhalov p = Popen(testcmd) 747*9f923083SAlexander Pyhalov p.wait() 748*9f923083SAlexander Pyhalov if p.returncode is not 0: 749*9f923083SAlexander Pyhalov logger.info("Warning: user '%s' cannot use passwordless sudo.", user) 750*9f923083SAlexander Pyhalov return False 751*9f923083SAlexander Pyhalov else: 752*9f923083SAlexander Pyhalov Cmd.verified_users.append(user) 753*9f923083SAlexander Pyhalov 754*9f923083SAlexander Pyhalov return True 755*9f923083SAlexander Pyhalov 756*9f923083SAlexander Pyhalov 757*9f923083SAlexander Pyhalovdef find_tests(testrun, options): 758*9f923083SAlexander Pyhalov """ 759*9f923083SAlexander Pyhalov For the given list of pathnames, add files as Tests. For directories, 760*9f923083SAlexander Pyhalov if do_groups is True, add the directory as a TestGroup. If False, 761*9f923083SAlexander Pyhalov recursively search for executable files. 762*9f923083SAlexander Pyhalov """ 763*9f923083SAlexander Pyhalov 764*9f923083SAlexander Pyhalov for p in sorted(options.pathnames): 765*9f923083SAlexander Pyhalov if os.path.isdir(p): 766*9f923083SAlexander Pyhalov for dirname, _, filenames in os.walk(p): 767*9f923083SAlexander Pyhalov if options.do_groups: 768*9f923083SAlexander Pyhalov testrun.addtestgroup(dirname, filenames, options) 769*9f923083SAlexander Pyhalov else: 770*9f923083SAlexander Pyhalov for f in sorted(filenames): 771*9f923083SAlexander Pyhalov testrun.addtest(os.path.join(dirname, f), options) 772*9f923083SAlexander Pyhalov else: 773*9f923083SAlexander Pyhalov testrun.addtest(p, options) 774*9f923083SAlexander Pyhalov 775*9f923083SAlexander Pyhalov 776*9f923083SAlexander Pyhalovdef fail(retstr, ret=1): 777*9f923083SAlexander Pyhalov print '%s: %s' % (argv[0], retstr) 778*9f923083SAlexander Pyhalov exit(ret) 779*9f923083SAlexander Pyhalov 780*9f923083SAlexander Pyhalov 781*9f923083SAlexander Pyhalovdef options_cb(option, opt_str, value, parser): 782*9f923083SAlexander Pyhalov path_options = ['runfile', 'outputdir', 'template'] 783*9f923083SAlexander Pyhalov 784*9f923083SAlexander Pyhalov if option.dest is 'runfile' and '-w' in parser.rargs or \ 785*9f923083SAlexander Pyhalov option.dest is 'template' and '-c' in parser.rargs: 786*9f923083SAlexander Pyhalov fail('-c and -w are mutually exclusive.') 787*9f923083SAlexander Pyhalov 788*9f923083SAlexander Pyhalov if opt_str in parser.rargs: 789*9f923083SAlexander Pyhalov fail('%s may only be specified once.' % opt_str) 790*9f923083SAlexander Pyhalov 791*9f923083SAlexander Pyhalov if option.dest is 'runfile': 792*9f923083SAlexander Pyhalov parser.values.cmd = 'rdconfig' 793*9f923083SAlexander Pyhalov if option.dest is 'template': 794*9f923083SAlexander Pyhalov parser.values.cmd = 'wrconfig' 795*9f923083SAlexander Pyhalov 796*9f923083SAlexander Pyhalov setattr(parser.values, option.dest, value) 797*9f923083SAlexander Pyhalov if option.dest in path_options: 798*9f923083SAlexander Pyhalov setattr(parser.values, option.dest, os.path.abspath(value)) 799*9f923083SAlexander Pyhalov 800*9f923083SAlexander Pyhalov 801*9f923083SAlexander Pyhalovdef parse_args(): 802*9f923083SAlexander Pyhalov parser = OptionParser() 803*9f923083SAlexander Pyhalov parser.add_option('-c', action='callback', callback=options_cb, 804*9f923083SAlexander Pyhalov type='string', dest='runfile', metavar='runfile', 805*9f923083SAlexander Pyhalov help='Specify tests to run via config file.') 806*9f923083SAlexander Pyhalov parser.add_option('-d', action='store_true', default=False, dest='dryrun', 807*9f923083SAlexander Pyhalov help='Dry run. Print tests, but take no other action.') 808*9f923083SAlexander Pyhalov parser.add_option('-g', action='store_true', default=False, 809*9f923083SAlexander Pyhalov dest='do_groups', help='Make directories TestGroups.') 810*9f923083SAlexander Pyhalov parser.add_option('-o', action='callback', callback=options_cb, 811*9f923083SAlexander Pyhalov default=BASEDIR, dest='outputdir', type='string', 812*9f923083SAlexander Pyhalov metavar='outputdir', help='Specify an output directory.') 813*9f923083SAlexander Pyhalov parser.add_option('-p', action='callback', callback=options_cb, 814*9f923083SAlexander Pyhalov default='', dest='pre', metavar='script', 815*9f923083SAlexander Pyhalov type='string', help='Specify a pre script.') 816*9f923083SAlexander Pyhalov parser.add_option('-P', action='callback', callback=options_cb, 817*9f923083SAlexander Pyhalov default='', dest='post', metavar='script', 818*9f923083SAlexander Pyhalov type='string', help='Specify a post script.') 819*9f923083SAlexander Pyhalov parser.add_option('-q', action='store_true', default=False, dest='quiet', 820*9f923083SAlexander Pyhalov help='Silence on the console during a test run.') 821*9f923083SAlexander Pyhalov parser.add_option('-t', action='callback', callback=options_cb, default=60, 822*9f923083SAlexander Pyhalov dest='timeout', metavar='seconds', type='int', 823*9f923083SAlexander Pyhalov help='Timeout (in seconds) for an individual test.') 824*9f923083SAlexander Pyhalov parser.add_option('-u', action='callback', callback=options_cb, 825*9f923083SAlexander Pyhalov default='', dest='user', metavar='user', type='string', 826*9f923083SAlexander Pyhalov help='Specify a different user name to run as.') 827*9f923083SAlexander Pyhalov parser.add_option('-w', action='callback', callback=options_cb, 828*9f923083SAlexander Pyhalov default=None, dest='template', metavar='template', 829*9f923083SAlexander Pyhalov type='string', help='Create a new config file.') 830*9f923083SAlexander Pyhalov parser.add_option('-x', action='callback', callback=options_cb, default='', 831*9f923083SAlexander Pyhalov dest='pre_user', metavar='pre_user', type='string', 832*9f923083SAlexander Pyhalov help='Specify a user to execute the pre script.') 833*9f923083SAlexander Pyhalov parser.add_option('-X', action='callback', callback=options_cb, default='', 834*9f923083SAlexander Pyhalov dest='post_user', metavar='post_user', type='string', 835*9f923083SAlexander Pyhalov help='Specify a user to execute the post script.') 836*9f923083SAlexander Pyhalov (options, pathnames) = parser.parse_args() 837*9f923083SAlexander Pyhalov 838*9f923083SAlexander Pyhalov if not options.runfile and not options.template: 839*9f923083SAlexander Pyhalov options.cmd = 'runtests' 840*9f923083SAlexander Pyhalov 841*9f923083SAlexander Pyhalov if options.runfile and len(pathnames): 842*9f923083SAlexander Pyhalov fail('Extraneous arguments.') 843*9f923083SAlexander Pyhalov 844*9f923083SAlexander Pyhalov options.pathnames = [os.path.abspath(path) for path in pathnames] 845*9f923083SAlexander Pyhalov 846*9f923083SAlexander Pyhalov return options 847*9f923083SAlexander Pyhalov 848*9f923083SAlexander Pyhalov 849*9f923083SAlexander Pyhalovdef main(): 850*9f923083SAlexander Pyhalov options = parse_args() 851*9f923083SAlexander Pyhalov testrun = TestRun(options) 852*9f923083SAlexander Pyhalov 853*9f923083SAlexander Pyhalov if options.cmd is 'runtests': 854*9f923083SAlexander Pyhalov find_tests(testrun, options) 855*9f923083SAlexander Pyhalov elif options.cmd is 'rdconfig': 856*9f923083SAlexander Pyhalov testrun.read(testrun.logger, options) 857*9f923083SAlexander Pyhalov elif options.cmd is 'wrconfig': 858*9f923083SAlexander Pyhalov find_tests(testrun, options) 859*9f923083SAlexander Pyhalov testrun.write(options) 860*9f923083SAlexander Pyhalov exit(0) 861*9f923083SAlexander Pyhalov else: 862*9f923083SAlexander Pyhalov fail('Unknown command specified') 863*9f923083SAlexander Pyhalov 864*9f923083SAlexander Pyhalov testrun.complete_outputdirs() 865*9f923083SAlexander Pyhalov testrun.run(options) 866*9f923083SAlexander Pyhalov testrun.summary() 867*9f923083SAlexander Pyhalov exit(0) 868*9f923083SAlexander Pyhalov 869*9f923083SAlexander Pyhalov 870*9f923083SAlexander Pyhalovif __name__ == '__main__': 871*9f923083SAlexander Pyhalov main() 872