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