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

5
Last change on this file since b7c4753 was b7c4753, checked in by Chris Johns <chrisj@…>, on 04/10/18 at 04:00:09

teater: Filter out an *.norun.* executables.

  • Property mode set to 100644
File size: 14.9 KB
RevLine 
[50fdf12]1#
2# RTEMS Tools Project (http://www.rtems.org/)
[b7c4753]3# Copyright 2013-2018 Chris Johns (chrisj@rtems.org)
[50fdf12]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
[b0fa2ae]31from __future__ import print_function
32
[50fdf12]33import copy
34import datetime
[b0fa2ae]35import fnmatch
[50fdf12]36import os
[cec5878]37import re
[50fdf12]38import sys
39import threading
40import time
41
[bf58911]42from rtemstoolkit import configuration
[50fdf12]43from rtemstoolkit import error
[c68beb8]44from rtemstoolkit import host
[50fdf12]45from rtemstoolkit import log
46from rtemstoolkit import path
[89e8c2a]47from rtemstoolkit import mailer
[3a867a4]48from rtemstoolkit import reraise
[c04a849]49from rtemstoolkit import stacktraces
[efc4f09]50from rtemstoolkit import version
[50fdf12]51
[b0fa2ae]52from . import bsps
53from . import config
54from . import console
55from . import options
56from . import report
[50fdf12]57
[89e8c2a]58class log_capture(object):
59    def __init__(self):
60        self.log = []
61        log.capture = self.capture
62
63    def __str__(self):
64        return os.linesep.join(self.log)
65
66    def capture(self, text):
67        self.log += [l for l in text.replace(chr(13), '').splitlines()]
68
69    def get(self):
[cec5878]70        s = []
71        status = []
72        status_regx = re.compile('^\[\s*\d+/\s*\d+\] p:.+')
73        for l in self.log:
74            if status_regx.match(l):
75                status += [l]
76            else:
77                if len(status) == 1:
78                    s += [status[0]]
79                    status = []
80                elif len(status) > 1:
81                    s += [status[0]]
82                    if len(status) > 2:
[bb218b6]83                        s += [' <<skipping passes>>']
[cec5878]84                    s += [status[-1]]
85                    status = []
86                s += [l]
87        return s
[89e8c2a]88
[50fdf12]89class test(object):
90    def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
91        self.index = index
92        self.total = total
93        self.report = report
94        self.bsp = bsp
95        self.bsp_config = bsp_config
96        self.opts = copy.copy(opts)
97        self.opts.defaults['test_index'] = str(index)
98        self.opts.defaults['test_total'] = str(total)
99        self.opts.defaults['bsp'] = bsp
[bf58911]100        self.opts.defaults['bsp_arch'] = '%{arch}'
[50fdf12]101        if not path.isfile(executable):
102            raise error.general('cannot find executable: %s' % (executable))
103        self.opts.defaults['test_executable'] = executable
104        if rtems_tools:
[9600c39]105            rtems_tools_bin = path.join(self.opts.defaults.expand(rtems_tools), 'bin')
[50fdf12]106            if not path.isdir(rtems_tools_bin):
107                raise error.general('cannot find RTEMS tools path: %s' % (rtems_tools_bin))
108            self.opts.defaults['rtems_tools'] = rtems_tools_bin
[662e1f7]109        self.config = config.file(index, total, self.report, self.bsp_config, self.opts)
[c04a849]110
111    def run(self):
112        if self.config:
113            self.config.run()
114
115    def kill(self):
116        if self.config:
117            self.config.kill()
[50fdf12]118
119class test_run(object):
120    def __init__(self, index, total, report, executable, rtems_tools, bsp, bsp_config, opts):
121        self.test = None
122        self.result = None
123        self.start_time = None
124        self.end_time = None
125        self.index = copy.copy(index)
126        self.total = total
127        self.report = report
128        self.executable = copy.copy(executable)
129        self.rtems_tools = rtems_tools
130        self.bsp = bsp
131        self.bsp_config = bsp_config
132        self.opts = opts
133
134    def runner(self):
135        self.start_time = datetime.datetime.now()
136        try:
137            self.test = test(self.index, self.total, self.report,
138                             self.executable, self.rtems_tools,
139                             self.bsp, self.bsp_config,
140                             self.opts)
[c04a849]141            self.test.run()
[50fdf12]142        except KeyboardInterrupt:
143            pass
144        except:
145            self.result = sys.exc_info()
146        self.end_time = datetime.datetime.now()
147
148    def run(self):
149        self.thread = threading.Thread(target = self.runner,
150                                       name = 'test[%s]' % path.basename(self.executable))
151        self.thread.start()
152
153    def is_alive(self):
154        return self.thread and self.thread.is_alive()
155
156    def reraise(self):
157        if self.result is not None:
[3a867a4]158            reraise.reraise(*self.result)
[50fdf12]159
[c04a849]160    def kill(self):
161        if self.test:
162            self.test.kill()
163
[803b63d]164def find_executables(paths, glob):
[50fdf12]165    executables = []
166    for p in paths:
167        if path.isfile(p):
168            executables += [p]
169        elif path.isdir(p):
170            for root, dirs, files in os.walk(p, followlinks = True):
171                for f in files:
[803b63d]172                    if fnmatch.fnmatch(f.lower(), glob):
[50fdf12]173                        executables += [path.join(root, f)]
[b7c4753]174    norun = re.compile('.*\.norun.*')
175    executables = [e for e in executables if not norun.match(e)]
[50fdf12]176    return sorted(executables)
177
178def report_finished(reports, report_mode, reporting, finished, job_trace):
179    processing = True
180    while processing:
181        processing = False
182        reported = []
183        for tst in finished:
184            if tst not in reported and \
185                    (reporting < 0 or tst.index == reporting):
186                if job_trace:
187                    log.notice('}} %*d: %s: %s (%d)' % (len(str(tst.total)), tst.index,
188                                                        path.basename(tst.executable),
189                                                        'reporting',
190                                                        reporting))
191                processing = True
192                reports.log(tst.executable, report_mode)
193                reported += [tst]
194                reporting += 1
195        finished[:] = [t for t in finished if t not in reported]
196        if len(reported):
197            del reported[:]
198            if job_trace:
[04a5204]199                print('}} threading:', threading.active_count())
[50fdf12]200                for t in threading.enumerate():
[04a5204]201                    print('}} ', t.name)
[50fdf12]202    return reporting
203
204def _job_trace(tst, msg, total, exe, active, reporting):
205    s = ''
206    for a in active:
207        s += ' %d:%s' % (a.index, path.basename(a.executable))
208    log.notice('}} %*d: %s: %s (%d %d %d%s)' % (len(str(tst.total)), tst.index,
209                                                path.basename(tst.executable),
210                                                msg,
211                                                reporting, total, exe, s))
212
[c04a849]213def killall(tests):
214    for test in tests:
215        test.kill()
216
[50fdf12]217def run(command_path = None):
218    import sys
[c04a849]219    tests = []
[50fdf12]220    stdtty = console.save()
221    opts = None
[803b63d]222    default_exefilter = '*.exe'
[50fdf12]223    try:
224        optargs = { '--rtems-tools': 'The path to the RTEMS tools',
225                    '--rtems-bsp':   'The RTEMS BSP to run the test on',
[bf58911]226                    '--user-config': 'Path to your local user configuration INI file',
[50fdf12]227                    '--report-mode': 'Reporting modes, failures (default),all,none',
228                    '--list-bsps':   'List the supported BSPs',
229                    '--debug-trace': 'Debug trace based on specific flags',
[803b63d]230                    '--filter':      'Glob that executables must match to run (default: ' +
231                              default_exefilter + ')',
[50fdf12]232                    '--stacktrace':  'Dump a stack trace on a user termination (^C)' }
[89e8c2a]233        mailer.append_options(optargs)
[50fdf12]234        opts = options.load(sys.argv,
235                            optargs = optargs,
236                            command_path = command_path)
[89e8c2a]237        mail = None
238        output = None
239        if opts.find_arg('--mail'):
240            mail = mailer.mail(opts)
[c73e030]241            # Request these now to generate any errors.
242            from_addr = mail.from_address()
243            smtp_host = mail.smtp_host()
244            to_addr = opts.find_arg('--mail-to')
245            if to_addr:
246                to_addr = to_addr[1]
247            else:
248                to_addr = 'build@rtems.org'
[89e8c2a]249            output = log_capture()
[efc4f09]250        log.notice('RTEMS Testing - Tester, %s' % (version.str()))
[50fdf12]251        if opts.find_arg('--list-bsps'):
[c04a849]252            bsps.list(opts)
[803b63d]253        exe_filter = opts.find_arg('--filter')
254        if exe_filter:
255            exe_filter = exe_filter[1]
256        else:
257            exe_filter = default_exefilter
[50fdf12]258        opts.log_info()
[c68beb8]259        log.output('Host: ' + host.label(mode = 'all'))
[50fdf12]260        debug_trace = opts.find_arg('--debug-trace')
261        if debug_trace:
[6f8b07c]262            if len(debug_trace) != 1:
263                debug_trace = debug_trace[1]
264            else:
265                raise error.general('no debug flags, can be: console,gdb,output')
[50fdf12]266        else:
267            debug_trace = ''
[c68beb8]268        opts.defaults['exe_trace'] = debug_trace
[50fdf12]269        job_trace = 'jobs' in debug_trace.split(',')
270        rtems_tools = opts.find_arg('--rtems-tools')
271        if rtems_tools:
[9600c39]272            if len(rtems_tools) != 2:
273                raise error.general('invalid RTEMS tools option')
[50fdf12]274            rtems_tools = rtems_tools[1]
[9600c39]275        else:
276            rtems_tools = '%{_prefix}'
[50fdf12]277        bsp = opts.find_arg('--rtems-bsp')
[9600c39]278        if bsp is None or len(bsp) != 2:
[bf58911]279            raise error.general('RTEMS BSP not provided or an invalid option')
[42e39d2]280        bsp = config.load(bsp[1], opts)
[bf58911]281        bsp_config = opts.defaults.expand(opts.defaults['tester'])
[50fdf12]282        report_mode = opts.find_arg('--report-mode')
283        if report_mode:
284            if report_mode[1] != 'failures' and \
285                    report_mode[1] != 'all' and \
286                    report_mode[1] != 'none':
287                raise error.general('invalid report mode')
288            report_mode = report_mode[1]
289        else:
290            report_mode = 'failures'
[803b63d]291        executables = find_executables(opts.params(), exe_filter)
[50fdf12]292        if len(executables) == 0:
[e4014f0]293            raise error.general('no executables supplied')
[50fdf12]294        start_time = datetime.datetime.now()
295        total = len(executables)
296        reports = report.report(total)
297        reporting = 1
298        jobs = int(opts.jobs(opts.defaults['_ncpus']))
299        exe = 0
300        finished = []
301        if jobs > len(executables):
302            jobs = len(executables)
303        while exe < total or len(tests) > 0:
304            if exe < total and len(tests) < jobs:
305                tst = test_run(exe + 1, total, reports,
306                               executables[exe],
307                               rtems_tools, bsp, bsp_config,
308                               opts)
309                exe += 1
310                tests += [tst]
311                if job_trace:
312                    _job_trace(tst, 'create',
313                               total, exe, tests, reporting)
314                tst.run()
315            else:
316                dead = [t for t in tests if not t.is_alive()]
317                tests[:] = [t for t in tests if t not in dead]
318                for tst in dead:
319                    if job_trace:
320                        _job_trace(tst, 'dead',
321                                   total, exe, tests, reporting)
322                    finished += [tst]
323                    tst.reraise()
324                del dead
325                if len(tests) >= jobs or exe >= total:
326                    time.sleep(0.250)
327                if len(finished):
328                    reporting = report_finished(reports,
329                                                report_mode,
330                                                reporting,
331                                                finished,
332                                                job_trace)
333        finished_time = datetime.datetime.now()
334        reporting = report_finished(reports, report_mode,
335                                    reporting, finished, job_trace)
336        if reporting < total:
337            log.warning('finished jobs does match: %d' % (reporting))
338            report_finished(reports, report_mode, -1, finished, job_trace)
339        reports.summary()
340        end_time = datetime.datetime.now()
[89e8c2a]341        average_time = 'Average test time: %s' % (str((end_time - start_time) / total))
342        total_time = 'Testing time     : %s' % (str(end_time - start_time))
343        log.notice(average_time)
344        log.notice(total_time)
345        if mail is not None and output is not None:
[24d51f7]346            m_arch = opts.defaults.expand('%{arch}')
347            m_bsp = opts.defaults.expand('%{bsp}')
[30218f5]348            build = ' %s:' % (reports.get_config('build', not_found = ''))
349            subject = '[rtems-test] %s/%s:%s %s' % (m_arch,
350                                                    m_bsp,
351                                                    build,
352                                                    reports.score_card('short'))
353            np = 'Not present in test'
354            ver = reports.get_config('version', not_found = np)
355            build = reports.get_config('build', not_found = np)
356            tools = reports.get_config('tools', not_found = np)
[89e8c2a]357            body = [total_time, average_time,
[c68beb8]358                    '', 'Host', '====', host.label(mode = 'all'),
[30218f5]359                    '', 'Configuration', '=============',
360                    'Version: %s' % (ver),
361                    'Build  : %s' % (build),
362                    'Tools  : %s' % (tools),
[89e8c2a]363                    '', 'Summary', '=======', '',
364                    reports.score_card(), '',
365                    reports.failures(),
[30218f5]366                    'Log', '===', ''] + output.get()
[89e8c2a]367            mail.send(to_addr, subject, os.linesep.join(body))
368
[04a5204]369    except error.general as gerr:
370        print(gerr)
[50fdf12]371        sys.exit(1)
[04a5204]372    except error.internal as ierr:
373        print(ierr)
[50fdf12]374        sys.exit(1)
[04a5204]375    except error.exit:
[50fdf12]376        sys.exit(2)
377    except KeyboardInterrupt:
[b7d48ef]378        if opts is not None and opts.find_arg('--stacktrace'):
[04a5204]379            print('}} dumping:', threading.active_count())
[50fdf12]380            for t in threading.enumerate():
[04a5204]381                print('}} ', t.name)
382            print(stacktraces.trace())
[50fdf12]383        log.notice('abort: user terminated')
[c04a849]384        killall(tests)
[50fdf12]385        sys.exit(1)
386    finally:
387        console.restore(stdtty)
388    sys.exit(0)
389
390if __name__ == "__main__":
391    run()
Note: See TracBrowser for help on using the repository browser.