source: rtems-tools/tester/rt/test.py @ c04a849

4.104.115
Last change on this file since c04a849 was c04a849, checked in by Chris Johns <chrisj@…>, on 05/31/14 at 10:03:05

tester: Correctly handle contro-c.

Add support to kill running tests if the user presses control-c.

  • Property mode set to 100644
File size: 12.3 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2013-2014 Chris Johns (chrisj@rtems.org)
4# All rights reserved.
5#
6# This file is part of the RTEMS Tools package in 'rtems-tools'.
7#
8# Redistribution and use in source and binary forms, with or without
9# modification, are permitted provided that the following conditions are met:
10#
11# 1. Redistributions of source code must retain the above copyright notice,
12# this list of conditions and the following disclaimer.
13#
14# 2. Redistributions in binary form must reproduce the above copyright notice,
15# this list of conditions and the following disclaimer in the documentation
16# and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28# POSSIBILITY OF SUCH DAMAGE.
29#
30
31import copy
32import datetime
33import os
34import sys
35import threading
36import time
37
38from rtemstoolkit import error
39from rtemstoolkit import log
40from rtemstoolkit import path
41from rtemstoolkit import stacktraces
42
43import bsps
44import config
45import console
46import options
47import report
48import version
49import fnmatch
50
51class test(object):
52    def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
53        self.index = index
54        self.total = total
55        self.report = report
56        self.bsp = bsp
57        self.bsp_config = bsp_config
58        self.opts = copy.copy(opts)
59        self.opts.defaults['test_index'] = str(index)
60        self.opts.defaults['test_total'] = str(total)
61        self.opts.defaults['bsp'] = bsp
62        self.opts.defaults['bsp_arch'] = '%%{%s_arch}' % (bsp)
63        self.opts.defaults['bsp_opts'] = '%%{%s_opts}' % (bsp)
64        if not path.isfile(executable):
65            raise error.general('cannot find executable: %s' % (executable))
66        self.opts.defaults['test_executable'] = executable
67        if rtems_tools:
68            rtems_tools_bin = path.join(rtems_tools, 'bin')
69            if not path.isdir(rtems_tools_bin):
70                raise error.general('cannot find RTEMS tools path: %s' % (rtems_tools_bin))
71            self.opts.defaults['rtems_tools'] = rtems_tools_bin
72        self.config = config.file(self.report, self.bsp_config, self.opts)
73
74    def run(self):
75        if self.config:
76            self.config.run()
77
78    def kill(self):
79        if self.config:
80            self.config.kill()
81
82class test_run(object):
83    def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
84        self.test = None
85        self.result = None
86        self.start_time = None
87        self.end_time = None
88        self.index = copy.copy(index)
89        self.total = total
90        self.report = report
91        self.executable = copy.copy(executable)
92        self.rtems_tools = rtems_tools
93        self.bsp = bsp
94        self.bsp_config = bsp_config
95        self.opts = opts
96
97    def runner(self):
98        self.start_time = datetime.datetime.now()
99        try:
100            self.test = test(self.index, self.total, self.report,
101                             self.executable, self.rtems_tools,
102                             self.bsp, self.bsp_config,
103                             self.opts)
104            self.test.run()
105        except KeyboardInterrupt:
106            pass
107        except:
108            self.result = sys.exc_info()
109        self.end_time = datetime.datetime.now()
110
111    def run(self):
112        self.thread = threading.Thread(target = self.runner,
113                                       name = 'test[%s]' % path.basename(self.executable))
114        self.thread.start()
115
116    def is_alive(self):
117        return self.thread and self.thread.is_alive()
118
119    def reraise(self):
120        if self.result is not None:
121            raise self.result[0], self.result[1], self.result[2]
122
123    def kill(self):
124        if self.test:
125            self.test.kill()
126
127def find_executables(paths, glob):
128    executables = []
129    for p in paths:
130        if path.isfile(p):
131            executables += [p]
132        elif path.isdir(p):
133            for root, dirs, files in os.walk(p, followlinks = True):
134                for f in files:
135                    if fnmatch.fnmatch(f.lower(), glob):
136                        executables += [path.join(root, f)]
137    return sorted(executables)
138
139def report_finished(reports, report_mode, reporting, finished, job_trace):
140    processing = True
141    while processing:
142        processing = False
143        reported = []
144        for tst in finished:
145            if tst not in reported and \
146                    (reporting < 0 or tst.index == reporting):
147                if job_trace:
148                    log.notice('}} %*d: %s: %s (%d)' % (len(str(tst.total)), tst.index,
149                                                        path.basename(tst.executable),
150                                                        'reporting',
151                                                        reporting))
152                processing = True
153                reports.log(tst.executable, report_mode)
154                reported += [tst]
155                reporting += 1
156        finished[:] = [t for t in finished if t not in reported]
157        if len(reported):
158            del reported[:]
159            if job_trace:
160                print '}} threading:', threading.active_count()
161                for t in threading.enumerate():
162                    print '}} ', t.name
163    return reporting
164
165def _job_trace(tst, msg, total, exe, active, reporting):
166    s = ''
167    for a in active:
168        s += ' %d:%s' % (a.index, path.basename(a.executable))
169    log.notice('}} %*d: %s: %s (%d %d %d%s)' % (len(str(tst.total)), tst.index,
170                                                path.basename(tst.executable),
171                                                msg,
172                                                reporting, total, exe, s))
173
174def list_bsps(opts):
175    path_ = opts.defaults.expand('%%{_configdir}/bsps/*.mc')
176    bsps = path.collect_files(path_)
177    log.notice(' BSP List:')
178    for bsp in bsps:
179        log.notice('  %s' % (path.basename(bsp[:-3])))
180    raise error.exit()
181
182def killall(tests):
183    for test in tests:
184        test.kill()
185
186def run(command_path = None):
187    import sys
188    tests = []
189    stdtty = console.save()
190    opts = None
191    default_exefilter = '*.exe'
192    try:
193        optargs = { '--rtems-tools': 'The path to the RTEMS tools',
194                    '--rtems-bsp':   'The RTEMS BSP to run the test on',
195                    '--report-mode': 'Reporting modes, failures (default),all,none',
196                    '--list-bsps':   'List the supported BSPs',
197                    '--debug-trace': 'Debug trace based on specific flags',
198                    '--filter':      'Glob that executables must match to run (default: ' +
199                              default_exefilter + ')',
200                    '--stacktrace':  'Dump a stack trace on a user termination (^C)' }
201        opts = options.load(sys.argv,
202                            optargs = optargs,
203                            command_path = command_path)
204        log.notice('RTEMS Testing - Tester, v%s' % (version.str()))
205        if opts.find_arg('--list-bsps'):
206            bsps.list(opts)
207        exe_filter = opts.find_arg('--filter')
208        if exe_filter:
209            exe_filter = exe_filter[1]
210        else:
211            exe_filter = default_exefilter
212        opts.log_info()
213        debug_trace = opts.find_arg('--debug-trace')
214        if debug_trace:
215            debug_trace = debug_trace[1]
216        else:
217            debug_trace = ''
218        opts.defaults['debug_trace'] = debug_trace
219        job_trace = 'jobs' in debug_trace.split(',')
220        rtems_tools = opts.find_arg('--rtems-tools')
221        if rtems_tools:
222            rtems_tools = rtems_tools[1]
223        bsp = opts.find_arg('--rtems-bsp')
224        if bsp is None:
225            raise error.general('no RTEMS BSP provided')
226        opts.defaults.load('%%{_configdir}/bsps/%s.mc' % (bsp[1]))
227        bsp = opts.defaults.get('%{bsp}')
228        if not bsp:
229            raise error.general('BSP definition (%{bsp}) not found in the global map')
230        bsp = bsp[2]
231        if not opts.defaults.set_read_map(bsp):
232            raise error.general('no BSP map found')
233        bsp_script = opts.defaults.get(bsp)
234        if not bsp_script:
235            raise error.general('BSP script not found: %s' % (bsp))
236        bsp_config = opts.defaults.expand(opts.defaults[bsp])
237        report_mode = opts.find_arg('--report-mode')
238        if report_mode:
239            if report_mode[1] != 'failures' and \
240                    report_mode[1] != 'all' and \
241                    report_mode[1] != 'none':
242                raise error.general('invalid report mode')
243            report_mode = report_mode[1]
244        else:
245            report_mode = 'failures'
246        executables = find_executables(opts.params(), exe_filter)
247        if len(executables) == 0:
248            raise error.general('no executbles supplied')
249        start_time = datetime.datetime.now()
250        total = len(executables)
251        reports = report.report(total)
252        invalid_tests = opts.defaults['invalid_tests']
253        if invalid_tests:
254            reports.set_invalid_tests([l.strip() for l in invalid_tests.splitlines()])
255        reporting = 1
256        jobs = int(opts.jobs(opts.defaults['_ncpus']))
257        exe = 0
258        finished = []
259        if jobs > len(executables):
260            jobs = len(executables)
261        while exe < total or len(tests) > 0:
262            if exe < total and len(tests) < jobs:
263                tst = test_run(exe + 1, total, reports,
264                               executables[exe],
265                               rtems_tools, bsp, bsp_config,
266                               opts)
267                exe += 1
268                tests += [tst]
269                if job_trace:
270                    _job_trace(tst, 'create',
271                               total, exe, tests, reporting)
272                tst.run()
273            else:
274                dead = [t for t in tests if not t.is_alive()]
275                tests[:] = [t for t in tests if t not in dead]
276                for tst in dead:
277                    if job_trace:
278                        _job_trace(tst, 'dead',
279                                   total, exe, tests, reporting)
280                    finished += [tst]
281                    tst.reraise()
282                del dead
283                if len(tests) >= jobs or exe >= total:
284                    time.sleep(0.250)
285                if len(finished):
286                    reporting = report_finished(reports,
287                                                report_mode,
288                                                reporting,
289                                                finished,
290                                                job_trace)
291        finished_time = datetime.datetime.now()
292        reporting = report_finished(reports, report_mode,
293                                    reporting, finished, job_trace)
294        if reporting < total:
295            log.warning('finished jobs does match: %d' % (reporting))
296            report_finished(reports, report_mode, -1, finished, job_trace)
297        reports.summary()
298        end_time = datetime.datetime.now()
299        log.notice('Average test time: %s' % (str((end_time - start_time) / total)))
300        log.notice('Testing time     : %s' % (str(end_time - start_time)))
301    except error.general, gerr:
302        print gerr
303        sys.exit(1)
304    except error.internal, ierr:
305        print ierr
306        sys.exit(1)
307    except error.exit, eerr:
308        sys.exit(2)
309    except KeyboardInterrupt:
310        if opts.find_arg('--stacktrace'):
311            print '}} dumping:', threading.active_count()
312            for t in threading.enumerate():
313                print '}} ', t.name
314            print stacktraces.trace()
315        log.notice('abort: user terminated')
316        killall(tests)
317        sys.exit(1)
318    finally:
319        console.restore(stdtty)
320    sys.exit(0)
321
322if __name__ == "__main__":
323    run()
Note: See TracBrowser for help on using the repository browser.