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