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