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# 15d583b39bSJohn Wren Kennedy# Copyright (c) 2012 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 237d583b39bSJohn Wren Kennedy user = ' (run as %s)' % self.user if len(self.user) else '' 238d583b39bSJohn Wren Kennedy msga = 'Test: %s%s ' % (self.pathname, user) 239d583b39bSJohn Wren Kennedy msgb = '[%s] [%s]' % (self.result.runtime, self.result.result) 240d583b39bSJohn Wren Kennedy pad = ' ' * (80 - (len(msga) + len(msgb))) 241d583b39bSJohn Wren Kennedy 242d583b39bSJohn Wren Kennedy # If -q is specified, only print a line for tests that didn't pass. 243d583b39bSJohn Wren Kennedy # This means passing tests need to be logged as DEBUG, or the one 244d583b39bSJohn Wren Kennedy # line summary will only be printed in the logfile for failures. 245d583b39bSJohn Wren Kennedy if not options.quiet: 246d583b39bSJohn Wren Kennedy logger.info('%s%s%s' % (msga, pad, msgb)) 247d583b39bSJohn Wren Kennedy elif self.result.result is not 'PASS': 248d583b39bSJohn Wren Kennedy logger.info('%s%s%s' % (msga, pad, msgb)) 249d583b39bSJohn Wren Kennedy else: 250d583b39bSJohn Wren Kennedy logger.debug('%s%s%s' % (msga, pad, msgb)) 251d583b39bSJohn Wren Kennedy 252d583b39bSJohn Wren Kennedy lines = self.result.stdout + self.result.stderr 253d583b39bSJohn Wren Kennedy for dt, line in sorted(lines): 254d583b39bSJohn Wren Kennedy logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line)) 255d583b39bSJohn Wren Kennedy 256d583b39bSJohn Wren Kennedy if len(self.result.stdout): 257d583b39bSJohn Wren Kennedy with open(os.path.join(self.outputdir, 'stdout'), 'w') as out: 258d583b39bSJohn Wren Kennedy for _, line in self.result.stdout: 259d583b39bSJohn Wren Kennedy os.write(out.fileno(), '%s\n' % line) 260d583b39bSJohn Wren Kennedy if len(self.result.stderr): 261d583b39bSJohn Wren Kennedy with open(os.path.join(self.outputdir, 'stderr'), 'w') as err: 262d583b39bSJohn Wren Kennedy for _, line in self.result.stderr: 263d583b39bSJohn Wren Kennedy os.write(err.fileno(), '%s\n' % line) 264d583b39bSJohn Wren Kennedy if len(self.result.stdout) and len(self.result.stderr): 265d583b39bSJohn Wren Kennedy with open(os.path.join(self.outputdir, 'merged'), 'w') as merged: 266d583b39bSJohn Wren Kennedy for _, line in sorted(lines): 267d583b39bSJohn Wren Kennedy os.write(merged.fileno(), '%s\n' % line) 268d583b39bSJohn Wren Kennedy 269d583b39bSJohn Wren Kennedy 270d583b39bSJohn Wren Kennedyclass Test(Cmd): 271d583b39bSJohn Wren Kennedy props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post', 272d583b39bSJohn Wren Kennedy 'post_user'] 273d583b39bSJohn Wren Kennedy 274d583b39bSJohn Wren Kennedy def __init__(self, pathname, outputdir=None, timeout=None, user=None, 275d583b39bSJohn Wren Kennedy pre=None, pre_user=None, post=None, post_user=None): 276d583b39bSJohn Wren Kennedy super(Test, self).__init__(pathname, outputdir, timeout, user) 277d583b39bSJohn Wren Kennedy self.pre = pre or '' 278d583b39bSJohn Wren Kennedy self.pre_user = pre_user or '' 279d583b39bSJohn Wren Kennedy self.post = post or '' 280d583b39bSJohn Wren Kennedy self.post_user = post_user or '' 281d583b39bSJohn Wren Kennedy 282d583b39bSJohn Wren Kennedy def __str__(self): 283d583b39bSJohn Wren Kennedy post_user = pre_user = '' 284d583b39bSJohn Wren Kennedy if len(self.pre_user): 285d583b39bSJohn Wren Kennedy pre_user = ' (as %s)' % (self.pre_user) 286d583b39bSJohn Wren Kennedy if len(self.post_user): 287d583b39bSJohn Wren Kennedy post_user = ' (as %s)' % (self.post_user) 288d583b39bSJohn Wren Kennedy return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nPre: %s%s\nPost: " \ 289d583b39bSJohn Wren Kennedy "%s%s\nUser: %s\n" % (self.pathname, self.outputdir, 290d583b39bSJohn Wren Kennedy self.timeout, self.pre, pre_user, self.post, post_user, 291d583b39bSJohn Wren Kennedy self.user) 292d583b39bSJohn Wren Kennedy 293d583b39bSJohn Wren Kennedy def verify(self, logger): 294d583b39bSJohn Wren Kennedy """ 295d583b39bSJohn Wren Kennedy Check the pre/post scripts, user and Test. Omit the Test from this 296d583b39bSJohn Wren Kennedy run if there are any problems. 297d583b39bSJohn Wren Kennedy """ 298d583b39bSJohn Wren Kennedy files = [self.pre, self.pathname, self.post] 299d583b39bSJohn Wren Kennedy users = [self.pre_user, self.user, self.post_user] 300d583b39bSJohn Wren Kennedy 301d583b39bSJohn Wren Kennedy for f in [f for f in files if len(f)]: 302d583b39bSJohn Wren Kennedy if not verify_file(f): 303d583b39bSJohn Wren Kennedy logger.info("Warning: Test '%s' not added to this run because" 304d583b39bSJohn Wren Kennedy " it failed verification." % f) 305d583b39bSJohn Wren Kennedy return False 306d583b39bSJohn Wren Kennedy 307d583b39bSJohn Wren Kennedy for user in [user for user in users if len(user)]: 308d583b39bSJohn Wren Kennedy if not verify_user(user, logger): 309d583b39bSJohn Wren Kennedy logger.info("Not adding Test '%s' to this run." % 310d583b39bSJohn Wren Kennedy self.pathname) 311d583b39bSJohn Wren Kennedy return False 312d583b39bSJohn Wren Kennedy 313d583b39bSJohn Wren Kennedy return True 314d583b39bSJohn Wren Kennedy 315d583b39bSJohn Wren Kennedy def run(self, logger, options): 316d583b39bSJohn Wren Kennedy """ 317d583b39bSJohn Wren Kennedy Create Cmd instances for the pre/post scripts. If the pre script 318d583b39bSJohn Wren Kennedy doesn't pass, skip this Test. Run the post script regardless. 319d583b39bSJohn Wren Kennedy """ 320d583b39bSJohn Wren Kennedy pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir, 321d583b39bSJohn Wren Kennedy os.path.basename(self.pre)), timeout=self.timeout, 322d583b39bSJohn Wren Kennedy user=self.pre_user) 323d583b39bSJohn Wren Kennedy test = Cmd(self.pathname, outputdir=self.outputdir, 324d583b39bSJohn Wren Kennedy timeout=self.timeout, user=self.user) 325d583b39bSJohn Wren Kennedy posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir, 326d583b39bSJohn Wren Kennedy os.path.basename(self.post)), timeout=self.timeout, 327d583b39bSJohn Wren Kennedy user=self.post_user) 328d583b39bSJohn Wren Kennedy 329d583b39bSJohn Wren Kennedy cont = True 330d583b39bSJohn Wren Kennedy if len(pretest.pathname): 331d583b39bSJohn Wren Kennedy pretest.run(options) 332d583b39bSJohn Wren Kennedy cont = pretest.result.result is 'PASS' 333d583b39bSJohn Wren Kennedy pretest.log(logger, options) 334d583b39bSJohn Wren Kennedy 335d583b39bSJohn Wren Kennedy if cont: 336d583b39bSJohn Wren Kennedy test.run(options) 337d583b39bSJohn Wren Kennedy else: 338d583b39bSJohn Wren Kennedy test.skip() 339d583b39bSJohn Wren Kennedy 340d583b39bSJohn Wren Kennedy test.log(logger, options) 341d583b39bSJohn Wren Kennedy 342d583b39bSJohn Wren Kennedy if len(posttest.pathname): 343d583b39bSJohn Wren Kennedy posttest.run(options) 344d583b39bSJohn Wren Kennedy posttest.log(logger, options) 345d583b39bSJohn Wren Kennedy 346d583b39bSJohn Wren Kennedy 347d583b39bSJohn Wren Kennedyclass TestGroup(Test): 348d583b39bSJohn Wren Kennedy props = Test.props + ['tests'] 349d583b39bSJohn Wren Kennedy 350d583b39bSJohn Wren Kennedy def __init__(self, pathname, outputdir=None, timeout=None, user=None, 351d583b39bSJohn Wren Kennedy pre=None, pre_user=None, post=None, post_user=None, 352d583b39bSJohn Wren Kennedy tests=None): 353d583b39bSJohn Wren Kennedy super(TestGroup, self).__init__(pathname, outputdir, timeout, user, 354d583b39bSJohn Wren Kennedy pre, pre_user, post, post_user) 355d583b39bSJohn Wren Kennedy self.tests = tests or [] 356d583b39bSJohn Wren Kennedy 357d583b39bSJohn Wren Kennedy def __str__(self): 358d583b39bSJohn Wren Kennedy post_user = pre_user = '' 359d583b39bSJohn Wren Kennedy if len(self.pre_user): 360d583b39bSJohn Wren Kennedy pre_user = ' (as %s)' % (self.pre_user) 361d583b39bSJohn Wren Kennedy if len(self.post_user): 362d583b39bSJohn Wren Kennedy post_user = ' (as %s)' % (self.post_user) 363d583b39bSJohn Wren Kennedy return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %s\n" \ 364d583b39bSJohn Wren Kennedy "Pre: %s%s\nPost: %s%s\nUser: %s\n" % (self.pathname, 365d583b39bSJohn Wren Kennedy self.outputdir, self.tests, self.timeout, self.pre, pre_user, 366d583b39bSJohn Wren Kennedy self.post, post_user, self.user) 367d583b39bSJohn Wren Kennedy 368d583b39bSJohn Wren Kennedy def verify(self, logger): 369d583b39bSJohn Wren Kennedy """ 370d583b39bSJohn Wren Kennedy Check the pre/post scripts, user and tests in this TestGroup. Omit 371d583b39bSJohn Wren Kennedy the TestGroup entirely, or simply delete the relevant tests in the 372d583b39bSJohn Wren Kennedy group, if that's all that's required. 373d583b39bSJohn Wren Kennedy """ 374d583b39bSJohn Wren Kennedy # If the pre or post scripts are relative pathnames, convert to 375d583b39bSJohn Wren Kennedy # absolute, so they stand a chance of passing verification. 376d583b39bSJohn Wren Kennedy if len(self.pre) and not os.path.isabs(self.pre): 377d583b39bSJohn Wren Kennedy self.pre = os.path.join(self.pathname, self.pre) 378d583b39bSJohn Wren Kennedy if len(self.post) and not os.path.isabs(self.post): 379d583b39bSJohn Wren Kennedy self.post = os.path.join(self.pathname, self.post) 380d583b39bSJohn Wren Kennedy 381d583b39bSJohn Wren Kennedy auxfiles = [self.pre, self.post] 382d583b39bSJohn Wren Kennedy users = [self.pre_user, self.user, self.post_user] 383d583b39bSJohn Wren Kennedy 384d583b39bSJohn Wren Kennedy for f in [f for f in auxfiles if len(f)]: 385d583b39bSJohn Wren Kennedy if self.pathname != os.path.dirname(f): 386d583b39bSJohn Wren Kennedy logger.info("Warning: TestGroup '%s' not added to this run. " 387d583b39bSJohn Wren Kennedy "Auxiliary script '%s' exists in a different " 388d583b39bSJohn Wren Kennedy "directory." % (self.pathname, f)) 389d583b39bSJohn Wren Kennedy return False 390d583b39bSJohn Wren Kennedy 391d583b39bSJohn Wren Kennedy if not verify_file(f): 392d583b39bSJohn Wren Kennedy logger.info("Warning: TestGroup '%s' not added to this run. " 393d583b39bSJohn Wren Kennedy "Auxiliary script '%s' failed verification." % 394d583b39bSJohn Wren Kennedy (self.pathname, f)) 395d583b39bSJohn Wren Kennedy return False 396d583b39bSJohn Wren Kennedy 397d583b39bSJohn Wren Kennedy for user in [user for user in users if len(user)]: 398d583b39bSJohn Wren Kennedy if not verify_user(user, logger): 399d583b39bSJohn Wren Kennedy logger.info("Not adding TestGroup '%s' to this run." % 400d583b39bSJohn Wren Kennedy self.pathname) 401d583b39bSJohn Wren Kennedy return False 402d583b39bSJohn Wren Kennedy 403d583b39bSJohn Wren Kennedy # If one of the tests is invalid, delete it, log it, and drive on. 404d583b39bSJohn Wren Kennedy for test in self.tests: 405d583b39bSJohn Wren Kennedy if not verify_file(os.path.join(self.pathname, test)): 406d583b39bSJohn Wren Kennedy del self.tests[self.tests.index(test)] 407d583b39bSJohn Wren Kennedy logger.info("Warning: Test '%s' removed from TestGroup '%s' " 408d583b39bSJohn Wren Kennedy "because it failed verification." % (test, 409d583b39bSJohn Wren Kennedy self.pathname)) 410d583b39bSJohn Wren Kennedy 411d583b39bSJohn Wren Kennedy return len(self.tests) is not 0 412d583b39bSJohn Wren Kennedy 413d583b39bSJohn Wren Kennedy def run(self, logger, options): 414d583b39bSJohn Wren Kennedy """ 415d583b39bSJohn Wren Kennedy Create Cmd instances for the pre/post scripts. If the pre script 416d583b39bSJohn Wren Kennedy doesn't pass, skip all the tests in this TestGroup. Run the post 417d583b39bSJohn Wren Kennedy script regardless. 418d583b39bSJohn Wren Kennedy """ 419d583b39bSJohn Wren Kennedy pretest = Cmd(self.pre, outputdir=os.path.join(self.outputdir, 420d583b39bSJohn Wren Kennedy os.path.basename(self.pre)), timeout=self.timeout, 421d583b39bSJohn Wren Kennedy user=self.pre_user) 422d583b39bSJohn Wren Kennedy posttest = Cmd(self.post, outputdir=os.path.join(self.outputdir, 423d583b39bSJohn Wren Kennedy os.path.basename(self.post)), timeout=self.timeout, 424d583b39bSJohn Wren Kennedy user=self.post_user) 425d583b39bSJohn Wren Kennedy 426d583b39bSJohn Wren Kennedy cont = True 427d583b39bSJohn Wren Kennedy if len(pretest.pathname): 428d583b39bSJohn Wren Kennedy pretest.run(options) 429d583b39bSJohn Wren Kennedy cont = pretest.result.result is 'PASS' 430d583b39bSJohn Wren Kennedy pretest.log(logger, options) 431d583b39bSJohn Wren Kennedy 432d583b39bSJohn Wren Kennedy for fname in self.tests: 433d583b39bSJohn Wren Kennedy test = Cmd(os.path.join(self.pathname, fname), 434d583b39bSJohn Wren Kennedy outputdir=os.path.join(self.outputdir, fname), 435d583b39bSJohn Wren Kennedy timeout=self.timeout, user=self.user) 436d583b39bSJohn Wren Kennedy if cont: 437d583b39bSJohn Wren Kennedy test.run(options) 438d583b39bSJohn Wren Kennedy else: 439d583b39bSJohn Wren Kennedy test.skip() 440d583b39bSJohn Wren Kennedy 441d583b39bSJohn Wren Kennedy test.log(logger, options) 442d583b39bSJohn Wren Kennedy 443d583b39bSJohn Wren Kennedy if len(posttest.pathname): 444d583b39bSJohn Wren Kennedy posttest.run(options) 445d583b39bSJohn Wren Kennedy posttest.log(logger, options) 446d583b39bSJohn Wren Kennedy 447d583b39bSJohn Wren Kennedy 448d583b39bSJohn Wren Kennedyclass TestRun(object): 449d583b39bSJohn Wren Kennedy props = ['quiet', 'outputdir'] 450d583b39bSJohn Wren Kennedy 451d583b39bSJohn Wren Kennedy def __init__(self, options): 452d583b39bSJohn Wren Kennedy self.tests = {} 453d583b39bSJohn Wren Kennedy self.testgroups = {} 454d583b39bSJohn Wren Kennedy self.starttime = time() 455d583b39bSJohn Wren Kennedy self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') 456d583b39bSJohn Wren Kennedy self.outputdir = os.path.join(options.outputdir, self.timestamp) 457d583b39bSJohn Wren Kennedy self.logger = self.setup_logging(options) 458d583b39bSJohn Wren Kennedy self.defaults = [ 459d583b39bSJohn Wren Kennedy ('outputdir', BASEDIR), 460d583b39bSJohn Wren Kennedy ('quiet', False), 461d583b39bSJohn Wren Kennedy ('timeout', 60), 462d583b39bSJohn Wren Kennedy ('user', ''), 463d583b39bSJohn Wren Kennedy ('pre', ''), 464d583b39bSJohn Wren Kennedy ('pre_user', ''), 465d583b39bSJohn Wren Kennedy ('post', ''), 466d583b39bSJohn Wren Kennedy ('post_user', '') 467d583b39bSJohn Wren Kennedy ] 468d583b39bSJohn Wren Kennedy 469d583b39bSJohn Wren Kennedy def __str__(self): 470d583b39bSJohn Wren Kennedy s = 'TestRun:\n outputdir: %s\n' % self.outputdir 471d583b39bSJohn Wren Kennedy s += 'TESTS:\n' 472d583b39bSJohn Wren Kennedy for key in sorted(self.tests.keys()): 473d583b39bSJohn Wren Kennedy s += '%s%s' % (self.tests[key].__str__(), '\n') 474d583b39bSJohn Wren Kennedy s += 'TESTGROUPS:\n' 475d583b39bSJohn Wren Kennedy for key in sorted(self.testgroups.keys()): 476d583b39bSJohn Wren Kennedy s += '%s%s' % (self.testgroups[key].__str__(), '\n') 477d583b39bSJohn Wren Kennedy return s 478d583b39bSJohn Wren Kennedy 479d583b39bSJohn Wren Kennedy def addtest(self, pathname, options): 480d583b39bSJohn Wren Kennedy """ 481d583b39bSJohn Wren Kennedy Create a new Test, and apply any properties that were passed in 482d583b39bSJohn Wren Kennedy from the command line. If it passes verification, add it to the 483d583b39bSJohn Wren Kennedy TestRun. 484d583b39bSJohn Wren Kennedy """ 485d583b39bSJohn Wren Kennedy test = Test(pathname) 486d583b39bSJohn Wren Kennedy for prop in Test.props: 487d583b39bSJohn Wren Kennedy setattr(test, prop, getattr(options, prop)) 488d583b39bSJohn Wren Kennedy 489d583b39bSJohn Wren Kennedy if test.verify(self.logger): 490d583b39bSJohn Wren Kennedy self.tests[pathname] = test 491d583b39bSJohn Wren Kennedy 492d583b39bSJohn Wren Kennedy def addtestgroup(self, dirname, filenames, options): 493d583b39bSJohn Wren Kennedy """ 494d583b39bSJohn Wren Kennedy Create a new TestGroup, and apply any properties that were passed 495d583b39bSJohn Wren Kennedy in from the command line. If it passes verification, add it to the 496d583b39bSJohn Wren Kennedy TestRun. 497d583b39bSJohn Wren Kennedy """ 498d583b39bSJohn Wren Kennedy if dirname not in self.testgroups: 499d583b39bSJohn Wren Kennedy testgroup = TestGroup(dirname) 500d583b39bSJohn Wren Kennedy for prop in Test.props: 501d583b39bSJohn Wren Kennedy setattr(testgroup, prop, getattr(options, prop)) 502d583b39bSJohn Wren Kennedy 503d583b39bSJohn Wren Kennedy # Prevent pre/post scripts from running as regular tests 504d583b39bSJohn Wren Kennedy for f in [testgroup.pre, testgroup.post]: 505d583b39bSJohn Wren Kennedy if f in filenames: 506d583b39bSJohn Wren Kennedy del filenames[filenames.index(f)] 507d583b39bSJohn Wren Kennedy 508d583b39bSJohn Wren Kennedy self.testgroups[dirname] = testgroup 509d583b39bSJohn Wren Kennedy self.testgroups[dirname].tests = sorted(filenames) 510d583b39bSJohn Wren Kennedy 511d583b39bSJohn Wren Kennedy testgroup.verify(self.logger) 512d583b39bSJohn Wren Kennedy 513d583b39bSJohn Wren Kennedy def read(self, logger, options): 514d583b39bSJohn Wren Kennedy """ 515d583b39bSJohn Wren Kennedy Read in the specified runfile, and apply the TestRun properties 516d583b39bSJohn Wren Kennedy listed in the 'DEFAULT' section to our TestRun. Then read each 517d583b39bSJohn Wren Kennedy section, and apply the appropriate properties to the Test or 518d583b39bSJohn Wren Kennedy TestGroup. Properties from individual sections override those set 519d583b39bSJohn Wren Kennedy in the 'DEFAULT' section. If the Test or TestGroup passes 520d583b39bSJohn Wren Kennedy verification, add it to the TestRun. 521d583b39bSJohn Wren Kennedy """ 522d583b39bSJohn Wren Kennedy config = ConfigParser.RawConfigParser() 523d583b39bSJohn Wren Kennedy if not len(config.read(options.runfile)): 524d583b39bSJohn Wren Kennedy fail("Coulnd't read config file %s" % options.runfile) 525d583b39bSJohn Wren Kennedy 526d583b39bSJohn Wren Kennedy for opt in TestRun.props: 527d583b39bSJohn Wren Kennedy if config.has_option('DEFAULT', opt): 528d583b39bSJohn Wren Kennedy setattr(self, opt, config.get('DEFAULT', opt)) 529d583b39bSJohn Wren Kennedy self.outputdir = os.path.join(self.outputdir, self.timestamp) 530d583b39bSJohn Wren Kennedy 531d583b39bSJohn Wren Kennedy for section in config.sections(): 532d583b39bSJohn Wren Kennedy if 'tests' in config.options(section): 533d583b39bSJohn Wren Kennedy testgroup = TestGroup(section) 534d583b39bSJohn Wren Kennedy for prop in TestGroup.props: 535d583b39bSJohn Wren Kennedy try: 536d583b39bSJohn Wren Kennedy setattr(testgroup, prop, config.get('DEFAULT', prop)) 537d583b39bSJohn Wren Kennedy setattr(testgroup, prop, config.get(section, prop)) 538d583b39bSJohn Wren Kennedy except ConfigParser.NoOptionError: 539d583b39bSJohn Wren Kennedy pass 540d583b39bSJohn Wren Kennedy 541d583b39bSJohn Wren Kennedy # Repopulate tests using eval to convert the string to a list 542d583b39bSJohn Wren Kennedy testgroup.tests = eval(config.get(section, 'tests')) 543d583b39bSJohn Wren Kennedy 544d583b39bSJohn Wren Kennedy if testgroup.verify(logger): 545d583b39bSJohn Wren Kennedy self.testgroups[section] = testgroup 546d583b39bSJohn Wren Kennedy else: 547d583b39bSJohn Wren Kennedy test = Test(section) 548d583b39bSJohn Wren Kennedy for prop in Test.props: 549d583b39bSJohn Wren Kennedy try: 550d583b39bSJohn Wren Kennedy setattr(test, prop, config.get('DEFAULT', prop)) 551d583b39bSJohn Wren Kennedy setattr(test, prop, config.get(section, prop)) 552d583b39bSJohn Wren Kennedy except ConfigParser.NoOptionError: 553d583b39bSJohn Wren Kennedy pass 554d583b39bSJohn Wren Kennedy if test.verify(logger): 555d583b39bSJohn Wren Kennedy self.tests[section] = test 556d583b39bSJohn Wren Kennedy 557d583b39bSJohn Wren Kennedy def write(self, options): 558d583b39bSJohn Wren Kennedy """ 559d583b39bSJohn Wren Kennedy Create a configuration file for editing and later use. The 560d583b39bSJohn Wren Kennedy 'DEFAULT' section of the config file is created from the 561d583b39bSJohn Wren Kennedy properties that were specified on the command line. Tests are 562d583b39bSJohn Wren Kennedy simply added as sections that inherit everything from the 563d583b39bSJohn Wren Kennedy 'DEFAULT' section. TestGroups are the same, except they get an 564d583b39bSJohn Wren Kennedy option including all the tests to run in that directory. 565d583b39bSJohn Wren Kennedy """ 566d583b39bSJohn Wren Kennedy 567d583b39bSJohn Wren Kennedy defaults = dict([(prop, getattr(options, prop)) for prop, _ in 568d583b39bSJohn Wren Kennedy self.defaults]) 569d583b39bSJohn Wren Kennedy config = ConfigParser.RawConfigParser(defaults) 570d583b39bSJohn Wren Kennedy 571d583b39bSJohn Wren Kennedy for test in sorted(self.tests.keys()): 572d583b39bSJohn Wren Kennedy config.add_section(test) 573d583b39bSJohn Wren Kennedy 574d583b39bSJohn Wren Kennedy for testgroup in sorted(self.testgroups.keys()): 575d583b39bSJohn Wren Kennedy config.add_section(testgroup) 576d583b39bSJohn Wren Kennedy config.set(testgroup, 'tests', self.testgroups[testgroup].tests) 577d583b39bSJohn Wren Kennedy 578d583b39bSJohn Wren Kennedy try: 579d583b39bSJohn Wren Kennedy with open(options.template, 'w') as f: 580d583b39bSJohn Wren Kennedy return config.write(f) 581d583b39bSJohn Wren Kennedy except IOError: 582d583b39bSJohn Wren Kennedy fail('Could not open \'%s\' for writing.' % options.template) 583d583b39bSJohn Wren Kennedy 584d583b39bSJohn Wren Kennedy def complete_outputdirs(self, options): 585d583b39bSJohn Wren Kennedy """ 586d583b39bSJohn Wren Kennedy Collect all the pathnames for Tests, and TestGroups. Work 587d583b39bSJohn Wren Kennedy backwards one pathname component at a time, to create a unique 588d583b39bSJohn Wren Kennedy directory name in which to deposit test output. Tests will be able 589d583b39bSJohn Wren Kennedy to write output files directly in the newly modified outputdir. 590d583b39bSJohn Wren Kennedy TestGroups will be able to create one subdirectory per test in the 591d583b39bSJohn Wren Kennedy outputdir, and are guaranteed uniqueness because a group can only 592d583b39bSJohn Wren Kennedy contain files in one directory. Pre and post tests will create a 593d583b39bSJohn Wren Kennedy directory rooted at the outputdir of the Test or TestGroup in 594d583b39bSJohn Wren Kennedy question for their output. 595d583b39bSJohn Wren Kennedy """ 596d583b39bSJohn Wren Kennedy done = False 597d583b39bSJohn Wren Kennedy components = 0 598d583b39bSJohn Wren Kennedy tmp_dict = dict(self.tests.items() + self.testgroups.items()) 599d583b39bSJohn Wren Kennedy total = len(tmp_dict) 600d583b39bSJohn Wren Kennedy base = self.outputdir 601d583b39bSJohn Wren Kennedy 602d583b39bSJohn Wren Kennedy while not done: 603d583b39bSJohn Wren Kennedy l = [] 604d583b39bSJohn Wren Kennedy components -= 1 605d583b39bSJohn Wren Kennedy for testfile in tmp_dict.keys(): 606d583b39bSJohn Wren Kennedy uniq = '/'.join(testfile.split('/')[components:]).lstrip('/') 607d583b39bSJohn Wren Kennedy if not uniq in l: 608d583b39bSJohn Wren Kennedy l.append(uniq) 609d583b39bSJohn Wren Kennedy tmp_dict[testfile].outputdir = os.path.join(base, uniq) 610d583b39bSJohn Wren Kennedy else: 611d583b39bSJohn Wren Kennedy break 612d583b39bSJohn Wren Kennedy done = total == len(l) 613d583b39bSJohn Wren Kennedy 614d583b39bSJohn Wren Kennedy def setup_logging(self, options): 615d583b39bSJohn Wren Kennedy """ 616d583b39bSJohn Wren Kennedy Two loggers are set up here. The first is for the logfile which 617d583b39bSJohn Wren Kennedy will contain one line summarizing the test, including the test 618d583b39bSJohn Wren Kennedy name, result, and running time. This logger will also capture the 619d583b39bSJohn Wren Kennedy timestamped combined stdout and stderr of each run. The second 620d583b39bSJohn Wren Kennedy logger is optional console output, which will contain only the one 621d583b39bSJohn Wren Kennedy line summary. The loggers are initialized at two different levels 622d583b39bSJohn Wren Kennedy to facilitate segregating the output. 623d583b39bSJohn Wren Kennedy """ 624d583b39bSJohn Wren Kennedy if options.dryrun is True: 625d583b39bSJohn Wren Kennedy return 626d583b39bSJohn Wren Kennedy 627d583b39bSJohn Wren Kennedy testlogger = logging.getLogger(__name__) 628d583b39bSJohn Wren Kennedy testlogger.setLevel(logging.DEBUG) 629d583b39bSJohn Wren Kennedy 630d583b39bSJohn Wren Kennedy if options.cmd is not 'wrconfig': 631d583b39bSJohn Wren Kennedy try: 632d583b39bSJohn Wren Kennedy old = os.umask(0) 633d583b39bSJohn Wren Kennedy os.makedirs(self.outputdir, mode=0777) 634d583b39bSJohn Wren Kennedy os.umask(old) 635d583b39bSJohn Wren Kennedy except OSError, e: 636d583b39bSJohn Wren Kennedy fail('%s' % e) 637d583b39bSJohn Wren Kennedy filename = os.path.join(self.outputdir, 'log') 638d583b39bSJohn Wren Kennedy 639d583b39bSJohn Wren Kennedy logfile = logging.FileHandler(filename) 640d583b39bSJohn Wren Kennedy logfile.setLevel(logging.DEBUG) 641d583b39bSJohn Wren Kennedy logfilefmt = logging.Formatter('%(message)s') 642d583b39bSJohn Wren Kennedy logfile.setFormatter(logfilefmt) 643d583b39bSJohn Wren Kennedy testlogger.addHandler(logfile) 644d583b39bSJohn Wren Kennedy 645d583b39bSJohn Wren Kennedy cons = logging.StreamHandler() 646d583b39bSJohn Wren Kennedy cons.setLevel(logging.INFO) 647d583b39bSJohn Wren Kennedy consfmt = logging.Formatter('%(message)s') 648d583b39bSJohn Wren Kennedy cons.setFormatter(consfmt) 649d583b39bSJohn Wren Kennedy testlogger.addHandler(cons) 650d583b39bSJohn Wren Kennedy 651d583b39bSJohn Wren Kennedy return testlogger 652d583b39bSJohn Wren Kennedy 653d583b39bSJohn Wren Kennedy def run(self, options): 654d583b39bSJohn Wren Kennedy """ 655d583b39bSJohn Wren Kennedy Walk through all the Tests and TestGroups, calling run(). 656d583b39bSJohn Wren Kennedy """ 657d583b39bSJohn Wren Kennedy try: 658d583b39bSJohn Wren Kennedy os.chdir(self.outputdir) 659d583b39bSJohn Wren Kennedy except OSError: 660d583b39bSJohn Wren Kennedy fail('Could not change to directory %s' % self.outputdir) 661d583b39bSJohn Wren Kennedy for test in sorted(self.tests.keys()): 662d583b39bSJohn Wren Kennedy self.tests[test].run(self.logger, options) 663d583b39bSJohn Wren Kennedy for testgroup in sorted(self.testgroups.keys()): 664d583b39bSJohn Wren Kennedy self.testgroups[testgroup].run(self.logger, options) 665d583b39bSJohn Wren Kennedy 666d583b39bSJohn Wren Kennedy def summary(self): 667d583b39bSJohn Wren Kennedy if Result.total is 0: 668d583b39bSJohn Wren Kennedy return 669d583b39bSJohn Wren Kennedy 670d583b39bSJohn Wren Kennedy print '\nResults Summary' 671d583b39bSJohn Wren Kennedy for key in Result.runresults.keys(): 672d583b39bSJohn Wren Kennedy if Result.runresults[key] is not 0: 673d583b39bSJohn Wren Kennedy print '%s\t% 4d' % (key, Result.runresults[key]) 674d583b39bSJohn Wren Kennedy 675d583b39bSJohn Wren Kennedy m, s = divmod(time() - self.starttime, 60) 676d583b39bSJohn Wren Kennedy h, m = divmod(m, 60) 677d583b39bSJohn Wren Kennedy print '\nRunning Time:\t%02d:%02d:%02d' % (h, m, s) 678d583b39bSJohn Wren Kennedy print 'Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) / 679d583b39bSJohn Wren Kennedy float(Result.total)) * 100) 680d583b39bSJohn Wren Kennedy print 'Log directory:\t%s' % self.outputdir 681d583b39bSJohn Wren Kennedy 682d583b39bSJohn Wren Kennedy 683d583b39bSJohn Wren Kennedydef verify_file(pathname): 684d583b39bSJohn Wren Kennedy """ 685d583b39bSJohn Wren Kennedy Verify that the supplied pathname is an executable regular file. 686d583b39bSJohn Wren Kennedy """ 687d583b39bSJohn Wren Kennedy if os.path.isdir(pathname) or os.path.islink(pathname): 688d583b39bSJohn Wren Kennedy return False 689d583b39bSJohn Wren Kennedy 690d583b39bSJohn Wren Kennedy if os.path.isfile(pathname) and os.access(pathname, os.X_OK): 691d583b39bSJohn Wren Kennedy return True 692d583b39bSJohn Wren Kennedy 693d583b39bSJohn Wren Kennedy return False 694d583b39bSJohn Wren Kennedy 695d583b39bSJohn Wren Kennedy 696d583b39bSJohn Wren Kennedydef verify_user(user, logger): 697d583b39bSJohn Wren Kennedy """ 698d583b39bSJohn Wren Kennedy Verify that the specified user exists on this system, and can execute 699d583b39bSJohn Wren Kennedy sudo without being prompted for a password. 700d583b39bSJohn Wren Kennedy """ 701d583b39bSJohn Wren Kennedy testcmd = [SUDO, '-n', '-u', user, TRUE] 702d583b39bSJohn Wren Kennedy can_sudo = exists = True 703d583b39bSJohn Wren Kennedy 704d583b39bSJohn Wren Kennedy if user in Cmd.verified_users: 705d583b39bSJohn Wren Kennedy return True 706d583b39bSJohn Wren Kennedy 707d583b39bSJohn Wren Kennedy try: 708d583b39bSJohn Wren Kennedy _ = getpwnam(user) 709d583b39bSJohn Wren Kennedy except KeyError: 710d583b39bSJohn Wren Kennedy exists = False 711d583b39bSJohn Wren Kennedy logger.info("Warning: user '%s' does not exist.", user) 712d583b39bSJohn Wren Kennedy return False 713d583b39bSJohn Wren Kennedy 714d583b39bSJohn Wren Kennedy p = Popen(testcmd) 715d583b39bSJohn Wren Kennedy p.wait() 716d583b39bSJohn Wren Kennedy if p.returncode is not 0: 717*5e989a96SDavid Höppner logger.info("Warning: user '%s' cannot use passwordless sudo.", user) 718d583b39bSJohn Wren Kennedy return False 719d583b39bSJohn Wren Kennedy else: 720d583b39bSJohn Wren Kennedy Cmd.verified_users.append(user) 721d583b39bSJohn Wren Kennedy 722d583b39bSJohn Wren Kennedy return True 723d583b39bSJohn Wren Kennedy 724d583b39bSJohn Wren Kennedy 725d583b39bSJohn Wren Kennedydef find_tests(testrun, options): 726d583b39bSJohn Wren Kennedy """ 727d583b39bSJohn Wren Kennedy For the given list of pathnames, add files as Tests. For directories, 728d583b39bSJohn Wren Kennedy if do_groups is True, add the directory as a TestGroup. If False, 729d583b39bSJohn Wren Kennedy recursively search for executable files. 730d583b39bSJohn Wren Kennedy """ 731d583b39bSJohn Wren Kennedy 732d583b39bSJohn Wren Kennedy for p in sorted(options.pathnames): 733d583b39bSJohn Wren Kennedy if os.path.isdir(p): 734d583b39bSJohn Wren Kennedy for dirname, _, filenames in os.walk(p): 735d583b39bSJohn Wren Kennedy if options.do_groups: 736d583b39bSJohn Wren Kennedy testrun.addtestgroup(dirname, filenames, options) 737d583b39bSJohn Wren Kennedy else: 738d583b39bSJohn Wren Kennedy for f in sorted(filenames): 739d583b39bSJohn Wren Kennedy testrun.addtest(os.path.join(dirname, f), options) 740d583b39bSJohn Wren Kennedy else: 741d583b39bSJohn Wren Kennedy testrun.addtest(p, options) 742d583b39bSJohn Wren Kennedy 743d583b39bSJohn Wren Kennedy 744d583b39bSJohn Wren Kennedydef fail(retstr, ret=1): 745d583b39bSJohn Wren Kennedy print '%s: %s' % (argv[0], retstr) 746d583b39bSJohn Wren Kennedy exit(ret) 747d583b39bSJohn Wren Kennedy 748d583b39bSJohn Wren Kennedy 749d583b39bSJohn Wren Kennedydef options_cb(option, opt_str, value, parser): 750d583b39bSJohn Wren Kennedy path_options = ['runfile', 'outputdir', 'template'] 751d583b39bSJohn Wren Kennedy 752d583b39bSJohn Wren Kennedy if option.dest is 'runfile' and '-w' in parser.rargs or \ 753d583b39bSJohn Wren Kennedy option.dest is 'template' and '-c' in parser.rargs: 754d583b39bSJohn Wren Kennedy fail('-c and -w are mutually exclusive.') 755d583b39bSJohn Wren Kennedy 756d583b39bSJohn Wren Kennedy if opt_str in parser.rargs: 757d583b39bSJohn Wren Kennedy fail('%s may only be specified once.' % opt_str) 758d583b39bSJohn Wren Kennedy 759d583b39bSJohn Wren Kennedy if option.dest is 'runfile': 760d583b39bSJohn Wren Kennedy parser.values.cmd = 'rdconfig' 761d583b39bSJohn Wren Kennedy if option.dest is 'template': 762d583b39bSJohn Wren Kennedy parser.values.cmd = 'wrconfig' 763d583b39bSJohn Wren Kennedy 764d583b39bSJohn Wren Kennedy setattr(parser.values, option.dest, value) 765d583b39bSJohn Wren Kennedy if option.dest in path_options: 766d583b39bSJohn Wren Kennedy setattr(parser.values, option.dest, os.path.abspath(value)) 767d583b39bSJohn Wren Kennedy 768d583b39bSJohn Wren Kennedy 769d583b39bSJohn Wren Kennedydef parse_args(): 770d583b39bSJohn Wren Kennedy parser = OptionParser() 771d583b39bSJohn Wren Kennedy parser.add_option('-c', action='callback', callback=options_cb, 772d583b39bSJohn Wren Kennedy type='string', dest='runfile', metavar='runfile', 773d583b39bSJohn Wren Kennedy help='Specify tests to run via config file.') 774d583b39bSJohn Wren Kennedy parser.add_option('-d', action='store_true', default=False, dest='dryrun', 775d583b39bSJohn Wren Kennedy help='Dry run. Print tests, but take no other action.') 776d583b39bSJohn Wren Kennedy parser.add_option('-g', action='store_true', default=False, 777d583b39bSJohn Wren Kennedy dest='do_groups', help='Make directories TestGroups.') 778d583b39bSJohn Wren Kennedy parser.add_option('-o', action='callback', callback=options_cb, 779d583b39bSJohn Wren Kennedy default=BASEDIR, dest='outputdir', type='string', 780d583b39bSJohn Wren Kennedy metavar='outputdir', help='Specify an output directory.') 781d583b39bSJohn Wren Kennedy parser.add_option('-p', action='callback', callback=options_cb, 782d583b39bSJohn Wren Kennedy default='', dest='pre', metavar='script', 783d583b39bSJohn Wren Kennedy type='string', help='Specify a pre script.') 784d583b39bSJohn Wren Kennedy parser.add_option('-P', action='callback', callback=options_cb, 785d583b39bSJohn Wren Kennedy default='', dest='post', metavar='script', 786d583b39bSJohn Wren Kennedy type='string', help='Specify a post script.') 787d583b39bSJohn Wren Kennedy parser.add_option('-q', action='store_true', default=False, dest='quiet', 788d583b39bSJohn Wren Kennedy help='Silence on the console during a test run.') 789d583b39bSJohn Wren Kennedy parser.add_option('-t', action='callback', callback=options_cb, default=60, 790d583b39bSJohn Wren Kennedy dest='timeout', metavar='seconds', type='int', 791d583b39bSJohn Wren Kennedy help='Timeout (in seconds) for an individual test.') 792d583b39bSJohn Wren Kennedy parser.add_option('-u', action='callback', callback=options_cb, 793d583b39bSJohn Wren Kennedy default='', dest='user', metavar='user', type='string', 794d583b39bSJohn Wren Kennedy help='Specify a different user name to run as.') 795d583b39bSJohn Wren Kennedy parser.add_option('-w', action='callback', callback=options_cb, 796d583b39bSJohn Wren Kennedy default=None, dest='template', metavar='template', 797d583b39bSJohn Wren Kennedy type='string', help='Create a new config file.') 798d583b39bSJohn Wren Kennedy parser.add_option('-x', action='callback', callback=options_cb, default='', 799d583b39bSJohn Wren Kennedy dest='pre_user', metavar='pre_user', type='string', 800d583b39bSJohn Wren Kennedy help='Specify a user to execute the pre script.') 801d583b39bSJohn Wren Kennedy parser.add_option('-X', action='callback', callback=options_cb, default='', 802d583b39bSJohn Wren Kennedy dest='post_user', metavar='post_user', type='string', 803d583b39bSJohn Wren Kennedy help='Specify a user to execute the post script.') 804d583b39bSJohn Wren Kennedy (options, pathnames) = parser.parse_args() 805d583b39bSJohn Wren Kennedy 806d583b39bSJohn Wren Kennedy if not options.runfile and not options.template: 807d583b39bSJohn Wren Kennedy options.cmd = 'runtests' 808d583b39bSJohn Wren Kennedy 809d583b39bSJohn Wren Kennedy if options.runfile and len(pathnames): 810d583b39bSJohn Wren Kennedy fail('Extraneous arguments.') 811d583b39bSJohn Wren Kennedy 812d583b39bSJohn Wren Kennedy options.pathnames = [os.path.abspath(path) for path in pathnames] 813d583b39bSJohn Wren Kennedy 814d583b39bSJohn Wren Kennedy return options 815d583b39bSJohn Wren Kennedy 816d583b39bSJohn Wren Kennedy 817d583b39bSJohn Wren Kennedydef main(args): 818d583b39bSJohn Wren Kennedy options = parse_args() 819d583b39bSJohn Wren Kennedy testrun = TestRun(options) 820d583b39bSJohn Wren Kennedy 821d583b39bSJohn Wren Kennedy if options.cmd is 'runtests': 822d583b39bSJohn Wren Kennedy find_tests(testrun, options) 823d583b39bSJohn Wren Kennedy elif options.cmd is 'rdconfig': 824d583b39bSJohn Wren Kennedy testrun.read(testrun.logger, options) 825d583b39bSJohn Wren Kennedy elif options.cmd is 'wrconfig': 826d583b39bSJohn Wren Kennedy find_tests(testrun, options) 827d583b39bSJohn Wren Kennedy testrun.write(options) 828d583b39bSJohn Wren Kennedy exit(0) 829d583b39bSJohn Wren Kennedy else: 830d583b39bSJohn Wren Kennedy fail('Unknown command specified') 831d583b39bSJohn Wren Kennedy 832d583b39bSJohn Wren Kennedy testrun.complete_outputdirs(options) 833d583b39bSJohn Wren Kennedy testrun.run(options) 834d583b39bSJohn Wren Kennedy testrun.summary() 835d583b39bSJohn Wren Kennedy exit(0) 836d583b39bSJohn Wren Kennedy 837d583b39bSJohn Wren Kennedy 838d583b39bSJohn Wren Kennedyif __name__ == '__main__': 839d583b39bSJohn Wren Kennedy main(argv[1:]) 840