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. 181a2acdcdSAndy Fiddaman# Copyright 2020 OmniOS Community Edition (OmniOSce) Association. 199f923083SAlexander Pyhalov# 209f923083SAlexander Pyhalov 21b0858fdcSAlexander Pyhalovfrom __future__ import print_function 22b0858fdcSAlexander Pyhalovimport sys 23b0858fdcSAlexander PyhalovPY3 = sys.version_info[0] == 3 24b0858fdcSAlexander Pyhalov 25b0858fdcSAlexander Pyhalovif PY3: 26b0858fdcSAlexander Pyhalov import configparser 27b0858fdcSAlexander Pyhalovelse: 28b0858fdcSAlexander Pyhalov import ConfigParser as configparser 29b0858fdcSAlexander Pyhalov 30cdd7a662SAlexander Pyhalovimport io 319f923083SAlexander Pyhalovimport os 329f923083SAlexander Pyhalovimport logging 33a0955b86SJohn Levonimport platform 3478801af7SAndy Fiddamanimport re 359f923083SAlexander Pyhalovfrom logging.handlers import WatchedFileHandler 369f923083SAlexander Pyhalovfrom datetime import datetime 379f923083SAlexander Pyhalovfrom optparse import OptionParser 389f923083SAlexander Pyhalovfrom pwd import getpwnam 399f923083SAlexander Pyhalovfrom pwd import getpwuid 409f923083SAlexander Pyhalovfrom select import select 419f923083SAlexander Pyhalovfrom subprocess import PIPE 429f923083SAlexander Pyhalovfrom subprocess import Popen 439f923083SAlexander Pyhalovfrom sys import argv 44b0858fdcSAlexander Pyhalovfrom sys import exit 45b0858fdcSAlexander Pyhalovfrom sys import maxsize 469f923083SAlexander Pyhalovfrom threading import Timer 479f923083SAlexander Pyhalovfrom time import time 489f923083SAlexander Pyhalov 499f923083SAlexander PyhalovBASEDIR = '/var/tmp/test_results' 50b8052df9SRyan MoellerTESTDIR = '/opt/zfs-tests/' 519f923083SAlexander PyhalovKILL = '/usr/bin/kill' 529f923083SAlexander PyhalovTRUE = '/usr/bin/true' 539f923083SAlexander PyhalovSUDO = '/usr/bin/sudo' 549f923083SAlexander Pyhalov 552491fc01SJohn Levonretcode = 0 562491fc01SJohn Levon 579f923083SAlexander Pyhalov# Custom class to reopen the log file in case it is forcibly closed by a test. 589f923083SAlexander Pyhalovclass WatchedFileHandlerClosed(WatchedFileHandler): 599f923083SAlexander Pyhalov """Watch files, including closed files. 609f923083SAlexander Pyhalov Similar to (and inherits from) logging.handler.WatchedFileHandler, 619f923083SAlexander Pyhalov except that IOErrors are handled by reopening the stream and retrying. 629f923083SAlexander Pyhalov This will be retried up to a configurable number of times before 639f923083SAlexander Pyhalov giving up, default 5. 649f923083SAlexander Pyhalov """ 659f923083SAlexander Pyhalov 66cdd7a662SAlexander Pyhalov def __init__(self, filename, mode='a', encoding='utf-8', delay=0, max_tries=5): 679f923083SAlexander Pyhalov self.max_tries = max_tries 689f923083SAlexander Pyhalov self.tries = 0 699f923083SAlexander Pyhalov WatchedFileHandler.__init__(self, filename, mode, encoding, delay) 709f923083SAlexander Pyhalov 719f923083SAlexander Pyhalov def emit(self, record): 729f923083SAlexander Pyhalov while True: 739f923083SAlexander Pyhalov try: 749f923083SAlexander Pyhalov WatchedFileHandler.emit(self, record) 759f923083SAlexander Pyhalov self.tries = 0 769f923083SAlexander Pyhalov return 779f923083SAlexander Pyhalov except IOError as err: 789f923083SAlexander Pyhalov if self.tries == self.max_tries: 799f923083SAlexander Pyhalov raise 809f923083SAlexander Pyhalov self.stream.close() 819f923083SAlexander Pyhalov self.stream = self._open() 829f923083SAlexander Pyhalov self.tries += 1 839f923083SAlexander Pyhalov 849f923083SAlexander Pyhalovclass Result(object): 859f923083SAlexander Pyhalov total = 0 869f923083SAlexander Pyhalov runresults = {'PASS': 0, 'FAIL': 0, 'SKIP': 0, 'KILLED': 0} 879f923083SAlexander Pyhalov 889f923083SAlexander Pyhalov def __init__(self): 899f923083SAlexander Pyhalov self.starttime = None 909f923083SAlexander Pyhalov self.returncode = None 919f923083SAlexander Pyhalov self.runtime = '' 929f923083SAlexander Pyhalov self.stdout = [] 939f923083SAlexander Pyhalov self.stderr = [] 949f923083SAlexander Pyhalov self.result = '' 959f923083SAlexander Pyhalov 969f923083SAlexander Pyhalov def done(self, proc, killed): 979f923083SAlexander Pyhalov """ 989f923083SAlexander Pyhalov Finalize the results of this Cmd. 9996c8483aSYuri Pankov Report SKIP for return codes 3,4 (NOTINUSE, UNSUPPORTED) 10096c8483aSYuri Pankov as defined in ../stf/include/stf.shlib 1019f923083SAlexander Pyhalov """ 1022491fc01SJohn Levon global retcode 1032491fc01SJohn Levon 1049f923083SAlexander Pyhalov Result.total += 1 1059f923083SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 1069f923083SAlexander Pyhalov self.runtime = '%02d:%02d' % (m, s) 1079f923083SAlexander Pyhalov self.returncode = proc.returncode 1089f923083SAlexander Pyhalov if killed: 1099f923083SAlexander Pyhalov self.result = 'KILLED' 1109f923083SAlexander Pyhalov Result.runresults['KILLED'] += 1 1112491fc01SJohn Levon retcode = 2; 1121a2acdcdSAndy Fiddaman elif self.returncode == 0: 1139f923083SAlexander Pyhalov self.result = 'PASS' 1149f923083SAlexander Pyhalov Result.runresults['PASS'] += 1 1151a2acdcdSAndy Fiddaman elif self.returncode == 3 or self.returncode == 4: 11696c8483aSYuri Pankov self.result = 'SKIP' 11796c8483aSYuri Pankov Result.runresults['SKIP'] += 1 1181a2acdcdSAndy Fiddaman elif self.returncode != 0: 1199f923083SAlexander Pyhalov self.result = 'FAIL' 1209f923083SAlexander Pyhalov Result.runresults['FAIL'] += 1 1212491fc01SJohn Levon retcode = 1; 1229f923083SAlexander Pyhalov 1239f923083SAlexander Pyhalov 1249f923083SAlexander Pyhalovclass Output(object): 1259f923083SAlexander Pyhalov """ 1269f923083SAlexander Pyhalov This class is a slightly modified version of the 'Stream' class found 1279f923083SAlexander Pyhalov here: http://goo.gl/aSGfv 1289f923083SAlexander Pyhalov """ 1299f923083SAlexander Pyhalov def __init__(self, stream): 1309f923083SAlexander Pyhalov self.stream = stream 1319f923083SAlexander Pyhalov self._buf = '' 1329f923083SAlexander Pyhalov self.lines = [] 1339f923083SAlexander Pyhalov 1349f923083SAlexander Pyhalov def fileno(self): 1359f923083SAlexander Pyhalov return self.stream.fileno() 1369f923083SAlexander Pyhalov 1379f923083SAlexander Pyhalov def read(self, drain=0): 1389f923083SAlexander Pyhalov """ 1399f923083SAlexander Pyhalov Read from the file descriptor. If 'drain' set, read until EOF. 1409f923083SAlexander Pyhalov """ 1419f923083SAlexander Pyhalov while self._read() is not None: 1429f923083SAlexander Pyhalov if not drain: 1439f923083SAlexander Pyhalov break 1449f923083SAlexander Pyhalov 1459f923083SAlexander Pyhalov def _read(self): 1469f923083SAlexander Pyhalov """ 1479f923083SAlexander Pyhalov Read up to 4k of data from this output stream. Collect the output 1489f923083SAlexander Pyhalov up to the last newline, and append it to any leftover data from a 1499f923083SAlexander Pyhalov previous call. The lines are stored as a (timestamp, data) tuple 1509f923083SAlexander Pyhalov for easy sorting/merging later. 1519f923083SAlexander Pyhalov """ 1529f923083SAlexander Pyhalov fd = self.fileno() 153cc37296fSAndy Fiddaman buf = os.read(fd, 4096).decode('utf-8', errors='ignore') 1549f923083SAlexander Pyhalov if not buf: 1559f923083SAlexander Pyhalov return None 1569f923083SAlexander Pyhalov if '\n' not in buf: 1579f923083SAlexander Pyhalov self._buf += buf 1589f923083SAlexander Pyhalov return [] 1599f923083SAlexander Pyhalov 1609f923083SAlexander Pyhalov buf = self._buf + buf 1619f923083SAlexander Pyhalov tmp, rest = buf.rsplit('\n', 1) 1629f923083SAlexander Pyhalov self._buf = rest 1639f923083SAlexander Pyhalov now = datetime.now() 1649f923083SAlexander Pyhalov rows = tmp.split('\n') 1659f923083SAlexander Pyhalov self.lines += [(now, r) for r in rows] 1669f923083SAlexander Pyhalov 1679f923083SAlexander Pyhalov 1689f923083SAlexander Pyhalovclass Cmd(object): 1699f923083SAlexander Pyhalov verified_users = [] 1709f923083SAlexander Pyhalov 171b8052df9SRyan Moeller def __init__(self, pathname, identifier=None, outputdir=None, 172b8052df9SRyan Moeller timeout=None, user=None, tags=None): 1739f923083SAlexander Pyhalov self.pathname = pathname 174b8052df9SRyan Moeller self.identifier = identifier 1759f923083SAlexander Pyhalov self.outputdir = outputdir or 'BASEDIR' 1769f923083SAlexander Pyhalov self.timeout = timeout 1779f923083SAlexander Pyhalov self.user = user or '' 1789f923083SAlexander Pyhalov self.killed = False 1799f923083SAlexander Pyhalov self.result = Result() 1809f923083SAlexander Pyhalov 1819f923083SAlexander Pyhalov if self.timeout is None: 1829f923083SAlexander Pyhalov self.timeout = 60 1839f923083SAlexander Pyhalov 1849f923083SAlexander Pyhalov def __str__(self): 185b8052df9SRyan Moeller return '''\ 186b8052df9SRyan MoellerPathname: %s 187b8052df9SRyan MoellerIdentifier: %s 188b8052df9SRyan MoellerOutputdir: %s 189b8052df9SRyan MoellerTimeout: %d 190b8052df9SRyan MoellerUser: %s 191b8052df9SRyan Moeller''' % (self.pathname, self.identifier, self.outputdir, self.timeout, self.user) 1929f923083SAlexander Pyhalov 1939f923083SAlexander Pyhalov def kill_cmd(self, proc): 1949f923083SAlexander Pyhalov """ 1959f923083SAlexander Pyhalov Kill a running command due to timeout, or ^C from the keyboard. If 1969f923083SAlexander Pyhalov sudo is required, this user was verified previously. 1979f923083SAlexander Pyhalov """ 1989f923083SAlexander Pyhalov self.killed = True 1999f923083SAlexander Pyhalov do_sudo = len(self.user) != 0 2009f923083SAlexander Pyhalov signal = '-TERM' 2019f923083SAlexander Pyhalov 2029f923083SAlexander Pyhalov cmd = [SUDO, KILL, signal, str(proc.pid)] 2039f923083SAlexander Pyhalov if not do_sudo: 2049f923083SAlexander Pyhalov del cmd[0] 2059f923083SAlexander Pyhalov 2069f923083SAlexander Pyhalov try: 2079f923083SAlexander Pyhalov kp = Popen(cmd) 2089f923083SAlexander Pyhalov kp.wait() 2099f923083SAlexander Pyhalov except: 2109f923083SAlexander Pyhalov pass 2119f923083SAlexander Pyhalov 2129f923083SAlexander Pyhalov def update_cmd_privs(self, cmd, user): 2139f923083SAlexander Pyhalov """ 2149f923083SAlexander Pyhalov If a user has been specified to run this Cmd and we're not already 2159f923083SAlexander Pyhalov running as that user, prepend the appropriate sudo command to run 2169f923083SAlexander Pyhalov as that user. 2179f923083SAlexander Pyhalov """ 2189f923083SAlexander Pyhalov me = getpwuid(os.getuid()) 2199f923083SAlexander Pyhalov 220*e581456dSPatrick Mooney if not user or user == me.pw_name: 2219f923083SAlexander Pyhalov return cmd 2229f923083SAlexander Pyhalov 2239f923083SAlexander Pyhalov ret = '%s -E -u %s %s' % (SUDO, user, cmd) 2249f923083SAlexander Pyhalov return ret.split(' ') 2259f923083SAlexander Pyhalov 2269f923083SAlexander Pyhalov def collect_output(self, proc): 2279f923083SAlexander Pyhalov """ 2289f923083SAlexander Pyhalov Read from stdout/stderr as data becomes available, until the 2299f923083SAlexander Pyhalov process is no longer running. Return the lines from the stdout and 2309f923083SAlexander Pyhalov stderr Output objects. 2319f923083SAlexander Pyhalov """ 2329f923083SAlexander Pyhalov out = Output(proc.stdout) 2339f923083SAlexander Pyhalov err = Output(proc.stderr) 2349f923083SAlexander Pyhalov res = [] 2359f923083SAlexander Pyhalov while proc.returncode is None: 2369f923083SAlexander Pyhalov proc.poll() 2379f923083SAlexander Pyhalov res = select([out, err], [], [], .1) 2389f923083SAlexander Pyhalov for fd in res[0]: 2399f923083SAlexander Pyhalov fd.read() 2409f923083SAlexander Pyhalov for fd in res[0]: 2419f923083SAlexander Pyhalov fd.read(drain=1) 2429f923083SAlexander Pyhalov 2439f923083SAlexander Pyhalov return out.lines, err.lines 2449f923083SAlexander Pyhalov 2459f923083SAlexander Pyhalov def run(self, options): 2469f923083SAlexander Pyhalov """ 2479f923083SAlexander Pyhalov This is the main function that runs each individual test. 2489f923083SAlexander Pyhalov Determine whether or not the command requires sudo, and modify it 2499f923083SAlexander Pyhalov if needed. Run the command, and update the result object. 2509f923083SAlexander Pyhalov """ 2519f923083SAlexander Pyhalov if options.dryrun is True: 252b0858fdcSAlexander Pyhalov print(self) 2539f923083SAlexander Pyhalov return 2549f923083SAlexander Pyhalov 2559f923083SAlexander Pyhalov privcmd = self.update_cmd_privs(self.pathname, self.user) 2569f923083SAlexander Pyhalov try: 2579f923083SAlexander Pyhalov old = os.umask(0) 2589f923083SAlexander Pyhalov if not os.path.isdir(self.outputdir): 259b0858fdcSAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 2609f923083SAlexander Pyhalov os.umask(old) 261b0858fdcSAlexander Pyhalov except OSError as e: 2629f923083SAlexander Pyhalov fail('%s' % e) 2639f923083SAlexander Pyhalov 2649f923083SAlexander Pyhalov try: 2659f923083SAlexander Pyhalov self.result.starttime = time() 266b0858fdcSAlexander Pyhalov proc = Popen(privcmd, stdout=PIPE, stderr=PIPE, stdin=PIPE, 267b0858fdcSAlexander Pyhalov universal_newlines=True) 2685cabbc6bSPrashanth Sreenivasa proc.stdin.close() 2699f923083SAlexander Pyhalov 2709f923083SAlexander Pyhalov # Allow a special timeout value of 0 to mean infinity 2719f923083SAlexander Pyhalov if int(self.timeout) == 0: 272b0858fdcSAlexander Pyhalov self.timeout = maxsize 2739f923083SAlexander Pyhalov t = Timer(int(self.timeout), self.kill_cmd, [proc]) 2749f923083SAlexander Pyhalov t.start() 2759f923083SAlexander Pyhalov self.result.stdout, self.result.stderr = self.collect_output(proc) 2769f923083SAlexander Pyhalov except KeyboardInterrupt: 2779f923083SAlexander Pyhalov self.kill_cmd(proc) 2789f923083SAlexander Pyhalov fail('\nRun terminated at user request.') 2799f923083SAlexander Pyhalov finally: 2809f923083SAlexander Pyhalov t.cancel() 2819f923083SAlexander Pyhalov 2829f923083SAlexander Pyhalov self.result.done(proc, self.killed) 2839f923083SAlexander Pyhalov 2849f923083SAlexander Pyhalov def skip(self): 2859f923083SAlexander Pyhalov """ 2869f923083SAlexander Pyhalov Initialize enough of the test result that we can log a skipped 2879f923083SAlexander Pyhalov command. 2889f923083SAlexander Pyhalov """ 2899f923083SAlexander Pyhalov Result.total += 1 2909f923083SAlexander Pyhalov Result.runresults['SKIP'] += 1 2919f923083SAlexander Pyhalov self.result.stdout = self.result.stderr = [] 2929f923083SAlexander Pyhalov self.result.starttime = time() 2939f923083SAlexander Pyhalov m, s = divmod(time() - self.result.starttime, 60) 2949f923083SAlexander Pyhalov self.result.runtime = '%02d:%02d' % (m, s) 2959f923083SAlexander Pyhalov self.result.result = 'SKIP' 2969f923083SAlexander Pyhalov 2979f923083SAlexander Pyhalov def log(self, logger, options): 2989f923083SAlexander Pyhalov """ 2999f923083SAlexander Pyhalov This function is responsible for writing all output. This includes 3009f923083SAlexander Pyhalov the console output, the logfile of all results (with timestamped 3019f923083SAlexander Pyhalov merged stdout and stderr), and for each test, the unmodified 3029f923083SAlexander Pyhalov stdout/stderr/merged in it's own file. 3039f923083SAlexander Pyhalov """ 3049f923083SAlexander Pyhalov if logger is None: 3059f923083SAlexander Pyhalov return 3069f923083SAlexander Pyhalov 3079f923083SAlexander Pyhalov logname = getpwuid(os.getuid()).pw_name 3089f923083SAlexander Pyhalov user = ' (run as %s)' % (self.user if len(self.user) else logname) 309b8052df9SRyan Moeller if self.identifier: 310b8052df9SRyan Moeller msga = 'Test (%s): %s%s ' % (self.identifier, self.pathname, user) 311b8052df9SRyan Moeller else: 3129f923083SAlexander Pyhalov msga = 'Test: %s%s ' % (self.pathname, user) 3139f923083SAlexander Pyhalov msgb = '[%s] [%s]' % (self.result.runtime, self.result.result) 3149f923083SAlexander Pyhalov pad = ' ' * (80 - (len(msga) + len(msgb))) 3159f923083SAlexander Pyhalov 3169f923083SAlexander Pyhalov # If -q is specified, only print a line for tests that didn't pass. 3179f923083SAlexander Pyhalov # This means passing tests need to be logged as DEBUG, or the one 3189f923083SAlexander Pyhalov # line summary will only be printed in the logfile for failures. 3199f923083SAlexander Pyhalov if not options.quiet: 3209f923083SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 3211a2acdcdSAndy Fiddaman elif self.result.result != 'PASS': 3229f923083SAlexander Pyhalov logger.info('%s%s%s' % (msga, pad, msgb)) 3239f923083SAlexander Pyhalov else: 3249f923083SAlexander Pyhalov logger.debug('%s%s%s' % (msga, pad, msgb)) 3259f923083SAlexander Pyhalov 3269f923083SAlexander Pyhalov lines = sorted(self.result.stdout + self.result.stderr, 327b0858fdcSAlexander Pyhalov key=lambda x: x[0]) 3289f923083SAlexander Pyhalov 3299f923083SAlexander Pyhalov for dt, line in lines: 3309f923083SAlexander Pyhalov logger.debug('%s %s' % (dt.strftime("%H:%M:%S.%f ")[:11], line)) 3319f923083SAlexander Pyhalov 3329f923083SAlexander Pyhalov if len(self.result.stdout): 333cdd7a662SAlexander Pyhalov with io.open(os.path.join(self.outputdir, 'stdout'), 334cdd7a662SAlexander Pyhalov encoding='utf-8', 335cdd7a662SAlexander Pyhalov errors='surrogateescape', 336cdd7a662SAlexander Pyhalov mode='w') as out: 3379f923083SAlexander Pyhalov for _, line in self.result.stdout: 338b0858fdcSAlexander Pyhalov out.write('%s\n' % line) 3399f923083SAlexander Pyhalov if len(self.result.stderr): 340cdd7a662SAlexander Pyhalov with io.open(os.path.join(self.outputdir, 'stderr'), 341cdd7a662SAlexander Pyhalov encoding='utf-8', 342cdd7a662SAlexander Pyhalov errors='surrogateescape', 343cdd7a662SAlexander Pyhalov mode='w') as err: 3449f923083SAlexander Pyhalov for _, line in self.result.stderr: 345b0858fdcSAlexander Pyhalov err.write('%s\n' % line) 3469f923083SAlexander Pyhalov if len(self.result.stdout) and len(self.result.stderr): 347cdd7a662SAlexander Pyhalov with io.open(os.path.join(self.outputdir, 'merged'), 348cdd7a662SAlexander Pyhalov encoding='utf-8', 349cdd7a662SAlexander Pyhalov errors='surrogateescape', 350cdd7a662SAlexander Pyhalov mode='w') as merged: 3519f923083SAlexander Pyhalov for _, line in lines: 352b0858fdcSAlexander Pyhalov merged.write('%s\n' % line) 3539f923083SAlexander Pyhalov 3549f923083SAlexander Pyhalov 3559f923083SAlexander Pyhalovclass Test(Cmd): 3569f923083SAlexander Pyhalov props = ['outputdir', 'timeout', 'user', 'pre', 'pre_user', 'post', 357b8052df9SRyan Moeller 'post_user', 'tags'] 3589f923083SAlexander Pyhalov 359b8052df9SRyan Moeller def __init__(self, pathname, 360b8052df9SRyan Moeller pre=None, pre_user=None, post=None, post_user=None, 361b8052df9SRyan Moeller tags=None, **kwargs): 362b8052df9SRyan Moeller super(Test, self).__init__(pathname, **kwargs) 3639f923083SAlexander Pyhalov self.pre = pre or '' 3649f923083SAlexander Pyhalov self.pre_user = pre_user or '' 3659f923083SAlexander Pyhalov self.post = post or '' 3669f923083SAlexander Pyhalov self.post_user = post_user or '' 367b8052df9SRyan Moeller self.tags = tags or [] 3689f923083SAlexander Pyhalov 3699f923083SAlexander Pyhalov def __str__(self): 3709f923083SAlexander Pyhalov post_user = pre_user = '' 3719f923083SAlexander Pyhalov if len(self.pre_user): 3729f923083SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 3739f923083SAlexander Pyhalov if len(self.post_user): 3749f923083SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 375b8052df9SRyan Moeller return '''\ 376b8052df9SRyan MoellerPathname: %s 377b8052df9SRyan MoellerIdentifier: %s 378b8052df9SRyan MoellerOutputdir: %s 379b8052df9SRyan MoellerTimeout: %d 380b8052df9SRyan MoellerUser: %s 381b8052df9SRyan MoellerPre: %s%s 382b8052df9SRyan MoellerPost: %s%s 383b8052df9SRyan MoellerTags: %s 384b8052df9SRyan Moeller''' % (self.pathname, self.identifier, self.outputdir, self.timeout, self.user, 385b8052df9SRyan Moeller self.pre, pre_user, self.post, post_user, self.tags) 3869f923083SAlexander Pyhalov 3879f923083SAlexander Pyhalov def verify(self, logger): 3889f923083SAlexander Pyhalov """ 3899f923083SAlexander Pyhalov Check the pre/post scripts, user and Test. Omit the Test from this 3909f923083SAlexander Pyhalov run if there are any problems. 3919f923083SAlexander Pyhalov """ 3929f923083SAlexander Pyhalov files = [self.pre, self.pathname, self.post] 3939f923083SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 3949f923083SAlexander Pyhalov 3959f923083SAlexander Pyhalov for f in [f for f in files if len(f)]: 3969f923083SAlexander Pyhalov if not verify_file(f): 3979f923083SAlexander Pyhalov logger.info("Warning: Test '%s' not added to this run because" 3989f923083SAlexander Pyhalov " it failed verification." % f) 3999f923083SAlexander Pyhalov return False 4009f923083SAlexander Pyhalov 4019f923083SAlexander Pyhalov for user in [user for user in users if len(user)]: 4029f923083SAlexander Pyhalov if not verify_user(user, logger): 4039f923083SAlexander Pyhalov logger.info("Not adding Test '%s' to this run." % 4049f923083SAlexander Pyhalov self.pathname) 4059f923083SAlexander Pyhalov return False 4069f923083SAlexander Pyhalov 4079f923083SAlexander Pyhalov return True 4089f923083SAlexander Pyhalov 4099f923083SAlexander Pyhalov def run(self, logger, options): 4109f923083SAlexander Pyhalov """ 4119f923083SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 4129f923083SAlexander Pyhalov doesn't pass, skip this Test. Run the post script regardless. 4139f923083SAlexander Pyhalov """ 4149f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.pre)) 415b8052df9SRyan Moeller pretest = Cmd(self.pre, identifier=self.identifier, outputdir=odir, 416b8052df9SRyan Moeller timeout=self.timeout, user=self.pre_user) 417b8052df9SRyan Moeller test = Cmd(self.pathname, identifier=self.identifier, 418b8052df9SRyan Moeller outputdir=self.outputdir, timeout=self.timeout, 419b8052df9SRyan Moeller user=self.user) 4209f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.post)) 421b8052df9SRyan Moeller posttest = Cmd(self.post, identifier=self.identifier, outputdir=odir, 422b8052df9SRyan Moeller timeout=self.timeout, user=self.post_user) 4239f923083SAlexander Pyhalov 4249f923083SAlexander Pyhalov cont = True 4259f923083SAlexander Pyhalov if len(pretest.pathname): 4269f923083SAlexander Pyhalov pretest.run(options) 4271a2acdcdSAndy Fiddaman cont = pretest.result.result == 'PASS' 4289f923083SAlexander Pyhalov pretest.log(logger, options) 4299f923083SAlexander Pyhalov 4309f923083SAlexander Pyhalov if cont: 4319f923083SAlexander Pyhalov test.run(options) 4329f923083SAlexander Pyhalov else: 4339f923083SAlexander Pyhalov test.skip() 4349f923083SAlexander Pyhalov 4359f923083SAlexander Pyhalov test.log(logger, options) 4369f923083SAlexander Pyhalov 4379f923083SAlexander Pyhalov if len(posttest.pathname): 4389f923083SAlexander Pyhalov posttest.run(options) 4399f923083SAlexander Pyhalov posttest.log(logger, options) 4409f923083SAlexander Pyhalov 4419f923083SAlexander Pyhalov 4429f923083SAlexander Pyhalovclass TestGroup(Test): 4439f923083SAlexander Pyhalov props = Test.props + ['tests'] 4449f923083SAlexander Pyhalov 445b8052df9SRyan Moeller def __init__(self, pathname, tests=None, **kwargs): 446b8052df9SRyan Moeller super(TestGroup, self).__init__(pathname, **kwargs) 4479f923083SAlexander Pyhalov self.tests = tests or [] 4489f923083SAlexander Pyhalov 4499f923083SAlexander Pyhalov def __str__(self): 4509f923083SAlexander Pyhalov post_user = pre_user = '' 4519f923083SAlexander Pyhalov if len(self.pre_user): 4529f923083SAlexander Pyhalov pre_user = ' (as %s)' % (self.pre_user) 4539f923083SAlexander Pyhalov if len(self.post_user): 4549f923083SAlexander Pyhalov post_user = ' (as %s)' % (self.post_user) 455b8052df9SRyan Moeller return '''\ 456b8052df9SRyan MoellerPathname: %s 457b8052df9SRyan MoellerIdentifier: %s 458b8052df9SRyan MoellerOutputdir: %s 459b8052df9SRyan MoellerTests: %s 460b8052df9SRyan MoellerTimeout: %d 461b8052df9SRyan MoellerUser: %s 462b8052df9SRyan MoellerPre: %s%s 463b8052df9SRyan MoellerPost: %s%s 464b8052df9SRyan MoellerTags: %s 465b8052df9SRyan Moeller''' % (self.pathname, self.identifier, self.outputdir, self.tests, 466b8052df9SRyan Moeller self.timeout, self.user, self.pre, pre_user, self.post, post_user, 467b8052df9SRyan Moeller self.tags) 4689f923083SAlexander Pyhalov 46978801af7SAndy Fiddaman def filter(self, keeplist): 47078801af7SAndy Fiddaman self.tests = [ x for x in self.tests if x in keeplist ] 47178801af7SAndy Fiddaman 4729f923083SAlexander Pyhalov def verify(self, logger): 4739f923083SAlexander Pyhalov """ 4749f923083SAlexander Pyhalov Check the pre/post scripts, user and tests in this TestGroup. Omit 4759f923083SAlexander Pyhalov the TestGroup entirely, or simply delete the relevant tests in the 4769f923083SAlexander Pyhalov group, if that's all that's required. 4779f923083SAlexander Pyhalov """ 4789f923083SAlexander Pyhalov # If the pre or post scripts are relative pathnames, convert to 4799f923083SAlexander Pyhalov # absolute, so they stand a chance of passing verification. 4809f923083SAlexander Pyhalov if len(self.pre) and not os.path.isabs(self.pre): 4819f923083SAlexander Pyhalov self.pre = os.path.join(self.pathname, self.pre) 4829f923083SAlexander Pyhalov if len(self.post) and not os.path.isabs(self.post): 4839f923083SAlexander Pyhalov self.post = os.path.join(self.pathname, self.post) 4849f923083SAlexander Pyhalov 4859f923083SAlexander Pyhalov auxfiles = [self.pre, self.post] 4869f923083SAlexander Pyhalov users = [self.pre_user, self.user, self.post_user] 4879f923083SAlexander Pyhalov 4889f923083SAlexander Pyhalov for f in [f for f in auxfiles if len(f)]: 4899f923083SAlexander Pyhalov if self.pathname != os.path.dirname(f): 4909f923083SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 4919f923083SAlexander Pyhalov "Auxiliary script '%s' exists in a different " 4929f923083SAlexander Pyhalov "directory." % (self.pathname, f)) 4939f923083SAlexander Pyhalov return False 4949f923083SAlexander Pyhalov 4959f923083SAlexander Pyhalov if not verify_file(f): 4969f923083SAlexander Pyhalov logger.info("Warning: TestGroup '%s' not added to this run. " 4979f923083SAlexander Pyhalov "Auxiliary script '%s' failed verification." % 4989f923083SAlexander Pyhalov (self.pathname, f)) 4999f923083SAlexander Pyhalov return False 5009f923083SAlexander Pyhalov 5019f923083SAlexander Pyhalov for user in [user for user in users if len(user)]: 5029f923083SAlexander Pyhalov if not verify_user(user, logger): 5039f923083SAlexander Pyhalov logger.info("Not adding TestGroup '%s' to this run." % 5049f923083SAlexander Pyhalov self.pathname) 5059f923083SAlexander Pyhalov return False 5069f923083SAlexander Pyhalov 5079f923083SAlexander Pyhalov # If one of the tests is invalid, delete it, log it, and drive on. 508dfc11533SChris Williamson self.tests[:] = [f for f in self.tests if 509dfc11533SChris Williamson verify_file(os.path.join(self.pathname, f))] 5109f923083SAlexander Pyhalov 5111a2acdcdSAndy Fiddaman return len(self.tests) != 0 5129f923083SAlexander Pyhalov 5139f923083SAlexander Pyhalov def run(self, logger, options): 5149f923083SAlexander Pyhalov """ 5159f923083SAlexander Pyhalov Create Cmd instances for the pre/post scripts. If the pre script 5169f923083SAlexander Pyhalov doesn't pass, skip all the tests in this TestGroup. Run the post 5179f923083SAlexander Pyhalov script regardless. 5189f923083SAlexander Pyhalov """ 519b8052df9SRyan Moeller # tags assigned to this test group also include the test names 520b8052df9SRyan Moeller if options.tags and not set(self.tags).intersection(set(options.tags)): 521b8052df9SRyan Moeller return 522b8052df9SRyan Moeller 5239f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.pre)) 5249f923083SAlexander Pyhalov pretest = Cmd(self.pre, outputdir=odir, timeout=self.timeout, 525b8052df9SRyan Moeller user=self.pre_user, identifier=self.identifier) 5269f923083SAlexander Pyhalov odir = os.path.join(self.outputdir, os.path.basename(self.post)) 5279f923083SAlexander Pyhalov posttest = Cmd(self.post, outputdir=odir, timeout=self.timeout, 528b8052df9SRyan Moeller user=self.post_user, identifier=self.identifier) 5299f923083SAlexander Pyhalov 5309f923083SAlexander Pyhalov cont = True 5319f923083SAlexander Pyhalov if len(pretest.pathname): 5329f923083SAlexander Pyhalov pretest.run(options) 5331a2acdcdSAndy Fiddaman cont = pretest.result.result == 'PASS' 5349f923083SAlexander Pyhalov pretest.log(logger, options) 5359f923083SAlexander Pyhalov 5369f923083SAlexander Pyhalov for fname in self.tests: 5379f923083SAlexander Pyhalov test = Cmd(os.path.join(self.pathname, fname), 5389f923083SAlexander Pyhalov outputdir=os.path.join(self.outputdir, fname), 539b8052df9SRyan Moeller timeout=self.timeout, user=self.user, 540b8052df9SRyan Moeller identifier=self.identifier) 5419f923083SAlexander Pyhalov if cont: 5429f923083SAlexander Pyhalov test.run(options) 5439f923083SAlexander Pyhalov else: 5449f923083SAlexander Pyhalov test.skip() 5459f923083SAlexander Pyhalov 5469f923083SAlexander Pyhalov test.log(logger, options) 5479f923083SAlexander Pyhalov 5489f923083SAlexander Pyhalov if len(posttest.pathname): 5499f923083SAlexander Pyhalov posttest.run(options) 5509f923083SAlexander Pyhalov posttest.log(logger, options) 5519f923083SAlexander Pyhalov 5529f923083SAlexander Pyhalov 5539f923083SAlexander Pyhalovclass TestRun(object): 5549f923083SAlexander Pyhalov props = ['quiet', 'outputdir'] 5559f923083SAlexander Pyhalov 5569f923083SAlexander Pyhalov def __init__(self, options): 5579f923083SAlexander Pyhalov self.tests = {} 5589f923083SAlexander Pyhalov self.testgroups = {} 5599f923083SAlexander Pyhalov self.starttime = time() 5609f923083SAlexander Pyhalov self.timestamp = datetime.now().strftime('%Y%m%dT%H%M%S') 5619f923083SAlexander Pyhalov self.outputdir = os.path.join(options.outputdir, self.timestamp) 5629f923083SAlexander Pyhalov self.logger = self.setup_logging(options) 5639f923083SAlexander Pyhalov self.defaults = [ 5649f923083SAlexander Pyhalov ('outputdir', BASEDIR), 5659f923083SAlexander Pyhalov ('quiet', False), 5669f923083SAlexander Pyhalov ('timeout', 60), 5679f923083SAlexander Pyhalov ('user', ''), 5689f923083SAlexander Pyhalov ('pre', ''), 5699f923083SAlexander Pyhalov ('pre_user', ''), 5709f923083SAlexander Pyhalov ('post', ''), 571b8052df9SRyan Moeller ('post_user', ''), 572b8052df9SRyan Moeller ('tags', []) 5739f923083SAlexander Pyhalov ] 5749f923083SAlexander Pyhalov 5759f923083SAlexander Pyhalov def __str__(self): 5769f923083SAlexander Pyhalov s = 'TestRun:\n outputdir: %s\n' % self.outputdir 5779f923083SAlexander Pyhalov s += 'TESTS:\n' 5789f923083SAlexander Pyhalov for key in sorted(self.tests.keys()): 5799f923083SAlexander Pyhalov s += '%s%s' % (self.tests[key].__str__(), '\n') 5809f923083SAlexander Pyhalov s += 'TESTGROUPS:\n' 5819f923083SAlexander Pyhalov for key in sorted(self.testgroups.keys()): 5829f923083SAlexander Pyhalov s += '%s%s' % (self.testgroups[key].__str__(), '\n') 5839f923083SAlexander Pyhalov return s 5849f923083SAlexander Pyhalov 5859f923083SAlexander Pyhalov def addtest(self, pathname, options): 5869f923083SAlexander Pyhalov """ 5879f923083SAlexander Pyhalov Create a new Test, and apply any properties that were passed in 5889f923083SAlexander Pyhalov from the command line. If it passes verification, add it to the 5899f923083SAlexander Pyhalov TestRun. 5909f923083SAlexander Pyhalov """ 5919f923083SAlexander Pyhalov test = Test(pathname) 5929f923083SAlexander Pyhalov for prop in Test.props: 5939f923083SAlexander Pyhalov setattr(test, prop, getattr(options, prop)) 5949f923083SAlexander Pyhalov 5959f923083SAlexander Pyhalov if test.verify(self.logger): 5969f923083SAlexander Pyhalov self.tests[pathname] = test 5979f923083SAlexander Pyhalov 5989f923083SAlexander Pyhalov def addtestgroup(self, dirname, filenames, options): 5999f923083SAlexander Pyhalov """ 6009f923083SAlexander Pyhalov Create a new TestGroup, and apply any properties that were passed 6019f923083SAlexander Pyhalov in from the command line. If it passes verification, add it to the 6029f923083SAlexander Pyhalov TestRun. 6039f923083SAlexander Pyhalov """ 6049f923083SAlexander Pyhalov if dirname not in self.testgroups: 6059f923083SAlexander Pyhalov testgroup = TestGroup(dirname) 6069f923083SAlexander Pyhalov for prop in Test.props: 6079f923083SAlexander Pyhalov setattr(testgroup, prop, getattr(options, prop)) 6089f923083SAlexander Pyhalov 6099f923083SAlexander Pyhalov # Prevent pre/post scripts from running as regular tests 6109f923083SAlexander Pyhalov for f in [testgroup.pre, testgroup.post]: 6119f923083SAlexander Pyhalov if f in filenames: 6129f923083SAlexander Pyhalov del filenames[filenames.index(f)] 6139f923083SAlexander Pyhalov 6149f923083SAlexander Pyhalov self.testgroups[dirname] = testgroup 6159f923083SAlexander Pyhalov self.testgroups[dirname].tests = sorted(filenames) 6169f923083SAlexander Pyhalov 6179f923083SAlexander Pyhalov testgroup.verify(self.logger) 6189f923083SAlexander Pyhalov 61978801af7SAndy Fiddaman def filter(self, keeplist): 62078801af7SAndy Fiddaman for group in list(self.testgroups.keys()): 62178801af7SAndy Fiddaman if group not in keeplist: 62278801af7SAndy Fiddaman del self.testgroups[group] 62378801af7SAndy Fiddaman continue 62478801af7SAndy Fiddaman 62578801af7SAndy Fiddaman g = self.testgroups[group] 62678801af7SAndy Fiddaman 62778801af7SAndy Fiddaman if g.pre and os.path.basename(g.pre) in keeplist[group]: 62878801af7SAndy Fiddaman continue 62978801af7SAndy Fiddaman 63078801af7SAndy Fiddaman g.filter(keeplist[group]) 63178801af7SAndy Fiddaman 63278801af7SAndy Fiddaman for test in list(self.tests.keys()): 63378801af7SAndy Fiddaman directory, base = os.path.split(test) 63478801af7SAndy Fiddaman if directory not in keeplist or base not in keeplist[directory]: 63578801af7SAndy Fiddaman del self.tests[test] 63678801af7SAndy Fiddaman 6379f923083SAlexander Pyhalov def read(self, logger, options): 6389f923083SAlexander Pyhalov """ 6399f923083SAlexander Pyhalov Read in the specified runfile, and apply the TestRun properties 6409f923083SAlexander Pyhalov listed in the 'DEFAULT' section to our TestRun. Then read each 6419f923083SAlexander Pyhalov section, and apply the appropriate properties to the Test or 6429f923083SAlexander Pyhalov TestGroup. Properties from individual sections override those set 6439f923083SAlexander Pyhalov in the 'DEFAULT' section. If the Test or TestGroup passes 6449f923083SAlexander Pyhalov verification, add it to the TestRun. 6459f923083SAlexander Pyhalov """ 646b0858fdcSAlexander Pyhalov config = configparser.RawConfigParser() 647b8052df9SRyan Moeller parsed = config.read(options.runfiles) 648b8052df9SRyan Moeller failed = options.runfiles - set(parsed) 649b8052df9SRyan Moeller if len(failed): 650b8052df9SRyan Moeller files = ' '.join(sorted(failed)) 651b8052df9SRyan Moeller fail("Couldn't read config files: %s" % files) 6529f923083SAlexander Pyhalov 6539f923083SAlexander Pyhalov for opt in TestRun.props: 6549f923083SAlexander Pyhalov if config.has_option('DEFAULT', opt): 6559f923083SAlexander Pyhalov setattr(self, opt, config.get('DEFAULT', opt)) 6569f923083SAlexander Pyhalov self.outputdir = os.path.join(self.outputdir, self.timestamp) 6579f923083SAlexander Pyhalov 658b8052df9SRyan Moeller testdir = options.testdir 659b8052df9SRyan Moeller 6609f923083SAlexander Pyhalov for section in config.sections(): 661a0955b86SJohn Levon if ('arch' in config.options(section) and 662a0955b86SJohn Levon platform.machine() != config.get(section, 'arch')): 663a0955b86SJohn Levon continue 664a0955b86SJohn Levon 665b8052df9SRyan Moeller parts = section.split(':', 1) 666b8052df9SRyan Moeller sectiondir = parts[0] 667b8052df9SRyan Moeller identifier = parts[1] if len(parts) == 2 else None 668b8052df9SRyan Moeller if os.path.isdir(sectiondir): 669b8052df9SRyan Moeller pathname = sectiondir 670b8052df9SRyan Moeller elif os.path.isdir(os.path.join(testdir, sectiondir)): 671b8052df9SRyan Moeller pathname = os.path.join(testdir, sectiondir) 672b8052df9SRyan Moeller else: 673b8052df9SRyan Moeller pathname = sectiondir 674b8052df9SRyan Moeller 675b8052df9SRyan Moeller testgroup = TestGroup(os.path.abspath(pathname), 676b8052df9SRyan Moeller identifier=identifier) 6779f923083SAlexander Pyhalov if 'tests' in config.options(section): 6789f923083SAlexander Pyhalov for prop in TestGroup.props: 6799f923083SAlexander Pyhalov for sect in ['DEFAULT', section]: 6809f923083SAlexander Pyhalov if config.has_option(sect, prop): 681b8052df9SRyan Moeller if prop == 'tags': 682b8052df9SRyan Moeller setattr(testgroup, prop, 683b8052df9SRyan Moeller eval(config.get(sect, prop))) 684b8052df9SRyan Moeller else: 685b8052df9SRyan Moeller setattr(testgroup, prop, 686b8052df9SRyan Moeller config.get(sect, prop)) 6879f923083SAlexander Pyhalov 6889f923083SAlexander Pyhalov # Repopulate tests using eval to convert the string to a list 6899f923083SAlexander Pyhalov testgroup.tests = eval(config.get(section, 'tests')) 6909f923083SAlexander Pyhalov 6919f923083SAlexander Pyhalov if testgroup.verify(logger): 6929f923083SAlexander Pyhalov self.testgroups[section] = testgroup 693dfc11533SChris Williamson 694dfc11533SChris Williamson elif 'autotests' in config.options(section): 695dfc11533SChris Williamson for prop in TestGroup.props: 696dfc11533SChris Williamson for sect in ['DEFAULT', section]: 697dfc11533SChris Williamson if config.has_option(sect, prop): 698dfc11533SChris Williamson setattr(testgroup, prop, config.get(sect, prop)) 699dfc11533SChris Williamson 700b8052df9SRyan Moeller filenames = os.listdir(pathname) 701dfc11533SChris Williamson # only files starting with "tst." are considered tests 702dfc11533SChris Williamson filenames = [f for f in filenames if f.startswith("tst.")] 703dfc11533SChris Williamson testgroup.tests = sorted(filenames) 704dfc11533SChris Williamson 705dfc11533SChris Williamson if testgroup.verify(logger): 706dfc11533SChris Williamson self.testgroups[section] = testgroup 7079f923083SAlexander Pyhalov else: 7089f923083SAlexander Pyhalov test = Test(section) 7099f923083SAlexander Pyhalov for prop in Test.props: 7109f923083SAlexander Pyhalov for sect in ['DEFAULT', section]: 7119f923083SAlexander Pyhalov if config.has_option(sect, prop): 7129f923083SAlexander Pyhalov setattr(test, prop, config.get(sect, prop)) 7139f923083SAlexander Pyhalov 7149f923083SAlexander Pyhalov if test.verify(logger): 7159f923083SAlexander Pyhalov self.tests[section] = test 7169f923083SAlexander Pyhalov 7179f923083SAlexander Pyhalov def write(self, options): 7189f923083SAlexander Pyhalov """ 7199f923083SAlexander Pyhalov Create a configuration file for editing and later use. The 7209f923083SAlexander Pyhalov 'DEFAULT' section of the config file is created from the 7219f923083SAlexander Pyhalov properties that were specified on the command line. Tests are 7229f923083SAlexander Pyhalov simply added as sections that inherit everything from the 7239f923083SAlexander Pyhalov 'DEFAULT' section. TestGroups are the same, except they get an 7249f923083SAlexander Pyhalov option including all the tests to run in that directory. 7259f923083SAlexander Pyhalov """ 7269f923083SAlexander Pyhalov 7279f923083SAlexander Pyhalov defaults = dict([(prop, getattr(options, prop)) for prop, _ in 7289f923083SAlexander Pyhalov self.defaults]) 729b0858fdcSAlexander Pyhalov config = configparser.RawConfigParser(defaults) 7309f923083SAlexander Pyhalov 7319f923083SAlexander Pyhalov for test in sorted(self.tests.keys()): 7329f923083SAlexander Pyhalov config.add_section(test) 73378801af7SAndy Fiddaman for prop in Test.props: 73478801af7SAndy Fiddaman if prop not in self.props: 73578801af7SAndy Fiddaman config.set(testgroup, prop, 73678801af7SAndy Fiddaman getattr(self.testgroups[testgroup], prop)) 7379f923083SAlexander Pyhalov 7389f923083SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 7399f923083SAlexander Pyhalov config.add_section(testgroup) 7409f923083SAlexander Pyhalov config.set(testgroup, 'tests', self.testgroups[testgroup].tests) 74178801af7SAndy Fiddaman for prop in TestGroup.props: 74278801af7SAndy Fiddaman if prop not in self.props: 74378801af7SAndy Fiddaman config.set(testgroup, prop, 74478801af7SAndy Fiddaman getattr(self.testgroups[testgroup], prop)) 7459f923083SAlexander Pyhalov 7469f923083SAlexander Pyhalov try: 7479f923083SAlexander Pyhalov with open(options.template, 'w') as f: 7489f923083SAlexander Pyhalov return config.write(f) 7499f923083SAlexander Pyhalov except IOError: 7509f923083SAlexander Pyhalov fail('Could not open \'%s\' for writing.' % options.template) 7519f923083SAlexander Pyhalov 7529f923083SAlexander Pyhalov def complete_outputdirs(self): 7539f923083SAlexander Pyhalov """ 7549f923083SAlexander Pyhalov Collect all the pathnames for Tests, and TestGroups. Work 7559f923083SAlexander Pyhalov backwards one pathname component at a time, to create a unique 7569f923083SAlexander Pyhalov directory name in which to deposit test output. Tests will be able 7579f923083SAlexander Pyhalov to write output files directly in the newly modified outputdir. 7589f923083SAlexander Pyhalov TestGroups will be able to create one subdirectory per test in the 7599f923083SAlexander Pyhalov outputdir, and are guaranteed uniqueness because a group can only 7609f923083SAlexander Pyhalov contain files in one directory. Pre and post tests will create a 7619f923083SAlexander Pyhalov directory rooted at the outputdir of the Test or TestGroup in 7629f923083SAlexander Pyhalov question for their output. 7639f923083SAlexander Pyhalov """ 7649f923083SAlexander Pyhalov done = False 7659f923083SAlexander Pyhalov components = 0 766b0858fdcSAlexander Pyhalov tmp_dict = dict(list(self.tests.items()) + list(self.testgroups.items())) 7679f923083SAlexander Pyhalov total = len(tmp_dict) 7689f923083SAlexander Pyhalov base = self.outputdir 7699f923083SAlexander Pyhalov 7709f923083SAlexander Pyhalov while not done: 7719f923083SAlexander Pyhalov l = [] 7729f923083SAlexander Pyhalov components -= 1 773b0858fdcSAlexander Pyhalov for testfile in list(tmp_dict.keys()): 7749f923083SAlexander Pyhalov uniq = '/'.join(testfile.split('/')[components:]).lstrip('/') 7759f923083SAlexander Pyhalov if uniq not in l: 7769f923083SAlexander Pyhalov l.append(uniq) 7779f923083SAlexander Pyhalov tmp_dict[testfile].outputdir = os.path.join(base, uniq) 7789f923083SAlexander Pyhalov else: 7799f923083SAlexander Pyhalov break 7809f923083SAlexander Pyhalov done = total == len(l) 7819f923083SAlexander Pyhalov 7829f923083SAlexander Pyhalov def setup_logging(self, options): 7839f923083SAlexander Pyhalov """ 7849f923083SAlexander Pyhalov Two loggers are set up here. The first is for the logfile which 7859f923083SAlexander Pyhalov will contain one line summarizing the test, including the test 7869f923083SAlexander Pyhalov name, result, and running time. This logger will also capture the 7879f923083SAlexander Pyhalov timestamped combined stdout and stderr of each run. The second 7889f923083SAlexander Pyhalov logger is optional console output, which will contain only the one 7899f923083SAlexander Pyhalov line summary. The loggers are initialized at two different levels 7909f923083SAlexander Pyhalov to facilitate segregating the output. 7919f923083SAlexander Pyhalov """ 7929f923083SAlexander Pyhalov if options.dryrun is True: 7939f923083SAlexander Pyhalov return 7949f923083SAlexander Pyhalov 7959f923083SAlexander Pyhalov testlogger = logging.getLogger(__name__) 7969f923083SAlexander Pyhalov testlogger.setLevel(logging.DEBUG) 7979f923083SAlexander Pyhalov 79878801af7SAndy Fiddaman if not options.template: 7999f923083SAlexander Pyhalov try: 8009f923083SAlexander Pyhalov old = os.umask(0) 801b0858fdcSAlexander Pyhalov os.makedirs(self.outputdir, mode=0o777) 8029f923083SAlexander Pyhalov os.umask(old) 803b0858fdcSAlexander Pyhalov except OSError as e: 8049f923083SAlexander Pyhalov fail('%s' % e) 8059f923083SAlexander Pyhalov filename = os.path.join(self.outputdir, 'log') 8069f923083SAlexander Pyhalov 8079f923083SAlexander Pyhalov logfile = WatchedFileHandlerClosed(filename) 8089f923083SAlexander Pyhalov logfile.setLevel(logging.DEBUG) 8099f923083SAlexander Pyhalov logfilefmt = logging.Formatter('%(message)s') 8109f923083SAlexander Pyhalov logfile.setFormatter(logfilefmt) 8119f923083SAlexander Pyhalov testlogger.addHandler(logfile) 8129f923083SAlexander Pyhalov 8139f923083SAlexander Pyhalov cons = logging.StreamHandler() 8149f923083SAlexander Pyhalov cons.setLevel(logging.INFO) 8159f923083SAlexander Pyhalov consfmt = logging.Formatter('%(message)s') 8169f923083SAlexander Pyhalov cons.setFormatter(consfmt) 8179f923083SAlexander Pyhalov testlogger.addHandler(cons) 8189f923083SAlexander Pyhalov 8199f923083SAlexander Pyhalov return testlogger 8209f923083SAlexander Pyhalov 8219f923083SAlexander Pyhalov def run(self, options): 8229f923083SAlexander Pyhalov """ 8239f923083SAlexander Pyhalov Walk through all the Tests and TestGroups, calling run(). 8249f923083SAlexander Pyhalov """ 82564ee6612SChris Fraire if not options.dryrun: 8269f923083SAlexander Pyhalov try: 8279f923083SAlexander Pyhalov os.chdir(self.outputdir) 8289f923083SAlexander Pyhalov except OSError: 8299f923083SAlexander Pyhalov fail('Could not change to directory %s' % self.outputdir) 8309f923083SAlexander Pyhalov for test in sorted(self.tests.keys()): 8319f923083SAlexander Pyhalov self.tests[test].run(self.logger, options) 8329f923083SAlexander Pyhalov for testgroup in sorted(self.testgroups.keys()): 8339f923083SAlexander Pyhalov self.testgroups[testgroup].run(self.logger, options) 8349f923083SAlexander Pyhalov 8359f923083SAlexander Pyhalov def summary(self): 8361a2acdcdSAndy Fiddaman if Result.total == 0: 83746593baaSToomas Soome print('No tests to run') 8389f923083SAlexander Pyhalov return 8399f923083SAlexander Pyhalov 840b0858fdcSAlexander Pyhalov print('\nResults Summary') 841b0858fdcSAlexander Pyhalov for key in list(Result.runresults.keys()): 8421a2acdcdSAndy Fiddaman if Result.runresults[key] != 0: 843b0858fdcSAlexander Pyhalov print('%s\t% 4d' % (key, Result.runresults[key])) 8449f923083SAlexander Pyhalov 8459f923083SAlexander Pyhalov m, s = divmod(time() - self.starttime, 60) 8469f923083SAlexander Pyhalov h, m = divmod(m, 60) 847b0858fdcSAlexander Pyhalov print('\nRunning Time:\t%02d:%02d:%02d' % (h, m, s)) 848b0858fdcSAlexander Pyhalov print('Percent passed:\t%.1f%%' % ((float(Result.runresults['PASS']) / 849b0858fdcSAlexander Pyhalov float(Result.total)) * 100)) 850b0858fdcSAlexander Pyhalov print('Log directory:\t%s' % self.outputdir) 8519f923083SAlexander Pyhalov 8529f923083SAlexander Pyhalov 8539f923083SAlexander Pyhalovdef verify_file(pathname): 8549f923083SAlexander Pyhalov """ 8559f923083SAlexander Pyhalov Verify that the supplied pathname is an executable regular file. 8569f923083SAlexander Pyhalov """ 8579f923083SAlexander Pyhalov if os.path.isdir(pathname) or os.path.islink(pathname): 8589f923083SAlexander Pyhalov return False 8599f923083SAlexander Pyhalov 8609f923083SAlexander Pyhalov if os.path.isfile(pathname) and os.access(pathname, os.X_OK): 8619f923083SAlexander Pyhalov return True 8629f923083SAlexander Pyhalov 8639f923083SAlexander Pyhalov return False 8649f923083SAlexander Pyhalov 8659f923083SAlexander Pyhalov 8669f923083SAlexander Pyhalovdef verify_user(user, logger): 8679f923083SAlexander Pyhalov """ 8689f923083SAlexander Pyhalov Verify that the specified user exists on this system, and can execute 8699f923083SAlexander Pyhalov sudo without being prompted for a password. 8709f923083SAlexander Pyhalov """ 8719f923083SAlexander Pyhalov testcmd = [SUDO, '-n', '-u', user, TRUE] 8729f923083SAlexander Pyhalov 8739f923083SAlexander Pyhalov if user in Cmd.verified_users: 8749f923083SAlexander Pyhalov return True 8759f923083SAlexander Pyhalov 8769f923083SAlexander Pyhalov try: 8779f923083SAlexander Pyhalov _ = getpwnam(user) 8789f923083SAlexander Pyhalov except KeyError: 8799f923083SAlexander Pyhalov logger.info("Warning: user '%s' does not exist.", user) 8809f923083SAlexander Pyhalov return False 8819f923083SAlexander Pyhalov 8829f923083SAlexander Pyhalov p = Popen(testcmd) 8839f923083SAlexander Pyhalov p.wait() 8841a2acdcdSAndy Fiddaman if p.returncode != 0: 8859f923083SAlexander Pyhalov logger.info("Warning: user '%s' cannot use passwordless sudo.", user) 8869f923083SAlexander Pyhalov return False 8879f923083SAlexander Pyhalov else: 8889f923083SAlexander Pyhalov Cmd.verified_users.append(user) 8899f923083SAlexander Pyhalov 8909f923083SAlexander Pyhalov return True 8919f923083SAlexander Pyhalov 8929f923083SAlexander Pyhalov 8939f923083SAlexander Pyhalovdef find_tests(testrun, options): 8949f923083SAlexander Pyhalov """ 8959f923083SAlexander Pyhalov For the given list of pathnames, add files as Tests. For directories, 8969f923083SAlexander Pyhalov if do_groups is True, add the directory as a TestGroup. If False, 8979f923083SAlexander Pyhalov recursively search for executable files. 8989f923083SAlexander Pyhalov """ 8999f923083SAlexander Pyhalov 9009f923083SAlexander Pyhalov for p in sorted(options.pathnames): 9019f923083SAlexander Pyhalov if os.path.isdir(p): 9029f923083SAlexander Pyhalov for dirname, _, filenames in os.walk(p): 9039f923083SAlexander Pyhalov if options.do_groups: 9049f923083SAlexander Pyhalov testrun.addtestgroup(dirname, filenames, options) 9059f923083SAlexander Pyhalov else: 9069f923083SAlexander Pyhalov for f in sorted(filenames): 9079f923083SAlexander Pyhalov testrun.addtest(os.path.join(dirname, f), options) 9089f923083SAlexander Pyhalov else: 9099f923083SAlexander Pyhalov testrun.addtest(p, options) 9109f923083SAlexander Pyhalov 9119f923083SAlexander Pyhalov 91278801af7SAndy Fiddamandef filter_tests(testrun, options): 91378801af7SAndy Fiddaman try: 9144947898cSToomas Soome fh = open(options.logfile, "r", errors='replace') 91578801af7SAndy Fiddaman except Exception as e: 91678801af7SAndy Fiddaman fail('%s' % e) 91778801af7SAndy Fiddaman 91878801af7SAndy Fiddaman failed = {} 91978801af7SAndy Fiddaman while True: 92078801af7SAndy Fiddaman line = fh.readline() 92178801af7SAndy Fiddaman if not line: 92278801af7SAndy Fiddaman break 92378801af7SAndy Fiddaman m = re.match(r'Test: (.*)/(\S+).*\[FAIL\]', line) 92478801af7SAndy Fiddaman if not m: 92578801af7SAndy Fiddaman continue 92678801af7SAndy Fiddaman group, test = m.group(1, 2) 927f4d2cf74SToomas Soome m = re.match(re.escape(options.testdir) + r'(.*)', group) 928f4d2cf74SToomas Soome if m: 929f4d2cf74SToomas Soome group = m.group(1) 93078801af7SAndy Fiddaman try: 93178801af7SAndy Fiddaman failed[group].append(test) 93278801af7SAndy Fiddaman except KeyError: 93378801af7SAndy Fiddaman failed[group] = [ test ] 93478801af7SAndy Fiddaman fh.close() 93578801af7SAndy Fiddaman 93678801af7SAndy Fiddaman testrun.filter(failed) 93778801af7SAndy Fiddaman 93878801af7SAndy Fiddaman 9399f923083SAlexander Pyhalovdef fail(retstr, ret=1): 940b0858fdcSAlexander Pyhalov print('%s: %s' % (argv[0], retstr)) 9419f923083SAlexander Pyhalov exit(ret) 9429f923083SAlexander Pyhalov 9439f923083SAlexander Pyhalov 9449f923083SAlexander Pyhalovdef options_cb(option, opt_str, value, parser): 945b8052df9SRyan Moeller path_options = ['outputdir', 'template', 'testdir', 'logfile'] 9469f923083SAlexander Pyhalov 9479f923083SAlexander Pyhalov if opt_str in parser.rargs: 9489f923083SAlexander Pyhalov fail('%s may only be specified once.' % opt_str) 9499f923083SAlexander Pyhalov 950b8052df9SRyan Moeller if option.dest == 'runfiles': 951b8052df9SRyan Moeller parser.values.cmd = 'rdconfig' 952b8052df9SRyan Moeller value = set(os.path.abspath(p) for p in value.split(',')) 953b8052df9SRyan Moeller if option.dest == 'tags': 954b8052df9SRyan Moeller value = [x.strip() for x in value.split(',')] 955b8052df9SRyan Moeller 9569f923083SAlexander Pyhalov if option.dest in path_options: 9579f923083SAlexander Pyhalov setattr(parser.values, option.dest, os.path.abspath(value)) 95878801af7SAndy Fiddaman else: 95978801af7SAndy Fiddaman setattr(parser.values, option.dest, value) 9609f923083SAlexander Pyhalov 9619f923083SAlexander Pyhalov 9629f923083SAlexander Pyhalovdef parse_args(): 9639f923083SAlexander Pyhalov parser = OptionParser() 9649f923083SAlexander Pyhalov parser.add_option('-c', action='callback', callback=options_cb, 965b8052df9SRyan Moeller type='string', dest='runfiles', metavar='runfiles', 966b8052df9SRyan Moeller help='Specify tests to run via config files.') 9679f923083SAlexander Pyhalov parser.add_option('-d', action='store_true', default=False, dest='dryrun', 9689f923083SAlexander Pyhalov help='Dry run. Print tests, but take no other action.') 96978801af7SAndy Fiddaman parser.add_option('-l', action='callback', callback=options_cb, 97078801af7SAndy Fiddaman default=None, dest='logfile', metavar='logfile', 97178801af7SAndy Fiddaman type='string', 97278801af7SAndy Fiddaman help='Read logfile and re-run tests which failed.') 9739f923083SAlexander Pyhalov parser.add_option('-g', action='store_true', default=False, 9749f923083SAlexander Pyhalov dest='do_groups', help='Make directories TestGroups.') 9759f923083SAlexander Pyhalov parser.add_option('-o', action='callback', callback=options_cb, 9769f923083SAlexander Pyhalov default=BASEDIR, dest='outputdir', type='string', 9779f923083SAlexander Pyhalov metavar='outputdir', help='Specify an output directory.') 978b8052df9SRyan Moeller parser.add_option('-i', action='callback', callback=options_cb, 979b8052df9SRyan Moeller default=TESTDIR, dest='testdir', type='string', 980b8052df9SRyan Moeller metavar='testdir', help='Specify a test directory.') 9819f923083SAlexander Pyhalov parser.add_option('-p', action='callback', callback=options_cb, 9829f923083SAlexander Pyhalov default='', dest='pre', metavar='script', 9839f923083SAlexander Pyhalov type='string', help='Specify a pre script.') 9849f923083SAlexander Pyhalov parser.add_option('-P', action='callback', callback=options_cb, 9859f923083SAlexander Pyhalov default='', dest='post', metavar='script', 9869f923083SAlexander Pyhalov type='string', help='Specify a post script.') 9879f923083SAlexander Pyhalov parser.add_option('-q', action='store_true', default=False, dest='quiet', 9889f923083SAlexander Pyhalov help='Silence on the console during a test run.') 9899f923083SAlexander Pyhalov parser.add_option('-t', action='callback', callback=options_cb, default=60, 9909f923083SAlexander Pyhalov dest='timeout', metavar='seconds', type='int', 9919f923083SAlexander Pyhalov help='Timeout (in seconds) for an individual test.') 9929f923083SAlexander Pyhalov parser.add_option('-u', action='callback', callback=options_cb, 9939f923083SAlexander Pyhalov default='', dest='user', metavar='user', type='string', 9949f923083SAlexander Pyhalov help='Specify a different user name to run as.') 9959f923083SAlexander Pyhalov parser.add_option('-w', action='callback', callback=options_cb, 9969f923083SAlexander Pyhalov default=None, dest='template', metavar='template', 9979f923083SAlexander Pyhalov type='string', help='Create a new config file.') 9989f923083SAlexander Pyhalov parser.add_option('-x', action='callback', callback=options_cb, default='', 9999f923083SAlexander Pyhalov dest='pre_user', metavar='pre_user', type='string', 10009f923083SAlexander Pyhalov help='Specify a user to execute the pre script.') 10019f923083SAlexander Pyhalov parser.add_option('-X', action='callback', callback=options_cb, default='', 10029f923083SAlexander Pyhalov dest='post_user', metavar='post_user', type='string', 10039f923083SAlexander Pyhalov help='Specify a user to execute the post script.') 1004b8052df9SRyan Moeller parser.add_option('-T', action='callback', callback=options_cb, default='', 1005b8052df9SRyan Moeller dest='tags', metavar='tags', type='string', 1006b8052df9SRyan Moeller help='Specify tags to execute specific test groups.') 10079f923083SAlexander Pyhalov (options, pathnames) = parser.parse_args() 10089f923083SAlexander Pyhalov 1009b8052df9SRyan Moeller if options.runfiles and len(pathnames): 10109f923083SAlexander Pyhalov fail('Extraneous arguments.') 10119f923083SAlexander Pyhalov 10129f923083SAlexander Pyhalov options.pathnames = [os.path.abspath(path) for path in pathnames] 10139f923083SAlexander Pyhalov 10149f923083SAlexander Pyhalov return options 10159f923083SAlexander Pyhalov 10169f923083SAlexander Pyhalov 10179f923083SAlexander Pyhalovdef main(): 10189f923083SAlexander Pyhalov options = parse_args() 101978801af7SAndy Fiddaman 10209f923083SAlexander Pyhalov testrun = TestRun(options) 10219f923083SAlexander Pyhalov 1022b8052df9SRyan Moeller if options.runfiles: 10239f923083SAlexander Pyhalov testrun.read(testrun.logger, options) 102478801af7SAndy Fiddaman else: 10259f923083SAlexander Pyhalov find_tests(testrun, options) 102678801af7SAndy Fiddaman 102778801af7SAndy Fiddaman if options.logfile: 102878801af7SAndy Fiddaman filter_tests(testrun, options) 102978801af7SAndy Fiddaman 103078801af7SAndy Fiddaman if options.template: 10319f923083SAlexander Pyhalov testrun.write(options) 10329f923083SAlexander Pyhalov exit(0) 10339f923083SAlexander Pyhalov 10349f923083SAlexander Pyhalov testrun.complete_outputdirs() 10359f923083SAlexander Pyhalov testrun.run(options) 10369f923083SAlexander Pyhalov testrun.summary() 10372491fc01SJohn Levon exit(retcode) 10389f923083SAlexander Pyhalov 10399f923083SAlexander Pyhalov 10409f923083SAlexander Pyhalovif __name__ == '__main__': 10419f923083SAlexander Pyhalov main() 1042