19f923083SAlexander Pyhalov#!@PYTHON@ 29f923083SAlexander Pyhalov 39f923083SAlexander Pyhalov# 49f923083SAlexander Pyhalov# This file and its contents are supplied under the terms of the 59f923083SAlexander Pyhalov# Common Development and Distribution License ("CDDL"), version 1.0. 69f923083SAlexander Pyhalov# You may only use this file in accordance with the terms of version 79f923083SAlexander Pyhalov# 1.0 of the CDDL. 89f923083SAlexander Pyhalov# 99f923083SAlexander Pyhalov# A full copy of the text of the CDDL should have accompanied this 109f923083SAlexander Pyhalov# source. A copy of the CDDL is also available via the Internet at 119f923083SAlexander Pyhalov# http://www.illumos.org/license/CDDL. 129f923083SAlexander Pyhalov# 139f923083SAlexander Pyhalov 149f923083SAlexander Pyhalov# 15dfc11533SChris Williamson# Copyright (c) 2012, 2016 by Delphix. All rights reserved. 1664ee6612SChris Fraire# Copyright (c) 2017, Chris Fraire <cfraire@me.com>. 172491fc01SJohn Levon# Copyright 2019 Joyent, Inc. 189f923083SAlexander Pyhalov# 199f923083SAlexander Pyhalov 20b0858fdcSAlexander Pyhalovfrom __future__ import print_function 21b0858fdcSAlexander Pyhalovimport sys 22b0858fdcSAlexander PyhalovPY3 = sys.version_info[0] == 3 23b0858fdcSAlexander Pyhalov 24b0858fdcSAlexander Pyhalovif PY3: 25b0858fdcSAlexander Pyhalov import configparser 26b0858fdcSAlexander Pyhalovelse: 27b0858fdcSAlexander Pyhalov import ConfigParser as configparser 28b0858fdcSAlexander Pyhalov 29*cdd7a662SAlexander Pyhalovimport io 309f923083SAlexander Pyhalovimport os 319f923083SAlexander Pyhalovimport logging 32a0955b86SJohn Levonimport platform 339f923083SAlexander Pyhalovfrom logging.handlers import WatchedFileHandler 349f923083SAlexander Pyhalovfrom datetime import datetime 359f923083SAlexander Pyhalovfrom optparse import OptionParser 369f923083SAlexander Pyhalovfrom pwd import getpwnam 379f923083SAlexander Pyhalovfrom pwd import getpwuid 389f923083SAlexander Pyhalovfrom select import select 399f923083SAlexander Pyhalovfrom subprocess import PIPE 409f923083SAlexander Pyhalovfrom subprocess import Popen 419f923083SAlexander Pyhalovfrom sys import argv 42b0858fdcSAlexander Pyhalovfrom sys import exit 43b0858fdcSAlexander Pyhalovfrom sys import maxsize 449f923083SAlexander Pyhalovfrom threading import Timer 459f923083SAlexander Pyhalovfrom time import time 469f923083SAlexander Pyhalov 479f923083SAlexander PyhalovBASEDIR = '/var/tmp/test_results' 489f923083SAlexander PyhalovKILL = '/usr/bin/kill' 499f923083SAlexander PyhalovTRUE = '/usr/bin/true' 509f923083SAlexander PyhalovSUDO = '/usr/bin/sudo' 519f923083SAlexander Pyhalov 522491fc01SJohn Levonretcode = 0 532491fc01SJohn Levon 549f923083SAlexander Pyhalov# Custom class to reopen the log file in case it is forcibly closed by a test. 559f923083SAlexander Pyhalovclass WatchedFileHandlerClosed(WatchedFileHandler): 569f923083SAlexander Pyhalov """Watch files, including closed files. 579f923083SAlexander Pyhalov Similar to (and inherits from) logging.handler.WatchedFileHandler, 589f923083SAlexander Pyhalov except that IOErrors are handled by reopening the stream and retrying. 599f923083SAlexander Pyhalov This will be retried up to a configurable number of times before 609f923083SAlexander Pyhalov giving up, default 5. 619f923083SAlexander Pyhalov """ 629f923083SAlexander Pyhalov 63*cdd7a662SAlexander Pyhalov def __init__(self, filename, mode='a', encoding='utf-8', delay=0, max_tries=5): 649f923083SAlexander Pyhalov self.max_tries = max_tries 659f923083SAlexander Pyhalov self.tries = 0 669f923083SAlexander Pyhalov WatchedFileHandler.__init__(self, filename, mode, encoding, delay) 679f923083SAlexander Pyhalov 689f923083SAlexander Pyhalov def emit(self, record): 699f923083SAlexander Pyhalov while True: 709f923083SAlexander Pyhalov try: 719f923083SAlexander Pyhalov WatchedFileHandler.emit(self, record) 729f923083SAlexander Pyhalov self.tries = 0 739f923083SAlexander Pyhalov return 749f923083SAlexander Pyhalov except IOError as err: 759f923083SAlexander Pyhalov if self.tries == self.max_tries: 769f923083SAlexander Pyhalov raise 779f923083SAlexander Pyhalov self.stream.close() 789f923083SAlexander Pyhalov self.stream = self._open() 799f923083SAlexander Pyhalov self.tries += 1 809f923083SAlexander Pyhalov 819f923083SAlexander Pyhalovclass Result(object): 829f923083SAlexander Pyhalov total = 0 839f923083SAlexander Pyhalov runresults = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0} 849f923083SAlexander Pyhalov 859f923083SAlexander Pyhalov def __init__(self): 869f923083SAlexander Pyhalov self.starttime = None 879f923083SAlexander Pyhalov self.returncode = None 889f923083SAlexander Pyhalov self.runtime = '' 899f923083SAlexander Pyhalov self.stdout = [] 909f923083SAlexander Pyhalov self.stderr = [] 919f923083SAlexander Pyhalov self.result = '' 929f923083SAlexander Pyhalov 939f923083SAlexander Pyhalov def done(self, proc, killed): 949f923083SAlexander Pyhalov """ 959f923083SAlexander Pyhalov Finalize the results of this Cmd. 9696c8483aSYuri Pankov Report SKIP for return codes 3,4 (NOTINUSE, UNSUPPORTED) 9796c8483aSYuri Pankov as defined in ../stf/include/stf.shlib 989f923083SAlexander Pyhalov """ 992491fc01SJohn Levon global retcode 1002491fc01SJohn Levon 1019f923083SAlexander Pyhalov Result.total += 1 1029f923083SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 1039f923083SAlexander Pyhalov self.runtime = '%02d:%02d' % (m, s) 1049f923083SAlexander Pyhalov self.returncode = proc.returncode 1059f923083SAlexander Pyhalov if killed: 1069f923083SAlexander Pyhalov self.result = 'KILLED' 1079f923083SAlexander Pyhalov Result.runresults['KILLED'] += 1 1082491fc01SJohn Levon retcode = 2; 1099f923083SAlexander Pyhalov elif self.returncode is 0: 1109f923083SAlexander Pyhalov self.result = 'PASS' 1119f923083SAlexander Pyhalov Result.runresults['PASS'] += 1 11296c8483aSYuri Pankov elif self.returncode is 3 or self.returncode is 4: 11396c8483aSYuri Pankov self.result = 'SKIP' 11496c8483aSYuri Pankov Result.runresults['SKIP'] += 1 1159f923083SAlexander Pyhalov elif self.returncode is not 0: 1169f923083SAlexander Pyhalov self.result = 'FAIL' 1179f923083SAlexander Pyhalov Result.runresults['FAIL'] += 1 1182491fc01SJohn Levon retcode = 1; 1199f923083SAlexander Pyhalov 1209f923083SAlexander Pyhalov 1219f923083SAlexander Pyhalovclass Output(object): 1229f923083SAlexander Pyhalov """ 1239f923083SAlexander Pyhalov This class is a slightly modified version of the 'Stream' class found 1249f923083SAlexander Pyhalov here: http://goo.gl/aSGfv 1259f923083SAlexander Pyhalov """ 1269f923083SAlexander Pyhalov def __init__(self, stream): 1279f923083SAlexander Pyhalov self.stream = stream 1289f923083SAlexander Pyhalov self._buf = '' 1299f923083SAlexander Pyhalov self.lines = [] 1309f923083SAlexander Pyhalov 1319f923083SAlexander Pyhalov def fileno(self): 1329f923083SAlexander Pyhalov return self.stream.fileno() 1339f923083SAlexander Pyhalov 1349f923083SAlexander Pyhalov def read(self, drain=0): 1359f923083SAlexander Pyhalov """ 1369f923083SAlexander Pyhalov Read from the file descriptor. If 'drain' set, read until EOF. 1379f923083SAlexander Pyhalov """ 1389f923083SAlexander Pyhalov while self._read() is not None: 1399f923083SAlexander Pyhalov if not drain: 1409f923083SAlexander Pyhalov break 1419f923083SAlexander Pyhalov 1429f923083SAlexander Pyhalov def _read(self): 1439f923083SAlexander Pyhalov """ 1449f923083SAlexander Pyhalov Read up to 4k of data from this output stream. Collect the output 1459f923083SAlexander Pyhalov up to the last newline, and append it to any leftover data from a 1469f923083SAlexander Pyhalov previous call. The lines are stored as a (timestamp, data) tuple 1479f923083SAlexander Pyhalov for easy sorting/merging later. 1489f923083SAlexander Pyhalov """ 1499f923083SAlexander Pyhalov fd = self.fileno() 150*cdd7a662SAlexander Pyhalov buf = os.read(fd, 4096).decode('utf-8') 1519f923083SAlexander Pyhalov if not buf: 1529f923083SAlexander Pyhalov return None 1539f923083SAlexander Pyhalov if '\n' not in buf: 1549f923083SAlexander Pyhalov self._buf += buf 1559f923083SAlexander Pyhalov return [] 1569f923083SAlexander Pyhalov 1579f923083SAlexander Pyhalov buf = self._buf + buf 1589f923083SAlexander Pyhalov tmp, rest = buf.rsplit('\n', 1) 1599f923083SAlexander Pyhalov self._buf = rest 1609f923083SAlexander Pyhalov now = datetime.now() 1619f923083SAlexander Pyhalov rows = tmp.split('\n') 1629f923083SAlexander Pyhalov self.lines += [(now, r) for r in rows] 1639f923083SAlexander Pyhalov 1649f923083SAlexander Pyhalov 1659f923083SAlexander Pyhalovclass Cmd(object): 1669f923083SAlexander Pyhalov verified_users = [] 1679f923083SAlexander Pyhalov 1689f923083SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None): 1699f923083SAlexander Pyhalov self.pathname = pathname 1709f923083SAlexander Pyhalov self.outputdir = outputdir or 'BASEDIR' 1719f923083SAlexander Pyhalov self.timeout = timeout 1729f923083SAlexander Pyhalov self.user = user or '' 1739f923083SAlexander Pyhalov self.killed = False 1749f923083SAlexander Pyhalov self.result = Result() 1759f923083SAlexander Pyhalov 1769f923083SAlexander Pyhalov if self.timeout is None: 1779f923083SAlexander Pyhalov self.timeout = 60 1789f923083SAlexander Pyhalov 1799f923083SAlexander Pyhalov def __str__(self): 18064ee6612SChris Fraire return "Pathname: %s\nOutputdir: %s\nTimeout: %s\nUser: %s\n" % \ 1819f923083SAlexander Pyhalov (self.pathname, self.outputdir, self.timeout, self.user) 1829f923083SAlexander Pyhalov 1839f923083SAlexander Pyhalov def kill_cmd(self, proc): 1849f923083SAlexander Pyhalov """ 1859f923083SAlexander Pyhalov Kill a running command due to timeout, or ^C from the keyboard. If 1869f923083SAlexander Pyhalov sudo is required, this user was verified previously. 1879f923083SAlexander Pyhalov """ 1889f923083SAlexander Pyhalov self.killed = True 1899f923083SAlexander Pyhalov do_sudo = len(self.user) != 0 1909f923083SAlexander Pyhalov signal = '-TERM' 1919f923083SAlexander Pyhalov 1929f923083SAlexander Pyhalov cmd = [SUDO, KILL, signal, str(proc.pid)] 1939f923083SAlexander Pyhalov if not do_sudo: 1949f923083SAlexander Pyhalov del cmd[0] 1959f923083SAlexander Pyhalov 1969f923083SAlexander Pyhalov try: 1979f923083SAlexander Pyhalov kp = Popen(cmd) 1989f923083SAlexander Pyhalov kp.wait() 1999f923083SAlexander Pyhalov except: 2009f923083SAlexander Pyhalov pass 2019f923083SAlexander Pyhalov 2029f923083SAlexander Pyhalov def update_cmd_privs(self, cmd, user): 2039f923083SAlexander Pyhalov """ 2049f923083SAlexander Pyhalov If a user has been specified to run this Cmd and we're not already 2059f923083SAlexander Pyhalov running as that user, prepend the appropriate sudo command to run 2069f923083SAlexander Pyhalov as that user. 2079f923083SAlexander Pyhalov """ 2089f923083SAlexander Pyhalov me = getpwuid(os.getuid()) 2099f923083SAlexander Pyhalov 2109f923083SAlexander Pyhalov if not user or user is me: 2119f923083SAlexander Pyhalov return cmd 2129f923083SAlexander Pyhalov 2139f923083SAlexander Pyhalov ret = '%s -E -u %s %s' % (SUDO, user, cmd) 2149f923083SAlexander Pyhalov return ret.split(' ') 2159f923083SAlexander Pyhalov 2169f923083SAlexander Pyhalov def collect_output(self, proc): 2179f923083SAlexander Pyhalov """ 2189f923083SAlexander Pyhalov Read from stdout/stderr as data becomes available, until the 2199f923083SAlexander Pyhalov process is no longer running. Return the lines from the stdout and 2209f923083SAlexander Pyhalov stderr Output objects. 2219f923083SAlexander Pyhalov """ 2229f923083SAlexander Pyhalov out = Output(proc.stdout) 2239f923083SAlexander Pyhalov err = Output(proc.stderr) 2249f923083SAlexander Pyhalov res = [] 2259f923083SAlexander Pyhalov while proc.returncode is None: 2269f923083SAlexander Pyhalov proc.poll() 2279f923083SAlexander Pyhalov res = select([out, err], [], [], .1) 2289f923083SAlexander Pyhalov for fd in res[0]: 2299f923083SAlexander Pyhalov fd.read() 2309f923083SAlexander Pyhalov for fd in res[0]: 2319f923083SAlexander Pyhalov fd.read(drain=1) 2329f923083SAlexander Pyhalov 2339f923083SAlexander Pyhalov return out.lines, err.lines 2349f923083SAlexander Pyhalov 2359f923083SAlexander Pyhalov def run(self, options): 2369f923083SAlexander Pyhalov """ 2379f923083SAlexander Pyhalov This is the main function that runs each individual test. 2389f923083SAlexander Pyhalov Determine whether or not the command requires sudo, and modify it 2399f923083SAlexander Pyhalov if needed. Run the command, and update the result object. 2409f923083SAlexander Pyhalov """ 2419f923083SAlexander Pyhalov if options.dryrun is True: 242b0858fdcSAlexander Pyhalov print(self) 2439f923083SAlexander Pyhalov return 2449f923083SAlexander Pyhalov 2459f923083SAlexander Pyhalov privcmd = self.update_cmd_privs(self.pathname, self.user) 2469f923083SAlexander Pyhalov try: 2479f923083SAlexander Pyhalov old = os.umask(0) 2489f923083SAlexander Pyhalov if not os.path.isdir(self.outputdir): 249b0858fdcSAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 2509f923083SAlexander Pyhalov os.umask(old) 251b0858fdcSAlexander Pyhalov except OSError as e: 2529f923083SAlexander Pyhalov fail('%s' % e) 2539f923083SAlexander Pyhalov 2549f923083SAlexander Pyhalov try: 2559f923083SAlexander Pyhalov self.result.starttime = time() 256b0858fdcSAlexander Pyhalov proc = Popen(privcmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, 257b0858fdcSAlexander Pyhalov universal_newlines=True) 2585cabbc6bSPrashanth Sreenivasa proc.stdin.close() 2599f923083SAlexander Pyhalov 2609f923083SAlexander Pyhalov # Allow a special timeout value of 0 to mean infinity 2619f923083SAlexander Pyhalov if int(self.timeout) == 0: 262b0858fdcSAlexander Pyhalov self.timeout = maxsize 2639f923083SAlexander Pyhalov t = Timer(int(self.timeout), self.kill_cmd, [proc]) 2649f923083SAlexander Pyhalov t.start() 2659f923083SAlexander Pyhalov self.result.stdout, self.result.stderr = self.collect_output(proc) 2669f923083SAlexander Pyhalov except KeyboardInterrupt: 2679f923083SAlexander Pyhalov self.kill_cmd(proc) 2689f923083SAlexander Pyhalov fail('\nRun terminated at user request.') 2699f923083SAlexander Pyhalov finally: 2709f923083SAlexander Pyhalov t.cancel() 2719f923083SAlexander Pyhalov 2729f923083SAlexander Pyhalov self.result.done(proc, self.killed) 2739f923083SAlexander Pyhalov 2749f923083SAlexander Pyhalov def skip(self): 2759f923083SAlexander Pyhalov """ 2769f923083SAlexander Pyhalov Initialize enough of the test result that we can log a skipped 2779f923083SAlexander Pyhalov command. 2789f923083SAlexander Pyhalov """ 2799f923083SAlexander Pyhalov Result.total += 1 2809f923083SAlexander Pyhalov Result.runresults['SKIP'] += 1 2819f923083SAlexander Pyhalov self.result.stdout = self.result.stderr = [] 2829f923083SAlexander Pyhalov self.result.starttime = time() 2839f923083SAlexander Pyhalov m, s = divmod(time() - self.result.starttime, 60) 2849f923083SAlexander Pyhalov self.result.runtime = '%02d:%02d' % (m, s) 2859f923083SAlexander Pyhalov self.result.result = 'SKIP' 2869f923083SAlexander Pyhalov 2879f923083SAlexander Pyhalov def log(self, logger, options): 2889f923083SAlexander Pyhalov """ 2899f923083SAlexander Pyhalov This function is responsible for writing all output. This includes 2909f923083SAlexander Pyhalov the console output, the logfile of all results (with timestamped 2919f923083SAlexander Pyhalov merged stdout and stderr), and for each test, the unmodified 2929f923083SAlexander Pyhalov stdout/stderr/merged in it's own file. 2939f923083SAlexander Pyhalov """ 2949f923083SAlexander Pyhalov if logger is None: 2959f923083SAlexander Pyhalov return 2969f923083SAlexander Pyhalov 2979f923083SAlexander Pyhalov logname = getpwuid(os.getuid()).pw_name 2989f923083SAlexander Pyhalov user = ' (run as %s)' % (self.user if len(self.user) else logname) 2999f923083SAlexander Pyhalov msga = 'Test: %s%s ' % (self.pathname, user) 3009f923083SAlexander Pyhalov msgb = '[%s] [%s]' % (self.result.runtime, self.result.result) 3019f923083SAlexander Pyhalov pad = ' ' * (80 - (len(msga) + len(msgb))) 3029f923083SAlexander Pyhalov 3039f923083SAlexander Pyhalov # If -q is specified, only print a line for tests that didn't pass. 3049f923083SAlexander Pyhalov # This means passing tests need to be logged as DEBUG, or the one 3059f923083SAlexander Pyhalov # line summary will only be printed in the logfile for failures. 3069f923083SAlexander Pyhalov if not options.quiet: 3079f923083SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 3089f923083SAlexander Pyhalov elif self.result.result is not 'PASS': 3099f923083SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 3109f923083SAlexander Pyhalov else: 3119f923083SAlexander Pyhalov logger.debug('%s%s%s' % (msga, pad, msgb)) 3129f923083SAlexander Pyhalov 3139f923083SAlexander Pyhalov lines = sorted(self.result.stdout + self.result.stderr, 314b0858fdcSAlexander Pyhalov key=lambda x: x[0]) 3159f923083SAlexander Pyhalov 3169f923083SAlexander Pyhalov for dt, line in lines: 3179f923083SAlexander Pyhalov logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line)) 3189f923083SAlexander Pyhalov 3199f923083SAlexander Pyhalov if len(self.result.stdout): 320*cdd7a662SAlexander Pyhalov with io.open(os.path.join(self.outputdir, 'stdout'), 321*cdd7a662SAlexander Pyhalov encoding='utf-8', 322*cdd7a662SAlexander Pyhalov errors='surrogateescape', 323*cdd7a662SAlexander Pyhalov mode='w') as out: 3249f923083SAlexander Pyhalov for _, line in self.result.stdout: 325b0858fdcSAlexander Pyhalov out.write('%s\n' % line) 3269f923083SAlexander Pyhalov if len(self.result.stderr): 327*cdd7a662SAlexander Pyhalov with io.open(os.path.join(self.outputdir, 'stderr'), 328*cdd7a662SAlexander Pyhalov encoding='utf-8', 329*cdd7a662SAlexander Pyhalov errors='surrogateescape', 330*cdd7a662SAlexander Pyhalov mode='w') as err: 3319f923083SAlexander Pyhalov for _, line in self.result.stderr: 332b0858fdcSAlexander Pyhalov err.write('%s\n' % line) 3339f923083SAlexander Pyhalov if len(self.result.stdout) and len(self.result.stderr): 334*cdd7a662SAlexander Pyhalov with io.open(os.path.join(self.outputdir, 'merged'), 335*cdd7a662SAlexander Pyhalov encoding='utf-8', 336*cdd7a662SAlexander Pyhalov errors='surrogateescape', 337*cdd7a662SAlexander Pyhalov mode='w') as merged: 3389f923083SAlexander Pyhalov for _, line in lines: 339b0858fdcSAlexander Pyhalov merged.write('%s\n' % line) 3409f923083SAlexander Pyhalov 3419f923083SAlexander Pyhalov 3429f923083SAlexander Pyhalovclass Test(Cmd): 3439f923083SAlexander Pyhalov props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post', 3449f923083SAlexander Pyhalov 'post_user'] 3459f923083SAlexander Pyhalov 3469f923083SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 3479f923083SAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None): 3489f923083SAlexander Pyhalov super(Test, self).__init__(pathname, outputdir, timeout, user) 3499f923083SAlexander Pyhalov self.pre = pre or '' 3509f923083SAlexander Pyhalov self.pre_user = pre_user or '' 3519f923083SAlexander Pyhalov self.post = post or '' 3529f923083SAlexander Pyhalov self.post_user = post_user or '' 3539f923083SAlexander Pyhalov 3549f923083SAlexander Pyhalov def __str__(self): 3559f923083SAlexander Pyhalov post_user = pre_user = '' 3569f923083SAlexander Pyhalov if len(self.pre_user): 3579f923083SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 3589f923083SAlexander Pyhalov if len(self.post_user): 3599f923083SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 3609f923083SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTimeout: %d\nPre: %s%s\nPost: " \ 3619f923083SAlexander Pyhalov "%s%s\nUser: %s\n" % \ 3629f923083SAlexander Pyhalov (self.pathname, self.outputdir, self.timeout, self.pre, 3639f923083SAlexander Pyhalov pre_user, self.post, post_user, self.user) 3649f923083SAlexander Pyhalov 3659f923083SAlexander Pyhalov def verify(self, logger): 3669f923083SAlexander Pyhalov """ 3679f923083SAlexander Pyhalov Check the pre/post scripts, user and Test. Omit the Test from this 3689f923083SAlexander Pyhalov run if there are any problems. 3699f923083SAlexander Pyhalov """ 3709f923083SAlexander Pyhalov files = [self.pre, self.pathname, self.post] 3719f923083SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 3729f923083SAlexander Pyhalov 3739f923083SAlexander Pyhalov for f in [f for f in files if len(f)]: 3749f923083SAlexander Pyhalov if not verify_file(f): 3759f923083SAlexander Pyhalov logger.info("Warning: Test '%s' not added to this run because" 3769f923083SAlexander Pyhalov " it failed verification." % f) 3779f923083SAlexander Pyhalov return False 3789f923083SAlexander Pyhalov 3799f923083SAlexander Pyhalov for user in [user for user in users if len(user)]: 3809f923083SAlexander Pyhalov if not verify_user(user, logger): 3819f923083SAlexander Pyhalov logger.info("Not adding Test '%s' to this run." % 3829f923083SAlexander Pyhalov self.pathname) 3839f923083SAlexander Pyhalov return False 3849f923083SAlexander Pyhalov 3859f923083SAlexander Pyhalov return True 3869f923083SAlexander Pyhalov 3879f923083SAlexander Pyhalov def run(self, logger, options): 3889f923083SAlexander Pyhalov """ 3899f923083SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 3909f923083SAlexander Pyhalov doesn't pass, skip this Test. Run the post script regardless. 3919f923083SAlexander Pyhalov """ 3929f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.pre)) 3939f923083SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=odir, timeout=self.timeout, 3949f923083SAlexander Pyhalov user=self.pre_user) 3959f923083SAlexander Pyhalov test = Cmd(self.pathname, outputdir=self.outputdir, 3969f923083SAlexander Pyhalov timeout=self.timeout, user=self.user) 3979f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.post)) 3989f923083SAlexander Pyhalov posttest = Cmd(self.post, outputdir=odir, timeout=self.timeout, 3999f923083SAlexander Pyhalov user=self.post_user) 4009f923083SAlexander Pyhalov 4019f923083SAlexander Pyhalov cont = True 4029f923083SAlexander Pyhalov if len(pretest.pathname): 4039f923083SAlexander Pyhalov pretest.run(options) 4049f923083SAlexander Pyhalov cont = pretest.result.result is 'PASS' 4059f923083SAlexander Pyhalov pretest.log(logger, options) 4069f923083SAlexander Pyhalov 4079f923083SAlexander Pyhalov if cont: 4089f923083SAlexander Pyhalov test.run(options) 4099f923083SAlexander Pyhalov else: 4109f923083SAlexander Pyhalov test.skip() 4119f923083SAlexander Pyhalov 4129f923083SAlexander Pyhalov test.log(logger, options) 4139f923083SAlexander Pyhalov 4149f923083SAlexander Pyhalov if len(posttest.pathname): 4159f923083SAlexander Pyhalov posttest.run(options) 4169f923083SAlexander Pyhalov posttest.log(logger, options) 4179f923083SAlexander Pyhalov 4189f923083SAlexander Pyhalov 4199f923083SAlexander Pyhalovclass TestGroup(Test): 4209f923083SAlexander Pyhalov props = Test.props + ['tests'] 4219f923083SAlexander Pyhalov 4229f923083SAlexander Pyhalov def __init__(self, pathname, outputdir=None, timeout=None, user=None, 4239f923083SAlexander Pyhalov pre=None, pre_user=None, post=None, post_user=None, 4249f923083SAlexander Pyhalov tests=None): 4259f923083SAlexander Pyhalov super(TestGroup, self).__init__(pathname, outputdir, timeout, user, 4269f923083SAlexander Pyhalov pre, pre_user, post, post_user) 4279f923083SAlexander Pyhalov self.tests = tests or [] 4289f923083SAlexander Pyhalov 4299f923083SAlexander Pyhalov def __str__(self): 4309f923083SAlexander Pyhalov post_user = pre_user = '' 4319f923083SAlexander Pyhalov if len(self.pre_user): 4329f923083SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 4339f923083SAlexander Pyhalov if len(self.post_user): 4349f923083SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 4359f923083SAlexander Pyhalov return "Pathname: %s\nOutputdir: %s\nTests: %s\nTimeout: %d\n" \ 4369f923083SAlexander Pyhalov "Pre: %s%s\nPost: %s%s\nUser: %s\n" % \ 4379f923083SAlexander Pyhalov (self.pathname, self.outputdir, self.tests, self.timeout, 4389f923083SAlexander Pyhalov self.pre, pre_user, self.post, post_user, self.user) 4399f923083SAlexander Pyhalov 4409f923083SAlexander Pyhalov def verify(self, logger): 4419f923083SAlexander Pyhalov """ 4429f923083SAlexander Pyhalov Check the pre/post scripts, user and tests in this TestGroup. Omit 4439f923083SAlexander Pyhalov the TestGroup entirely, or simply delete the relevant tests in the 4449f923083SAlexander Pyhalov group, if that's all that's required. 4459f923083SAlexander Pyhalov """ 4469f923083SAlexander Pyhalov # If the pre or post scripts are relative pathnames, convert to 4479f923083SAlexander Pyhalov # absolute, so they stand a chance of passing verification. 4489f923083SAlexander Pyhalov if len(self.pre) and not os.path.isabs(self.pre): 4499f923083SAlexander Pyhalov self.pre = os.path.join(self.pathname, self.pre) 4509f923083SAlexander Pyhalov if len(self.post) and not os.path.isabs(self.post): 4519f923083SAlexander Pyhalov self.post = os.path.join(self.pathname, self.post) 4529f923083SAlexander Pyhalov 4539f923083SAlexander Pyhalov auxfiles = [self.pre, self.post] 4549f923083SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 4559f923083SAlexander Pyhalov 4569f923083SAlexander Pyhalov for f in [f for f in auxfiles if len(f)]: 4579f923083SAlexander Pyhalov if self.pathname != os.path.dirname(f): 4589f923083SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 4599f923083SAlexander Pyhalov "Auxiliary script '%s' exists in a different " 4609f923083SAlexander Pyhalov "directory." % (self.pathname, f)) 4619f923083SAlexander Pyhalov return False 4629f923083SAlexander Pyhalov 4639f923083SAlexander Pyhalov if not verify_file(f): 4649f923083SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 4659f923083SAlexander Pyhalov "Auxiliary script '%s' failed verification." % 4669f923083SAlexander Pyhalov (self.pathname, f)) 4679f923083SAlexander Pyhalov return False 4689f923083SAlexander Pyhalov 4699f923083SAlexander Pyhalov for user in [user for user in users if len(user)]: 4709f923083SAlexander Pyhalov if not verify_user(user, logger): 4719f923083SAlexander Pyhalov logger.info("Not adding TestGroup '%s' to this run." % 4729f923083SAlexander Pyhalov self.pathname) 4739f923083SAlexander Pyhalov return False 4749f923083SAlexander Pyhalov 4759f923083SAlexander Pyhalov # If one of the tests is invalid, delete it, log it, and drive on. 476dfc11533SChris Williamson self.tests[:] = [f for f in self.tests if 477dfc11533SChris Williamson verify_file(os.path.join(self.pathname, f))] 4789f923083SAlexander Pyhalov 4799f923083SAlexander Pyhalov return len(self.tests) is not 0 4809f923083SAlexander Pyhalov 4819f923083SAlexander Pyhalov def run(self, logger, options): 4829f923083SAlexander Pyhalov """ 4839f923083SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 4849f923083SAlexander Pyhalov doesn't pass, skip all the tests in this TestGroup. Run the post 4859f923083SAlexander Pyhalov script regardless. 4869f923083SAlexander Pyhalov """ 4879f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.pre)) 4889f923083SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=odir, timeout=self.timeout, 4899f923083SAlexander Pyhalov user=self.pre_user) 4909f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.post)) 4919f923083SAlexander Pyhalov posttest = Cmd(self.post, outputdir=odir, timeout=self.timeout, 4929f923083SAlexander Pyhalov user=self.post_user) 4939f923083SAlexander Pyhalov 4949f923083SAlexander Pyhalov cont = True 4959f923083SAlexander Pyhalov if len(pretest.pathname): 4969f923083SAlexander Pyhalov pretest.run(options) 4979f923083SAlexander Pyhalov cont = pretest.result.result is 'PASS' 4989f923083SAlexander Pyhalov pretest.log(logger, options) 4999f923083SAlexander Pyhalov 5009f923083SAlexander Pyhalov for fname in self.tests: 5019f923083SAlexander Pyhalov test = Cmd(os.path.join(self.pathname, fname), 5029f923083SAlexander Pyhalov outputdir=os.path.join(self.outputdir, fname), 5039f923083SAlexander Pyhalov timeout=self.timeout, user=self.user) 5049f923083SAlexander Pyhalov if cont: 5059f923083SAlexander Pyhalov test.run(options) 5069f923083SAlexander Pyhalov else: 5079f923083SAlexander Pyhalov test.skip() 5089f923083SAlexander Pyhalov 5099f923083SAlexander Pyhalov test.log(logger, options) 5109f923083SAlexander Pyhalov 5119f923083SAlexander Pyhalov if len(posttest.pathname): 5129f923083SAlexander Pyhalov posttest.run(options) 5139f923083SAlexander Pyhalov posttest.log(logger, options) 5149f923083SAlexander Pyhalov 5159f923083SAlexander Pyhalov 5169f923083SAlexander Pyhalovclass TestRun(object): 5179f923083SAlexander Pyhalov props = ['quiet', 'outputdir'] 5189f923083SAlexander Pyhalov 5199f923083SAlexander Pyhalov def __init__(self, options): 5209f923083SAlexander Pyhalov self.tests = {} 5219f923083SAlexander Pyhalov self.testgroups = {} 5229f923083SAlexander Pyhalov self.starttime = time() 5239f923083SAlexander Pyhalov self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') 5249f923083SAlexander Pyhalov self.outputdir = os.path.join(options.outputdir, self.timestamp) 5259f923083SAlexander Pyhalov self.logger = self.setup_logging(options) 5269f923083SAlexander Pyhalov self.defaults = [ 5279f923083SAlexander Pyhalov ('outputdir', BASEDIR), 5289f923083SAlexander Pyhalov ('quiet', False), 5299f923083SAlexander Pyhalov ('timeout', 60), 5309f923083SAlexander Pyhalov ('user', ''), 5319f923083SAlexander Pyhalov ('pre', ''), 5329f923083SAlexander Pyhalov ('pre_user', ''), 5339f923083SAlexander Pyhalov ('post', ''), 5349f923083SAlexander Pyhalov ('post_user', '') 5359f923083SAlexander Pyhalov ] 5369f923083SAlexander Pyhalov 5379f923083SAlexander Pyhalov def __str__(self): 5389f923083SAlexander Pyhalov s = 'TestRun:\n outputdir: %s\n' % self.outputdir 5399f923083SAlexander Pyhalov s += 'TESTS:\n' 5409f923083SAlexander Pyhalov for key in sorted(self.tests.keys()): 5419f923083SAlexander Pyhalov s += '%s%s' % (self.tests[key].__str__(), '\n') 5429f923083SAlexander Pyhalov s += 'TESTGROUPS:\n' 5439f923083SAlexander Pyhalov for key in sorted(self.testgroups.keys()): 5449f923083SAlexander Pyhalov s += '%s%s' % (self.testgroups[key].__str__(), '\n') 5459f923083SAlexander Pyhalov return s 5469f923083SAlexander Pyhalov 5479f923083SAlexander Pyhalov def addtest(self, pathname, options): 5489f923083SAlexander Pyhalov """ 5499f923083SAlexander Pyhalov Create a new Test, and apply any properties that were passed in 5509f923083SAlexander Pyhalov from the command line. If it passes verification, add it to the 5519f923083SAlexander Pyhalov TestRun. 5529f923083SAlexander Pyhalov """ 5539f923083SAlexander Pyhalov test = Test(pathname) 5549f923083SAlexander Pyhalov for prop in Test.props: 5559f923083SAlexander Pyhalov setattr(test, prop, getattr(options, prop)) 5569f923083SAlexander Pyhalov 5579f923083SAlexander Pyhalov if test.verify(self.logger): 5589f923083SAlexander Pyhalov self.tests[pathname] = test 5599f923083SAlexander Pyhalov 5609f923083SAlexander Pyhalov def addtestgroup(self, dirname, filenames, options): 5619f923083SAlexander Pyhalov """ 5629f923083SAlexander Pyhalov Create a new TestGroup, and apply any properties that were passed 5639f923083SAlexander Pyhalov in from the command line. If it passes verification, add it to the 5649f923083SAlexander Pyhalov TestRun. 5659f923083SAlexander Pyhalov """ 5669f923083SAlexander Pyhalov if dirname not in self.testgroups: 5679f923083SAlexander Pyhalov testgroup = TestGroup(dirname) 5689f923083SAlexander Pyhalov for prop in Test.props: 5699f923083SAlexander Pyhalov setattr(testgroup, prop, getattr(options, prop)) 5709f923083SAlexander Pyhalov 5719f923083SAlexander Pyhalov # Prevent pre/post scripts from running as regular tests 5729f923083SAlexander Pyhalov for f in [testgroup.pre, testgroup.post]: 5739f923083SAlexander Pyhalov if f in filenames: 5749f923083SAlexander Pyhalov del filenames[filenames.index(f)] 5759f923083SAlexander Pyhalov 5769f923083SAlexander Pyhalov self.testgroups[dirname] = testgroup 5779f923083SAlexander Pyhalov self.testgroups[dirname].tests = sorted(filenames) 5789f923083SAlexander Pyhalov 5799f923083SAlexander Pyhalov testgroup.verify(self.logger) 5809f923083SAlexander Pyhalov 5819f923083SAlexander Pyhalov def read(self, logger, options): 5829f923083SAlexander Pyhalov """ 5839f923083SAlexander Pyhalov Read in the specified runfile, and apply the TestRun properties 5849f923083SAlexander Pyhalov listed in the 'DEFAULT' section to our TestRun. Then read each 5859f923083SAlexander Pyhalov section, and apply the appropriate properties to the Test or 5869f923083SAlexander Pyhalov TestGroup. Properties from individual sections override those set 5879f923083SAlexander Pyhalov in the 'DEFAULT' section. If the Test or TestGroup passes 5889f923083SAlexander Pyhalov verification, add it to the TestRun. 5899f923083SAlexander Pyhalov """ 590b0858fdcSAlexander Pyhalov config = configparser.RawConfigParser() 5919f923083SAlexander Pyhalov if not len(config.read(options.runfile)): 5929f923083SAlexander Pyhalov fail("Coulnd't read config file %s" % options.runfile) 5939f923083SAlexander Pyhalov 5949f923083SAlexander Pyhalov for opt in TestRun.props: 5959f923083SAlexander Pyhalov if config.has_option('DEFAULT', opt): 5969f923083SAlexander Pyhalov setattr(self, opt, config.get('DEFAULT', opt)) 5979f923083SAlexander Pyhalov self.outputdir = os.path.join(self.outputdir, self.timestamp) 5989f923083SAlexander Pyhalov 5999f923083SAlexander Pyhalov for section in config.sections(): 600a0955b86SJohn Levon if ('arch' in config.options(section) and 601a0955b86SJohn Levon platform.machine() != config.get(section, 'arch')): 602a0955b86SJohn Levon continue 603a0955b86SJohn Levon 6049f923083SAlexander Pyhalov if 'tests' in config.options(section): 6059f923083SAlexander Pyhalov testgroup = TestGroup(section) 6069f923083SAlexander Pyhalov for prop in TestGroup.props: 6079f923083SAlexander Pyhalov for sect in ['DEFAULT', section]: 6089f923083SAlexander Pyhalov if config.has_option(sect, prop): 6099f923083SAlexander Pyhalov setattr(testgroup, prop, config.get(sect, prop)) 6109f923083SAlexander Pyhalov 6119f923083SAlexander Pyhalov # Repopulate tests using eval to convert the string to a list 6129f923083SAlexander Pyhalov testgroup.tests = eval(config.get(section, 'tests')) 6139f923083SAlexander Pyhalov 6149f923083SAlexander Pyhalov if testgroup.verify(logger): 6159f923083SAlexander Pyhalov self.testgroups[section] = testgroup 616dfc11533SChris Williamson 617dfc11533SChris Williamson elif 'autotests' in config.options(section): 618dfc11533SChris Williamson testgroup = TestGroup(section) 619dfc11533SChris Williamson for prop in TestGroup.props: 620dfc11533SChris Williamson for sect in ['DEFAULT', section]: 621dfc11533SChris Williamson if config.has_option(sect, prop): 622dfc11533SChris Williamson setattr(testgroup, prop, config.get(sect, prop)) 623dfc11533SChris Williamson 624dfc11533SChris Williamson filenames = os.listdir(section) 625dfc11533SChris Williamson # only files starting with "tst." are considered tests 626dfc11533SChris Williamson filenames = [f for f in filenames if f.startswith("tst.")] 627dfc11533SChris Williamson testgroup.tests = sorted(filenames) 628dfc11533SChris Williamson 629dfc11533SChris Williamson if testgroup.verify(logger): 630dfc11533SChris Williamson self.testgroups[section] = testgroup 631dfc11533SChris Williamson 6329f923083SAlexander Pyhalov else: 6339f923083SAlexander Pyhalov test = Test(section) 6349f923083SAlexander Pyhalov for prop in Test.props: 6359f923083SAlexander Pyhalov for sect in ['DEFAULT', section]: 6369f923083SAlexander Pyhalov if config.has_option(sect, prop): 6379f923083SAlexander Pyhalov setattr(test, prop, config.get(sect, prop)) 6389f923083SAlexander Pyhalov 6399f923083SAlexander Pyhalov if test.verify(logger): 6409f923083SAlexander Pyhalov self.tests[section] = test 6419f923083SAlexander Pyhalov 6429f923083SAlexander Pyhalov def write(self, options): 6439f923083SAlexander Pyhalov """ 6449f923083SAlexander Pyhalov Create a configuration file for editing and later use. The 6459f923083SAlexander Pyhalov 'DEFAULT' section of the config file is created from the 6469f923083SAlexander Pyhalov properties that were specified on the command line. Tests are 6479f923083SAlexander Pyhalov simply added as sections that inherit everything from the 6489f923083SAlexander Pyhalov 'DEFAULT' section. TestGroups are the same, except they get an 6499f923083SAlexander Pyhalov option including all the tests to run in that directory. 6509f923083SAlexander Pyhalov """ 6519f923083SAlexander Pyhalov 6529f923083SAlexander Pyhalov defaults = dict([(prop, getattr(options, prop)) for prop, _ in 6539f923083SAlexander Pyhalov self.defaults]) 654b0858fdcSAlexander Pyhalov config = configparser.RawConfigParser(defaults) 6559f923083SAlexander Pyhalov 6569f923083SAlexander Pyhalov for test in sorted(self.tests.keys()): 6579f923083SAlexander Pyhalov config.add_section(test) 6589f923083SAlexander Pyhalov 6599f923083SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 6609f923083SAlexander Pyhalov config.add_section(testgroup) 6619f923083SAlexander Pyhalov config.set(testgroup, 'tests', self.testgroups[testgroup].tests) 6629f923083SAlexander Pyhalov 6639f923083SAlexander Pyhalov try: 6649f923083SAlexander Pyhalov with open(options.template, 'w') as f: 6659f923083SAlexander Pyhalov return config.write(f) 6669f923083SAlexander Pyhalov except IOError: 6679f923083SAlexander Pyhalov fail('Could not open \'%s\' for writing.' % options.template) 6689f923083SAlexander Pyhalov 6699f923083SAlexander Pyhalov def complete_outputdirs(self): 6709f923083SAlexander Pyhalov """ 6719f923083SAlexander Pyhalov Collect all the pathnames for Tests, and TestGroups. Work 6729f923083SAlexander Pyhalov backwards one pathname component at a time, to create a unique 6739f923083SAlexander Pyhalov directory name in which to deposit test output. Tests will be able 6749f923083SAlexander Pyhalov to write output files directly in the newly modified outputdir. 6759f923083SAlexander Pyhalov TestGroups will be able to create one subdirectory per test in the 6769f923083SAlexander Pyhalov outputdir, and are guaranteed uniqueness because a group can only 6779f923083SAlexander Pyhalov contain files in one directory. Pre and post tests will create a 6789f923083SAlexander Pyhalov directory rooted at the outputdir of the Test or TestGroup in 6799f923083SAlexander Pyhalov question for their output. 6809f923083SAlexander Pyhalov """ 6819f923083SAlexander Pyhalov done = False 6829f923083SAlexander Pyhalov components = 0 683b0858fdcSAlexander Pyhalov tmp_dict = dict(list(self.tests.items()) + list(self.testgroups.items())) 6849f923083SAlexander Pyhalov total = len(tmp_dict) 6859f923083SAlexander Pyhalov base = self.outputdir 6869f923083SAlexander Pyhalov 6879f923083SAlexander Pyhalov while not done: 6889f923083SAlexander Pyhalov l = [] 6899f923083SAlexander Pyhalov components -= 1 690b0858fdcSAlexander Pyhalov for testfile in list(tmp_dict.keys()): 6919f923083SAlexander Pyhalov uniq = '/'.join(testfile.split('/')[components:]).lstrip('/') 6929f923083SAlexander Pyhalov if uniq not in l: 6939f923083SAlexander Pyhalov l.append(uniq) 6949f923083SAlexander Pyhalov tmp_dict[testfile].outputdir = os.path.join(base, uniq) 6959f923083SAlexander Pyhalov else: 6969f923083SAlexander Pyhalov break 6979f923083SAlexander Pyhalov done = total == len(l) 6989f923083SAlexander Pyhalov 6999f923083SAlexander Pyhalov def setup_logging(self, options): 7009f923083SAlexander Pyhalov """ 7019f923083SAlexander Pyhalov Two loggers are set up here. The first is for the logfile which 7029f923083SAlexander Pyhalov will contain one line summarizing the test, including the test 7039f923083SAlexander Pyhalov name, result, and running time. This logger will also capture the 7049f923083SAlexander Pyhalov timestamped combined stdout and stderr of each run. The second 7059f923083SAlexander Pyhalov logger is optional console output, which will contain only the one 7069f923083SAlexander Pyhalov line summary. The loggers are initialized at two different levels 7079f923083SAlexander Pyhalov to facilitate segregating the output. 7089f923083SAlexander Pyhalov """ 7099f923083SAlexander Pyhalov if options.dryrun is True: 7109f923083SAlexander Pyhalov return 7119f923083SAlexander Pyhalov 7129f923083SAlexander Pyhalov testlogger = logging.getLogger(__name__) 7139f923083SAlexander Pyhalov testlogger.setLevel(logging.DEBUG) 7149f923083SAlexander Pyhalov 7159f923083SAlexander Pyhalov if options.cmd is not 'wrconfig': 7169f923083SAlexander Pyhalov try: 7179f923083SAlexander Pyhalov old = os.umask(0) 718b0858fdcSAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 7199f923083SAlexander Pyhalov os.umask(old) 720b0858fdcSAlexander Pyhalov except OSError as e: 7219f923083SAlexander Pyhalov fail('%s' % e) 7229f923083SAlexander Pyhalov filename = os.path.join(self.outputdir, 'log') 7239f923083SAlexander Pyhalov 7249f923083SAlexander Pyhalov logfile = WatchedFileHandlerClosed(filename) 7259f923083SAlexander Pyhalov logfile.setLevel(logging.DEBUG) 7269f923083SAlexander Pyhalov logfilefmt = logging.Formatter('%(message)s') 7279f923083SAlexander Pyhalov logfile.setFormatter(logfilefmt) 7289f923083SAlexander Pyhalov testlogger.addHandler(logfile) 7299f923083SAlexander Pyhalov 7309f923083SAlexander Pyhalov cons = logging.StreamHandler() 7319f923083SAlexander Pyhalov cons.setLevel(logging.INFO) 7329f923083SAlexander Pyhalov consfmt = logging.Formatter('%(message)s') 7339f923083SAlexander Pyhalov cons.setFormatter(consfmt) 7349f923083SAlexander Pyhalov testlogger.addHandler(cons) 7359f923083SAlexander Pyhalov 7369f923083SAlexander Pyhalov return testlogger 7379f923083SAlexander Pyhalov 7389f923083SAlexander Pyhalov def run(self, options): 7399f923083SAlexander Pyhalov """ 7409f923083SAlexander Pyhalov Walk through all the Tests and TestGroups, calling run(). 7419f923083SAlexander Pyhalov """ 74264ee6612SChris Fraire if not options.dryrun: 7439f923083SAlexander Pyhalov try: 7449f923083SAlexander Pyhalov os.chdir(self.outputdir) 7459f923083SAlexander Pyhalov except OSError: 7469f923083SAlexander Pyhalov fail('Could not change to directory %s' % self.outputdir) 7479f923083SAlexander Pyhalov for test in sorted(self.tests.keys()): 7489f923083SAlexander Pyhalov self.tests[test].run(self.logger, options) 7499f923083SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 7509f923083SAlexander Pyhalov self.testgroups[testgroup].run(self.logger, options) 7519f923083SAlexander Pyhalov 7529f923083SAlexander Pyhalov def summary(self): 7539f923083SAlexander Pyhalov if Result.total is 0: 7549f923083SAlexander Pyhalov return 7559f923083SAlexander Pyhalov 756b0858fdcSAlexander Pyhalov print('\nResults Summary') 757b0858fdcSAlexander Pyhalov for key in list(Result.runresults.keys()): 7589f923083SAlexander Pyhalov if Result.runresults[key] is not 0: 759b0858fdcSAlexander Pyhalov print('%s\t% 4d' % (key, Result.runresults[key])) 7609f923083SAlexander Pyhalov 7619f923083SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 7629f923083SAlexander Pyhalov h, m = divmod(m, 60) 763b0858fdcSAlexander Pyhalov print('\nRunning Time:\t%02d:%02d:%02d' % (h, m, s)) 764b0858fdcSAlexander Pyhalov print('Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) / 765b0858fdcSAlexander Pyhalov float(Result.total)) * 100)) 766b0858fdcSAlexander Pyhalov print('Log directory:\t%s' % self.outputdir) 7679f923083SAlexander Pyhalov 7689f923083SAlexander Pyhalov 7699f923083SAlexander Pyhalovdef verify_file(pathname): 7709f923083SAlexander Pyhalov """ 7719f923083SAlexander Pyhalov Verify that the supplied pathname is an executable regular file. 7729f923083SAlexander Pyhalov """ 7739f923083SAlexander Pyhalov if os.path.isdir(pathname) or os.path.islink(pathname): 7749f923083SAlexander Pyhalov return False 7759f923083SAlexander Pyhalov 7769f923083SAlexander Pyhalov if os.path.isfile(pathname) and os.access(pathname, os.X_OK): 7779f923083SAlexander Pyhalov return True 7789f923083SAlexander Pyhalov 7799f923083SAlexander Pyhalov return False 7809f923083SAlexander Pyhalov 7819f923083SAlexander Pyhalov 7829f923083SAlexander Pyhalovdef verify_user(user, logger): 7839f923083SAlexander Pyhalov """ 7849f923083SAlexander Pyhalov Verify that the specified user exists on this system, and can execute 7859f923083SAlexander Pyhalov sudo without being prompted for a password. 7869f923083SAlexander Pyhalov """ 7879f923083SAlexander Pyhalov testcmd = [SUDO, '-n', '-u', user, TRUE] 7889f923083SAlexander Pyhalov 7899f923083SAlexander Pyhalov if user in Cmd.verified_users: 7909f923083SAlexander Pyhalov return True 7919f923083SAlexander Pyhalov 7929f923083SAlexander Pyhalov try: 7939f923083SAlexander Pyhalov _ = getpwnam(user) 7949f923083SAlexander Pyhalov except KeyError: 7959f923083SAlexander Pyhalov logger.info("Warning: user '%s' does not exist.", user) 7969f923083SAlexander Pyhalov return False 7979f923083SAlexander Pyhalov 7989f923083SAlexander Pyhalov p = Popen(testcmd) 7999f923083SAlexander Pyhalov p.wait() 8009f923083SAlexander Pyhalov if p.returncode is not 0: 8019f923083SAlexander Pyhalov logger.info("Warning: user '%s' cannot use passwordless sudo.", user) 8029f923083SAlexander Pyhalov return False 8039f923083SAlexander Pyhalov else: 8049f923083SAlexander Pyhalov Cmd.verified_users.append(user) 8059f923083SAlexander Pyhalov 8069f923083SAlexander Pyhalov return True 8079f923083SAlexander Pyhalov 8089f923083SAlexander Pyhalov 8099f923083SAlexander Pyhalovdef find_tests(testrun, options): 8109f923083SAlexander Pyhalov """ 8119f923083SAlexander Pyhalov For the given list of pathnames, add files as Tests. For directories, 8129f923083SAlexander Pyhalov if do_groups is True, add the directory as a TestGroup. If False, 8139f923083SAlexander Pyhalov recursively search for executable files. 8149f923083SAlexander Pyhalov """ 8159f923083SAlexander Pyhalov 8169f923083SAlexander Pyhalov for p in sorted(options.pathnames): 8179f923083SAlexander Pyhalov if os.path.isdir(p): 8189f923083SAlexander Pyhalov for dirname, _, filenames in os.walk(p): 8199f923083SAlexander Pyhalov if options.do_groups: 8209f923083SAlexander Pyhalov testrun.addtestgroup(dirname, filenames, options) 8219f923083SAlexander Pyhalov else: 8229f923083SAlexander Pyhalov for f in sorted(filenames): 8239f923083SAlexander Pyhalov testrun.addtest(os.path.join(dirname, f), options) 8249f923083SAlexander Pyhalov else: 8259f923083SAlexander Pyhalov testrun.addtest(p, options) 8269f923083SAlexander Pyhalov 8279f923083SAlexander Pyhalov 8289f923083SAlexander Pyhalovdef fail(retstr, ret=1): 829b0858fdcSAlexander Pyhalov print('%s: %s' % (argv[0], retstr)) 8309f923083SAlexander Pyhalov exit(ret) 8319f923083SAlexander Pyhalov 8329f923083SAlexander Pyhalov 8339f923083SAlexander Pyhalovdef options_cb(option, opt_str, value, parser): 8349f923083SAlexander Pyhalov path_options = ['runfile', 'outputdir', 'template'] 8359f923083SAlexander Pyhalov 8369f923083SAlexander Pyhalov if option.dest is 'runfile' and '-w' in parser.rargs or \ 8379f923083SAlexander Pyhalov option.dest is 'template' and '-c' in parser.rargs: 8389f923083SAlexander Pyhalov fail('-c and -w are mutually exclusive.') 8399f923083SAlexander Pyhalov 8409f923083SAlexander Pyhalov if opt_str in parser.rargs: 8419f923083SAlexander Pyhalov fail('%s may only be specified once.' % opt_str) 8429f923083SAlexander Pyhalov 8439f923083SAlexander Pyhalov if option.dest is 'runfile': 8449f923083SAlexander Pyhalov parser.values.cmd = 'rdconfig' 8459f923083SAlexander Pyhalov if option.dest is 'template': 8469f923083SAlexander Pyhalov parser.values.cmd = 'wrconfig' 8479f923083SAlexander Pyhalov 8489f923083SAlexander Pyhalov setattr(parser.values, option.dest, value) 8499f923083SAlexander Pyhalov if option.dest in path_options: 8509f923083SAlexander Pyhalov setattr(parser.values, option.dest, os.path.abspath(value)) 8519f923083SAlexander Pyhalov 8529f923083SAlexander Pyhalov 8539f923083SAlexander Pyhalovdef parse_args(): 8549f923083SAlexander Pyhalov parser = OptionParser() 8559f923083SAlexander Pyhalov parser.add_option('-c', action='callback', callback=options_cb, 8569f923083SAlexander Pyhalov type='string', dest='runfile', metavar='runfile', 8579f923083SAlexander Pyhalov help='Specify tests to run via config file.') 8589f923083SAlexander Pyhalov parser.add_option('-d', action='store_true', default=False, dest='dryrun', 8599f923083SAlexander Pyhalov help='Dry run. Print tests, but take no other action.') 8609f923083SAlexander Pyhalov parser.add_option('-g', action='store_true', default=False, 8619f923083SAlexander Pyhalov dest='do_groups', help='Make directories TestGroups.') 8629f923083SAlexander Pyhalov parser.add_option('-o', action='callback', callback=options_cb, 8639f923083SAlexander Pyhalov default=BASEDIR, dest='outputdir', type='string', 8649f923083SAlexander Pyhalov metavar='outputdir', help='Specify an output directory.') 8659f923083SAlexander Pyhalov parser.add_option('-p', action='callback', callback=options_cb, 8669f923083SAlexander Pyhalov default='', dest='pre', metavar='script', 8679f923083SAlexander Pyhalov type='string', help='Specify a pre script.') 8689f923083SAlexander Pyhalov parser.add_option('-P', action='callback', callback=options_cb, 8699f923083SAlexander Pyhalov default='', dest='post', metavar='script', 8709f923083SAlexander Pyhalov type='string', help='Specify a post script.') 8719f923083SAlexander Pyhalov parser.add_option('-q', action='store_true', default=False, dest='quiet', 8729f923083SAlexander Pyhalov help='Silence on the console during a test run.') 8739f923083SAlexander Pyhalov parser.add_option('-t', action='callback', callback=options_cb, default=60, 8749f923083SAlexander Pyhalov dest='timeout', metavar='seconds', type='int', 8759f923083SAlexander Pyhalov help='Timeout (in seconds) for an individual test.') 8769f923083SAlexander Pyhalov parser.add_option('-u', action='callback', callback=options_cb, 8779f923083SAlexander Pyhalov default='', dest='user', metavar='user', type='string', 8789f923083SAlexander Pyhalov help='Specify a different user name to run as.') 8799f923083SAlexander Pyhalov parser.add_option('-w', action='callback', callback=options_cb, 8809f923083SAlexander Pyhalov default=None, dest='template', metavar='template', 8819f923083SAlexander Pyhalov type='string', help='Create a new config file.') 8829f923083SAlexander Pyhalov parser.add_option('-x', action='callback', callback=options_cb, default='', 8839f923083SAlexander Pyhalov dest='pre_user', metavar='pre_user', type='string', 8849f923083SAlexander Pyhalov help='Specify a user to execute the pre script.') 8859f923083SAlexander Pyhalov parser.add_option('-X', action='callback', callback=options_cb, default='', 8869f923083SAlexander Pyhalov dest='post_user', metavar='post_user', type='string', 8879f923083SAlexander Pyhalov help='Specify a user to execute the post script.') 8889f923083SAlexander Pyhalov (options, pathnames) = parser.parse_args() 8899f923083SAlexander Pyhalov 8909f923083SAlexander Pyhalov if not options.runfile and not options.template: 8919f923083SAlexander Pyhalov options.cmd = 'runtests' 8929f923083SAlexander Pyhalov 8939f923083SAlexander Pyhalov if options.runfile and len(pathnames): 8949f923083SAlexander Pyhalov fail('Extraneous arguments.') 8959f923083SAlexander Pyhalov 8969f923083SAlexander Pyhalov options.pathnames = [os.path.abspath(path) for path in pathnames] 8979f923083SAlexander Pyhalov 8989f923083SAlexander Pyhalov return options 8999f923083SAlexander Pyhalov 9009f923083SAlexander Pyhalov 9019f923083SAlexander Pyhalovdef main(): 9029f923083SAlexander Pyhalov options = parse_args() 9039f923083SAlexander Pyhalov testrun = TestRun(options) 9049f923083SAlexander Pyhalov 9059f923083SAlexander Pyhalov if options.cmd is 'runtests': 9069f923083SAlexander Pyhalov find_tests(testrun, options) 9079f923083SAlexander Pyhalov elif options.cmd is 'rdconfig': 9089f923083SAlexander Pyhalov testrun.read(testrun.logger, options) 9099f923083SAlexander Pyhalov elif options.cmd is 'wrconfig': 9109f923083SAlexander Pyhalov find_tests(testrun, options) 9119f923083SAlexander Pyhalov testrun.write(options) 9129f923083SAlexander Pyhalov exit(0) 9139f923083SAlexander Pyhalov else: 9149f923083SAlexander Pyhalov fail('Unknown command specified') 9159f923083SAlexander Pyhalov 9169f923083SAlexander Pyhalov testrun.complete_outputdirs() 9179f923083SAlexander Pyhalov testrun.run(options) 9189f923083SAlexander Pyhalov testrun.summary() 9192491fc01SJohn Levon exit(retcode) 9209f923083SAlexander Pyhalov 9219f923083SAlexander Pyhalov 9229f923083SAlexander Pyhalovif __name__ == '__main__': 9239f923083SAlexander Pyhalov main() 924