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