source: rtems-tools/tester/rt/config.py @ a4fbd16

Last change on this file since a4fbd16 was a4fbd16, checked in by Chris Johns <chrisj@…>, on Nov 26, 2018 at 3:38:37 AM

tester: Fix the handling of the version and tools recs when running parallel jobs

  • Property mode set to 100644
File size: 18.5 KB
Line 
1#
2# RTEMS Tools Project (http://www.rtems.org/)
3# Copyright 2013-2017 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
31#
32# RTEMS Testing Config
33#
34
35from __future__ import print_function
36
37import datetime
38import os
39import re
40import threading
41
42from rtemstoolkit import configuration
43from rtemstoolkit import config
44from rtemstoolkit import error
45from rtemstoolkit import execute
46from rtemstoolkit import log
47from rtemstoolkit import path
48
49import console
50import gdb
51import tftp
52
53timeout = 15
54
55class file(config.file):
56    """RTEMS Testing configuration."""
57
58    _directives = ['%execute',
59                   '%gdb',
60                   '%tftp',
61                   '%console']
62
63    def __init__(self, index, total, report, name, opts,
64                 console_prefix = ']', _directives = _directives):
65        super(file, self).__init__(name, opts, directives = _directives)
66        self.lock = threading.Lock()
67        self.realtime_trace = self.exe_trace('output')
68        self.console_trace = self.exe_trace('console')
69        self.console_prefix = console_prefix
70        self.show_header = not self.defined('test_disable_header')
71        self.process = None
72        self.console = None
73        self.output = None
74        self.index = index
75        self.total = total
76        self.report = report
77        self.name = name
78        self.timedout = False
79        self.test_started = False
80        self.kill_good = False
81        self.kill_on_end = False
82        self.test_label = None
83        self.target_reset_regx = None
84        self.target_start_regx = None
85
86    def __del__(self):
87        if self.console:
88            del self.console
89        super(file, self).__del__()
90
91    def _lock(self):
92        self.lock.acquire()
93
94    def _unlock(self):
95        self.lock.release()
96
97    def _timeout(self):
98        self._lock()
99        self.timedout = True
100        self._unlock()
101        self.capture('*** TIMEOUT TIMEOUT')
102
103    def _ok_kill(self):
104        self._lock()
105        self.kill_good = True
106        self._unlock()
107        try:
108            self.process.kill()
109        except:
110            pass
111
112    def _target_regex(self, label):
113        regex = None
114        if self.defined(label):
115            r = self.expand('%%{%s}' % (label))
116            try:
117                regex = re.compile(r, re.MULTILINE)
118            except:
119                msg = 'invalid target regex: %s: %s' % (label, r)
120                raise error.general(msg)
121        return regex
122
123    def _target_command(self, command, bsp_arch = None, bsp = None, exe = None, fexe = None):
124        if self.defined('target_%s_command' % (command)):
125            cmd = self.expand('%%{target_%s_command}' % (command)).strip()
126            if bsp_arch is not None and '@ARCH@' in cmd:
127                cmd = cmd.replace('@ARCH@', bsp_arch)
128            if bsp is not None and '@BSP@' in cmd:
129                cmd = cmd.replace('@BSP@', bsp)
130            if exe is not None and '@EXE@' in cmd:
131                cmd = cmd.replace('@EXE@', exe)
132            if fexe is not None and '@FEXE@' in cmd:
133                cmd = cmd.replace('@FEXE@', exe)
134            if len(cmd) > 0:
135                output = ''
136                if not self.opts.dry_run():
137                    rs_proc = execute.capture_execution()
138                    ec, proc, output = rs_proc.open(cmd, shell = True)
139                self._capture_console('target %s: %s' % (command, cmd))
140                if len(output) > 0:
141                    output = os.linesep.join([' ' + l for l in output.splitlines()])
142                    self._capture_console(output)
143
144    def _target_exe_filter(self, exe):
145        if self.defined('target_exe_filter'):
146            f = self.expand('%{target_exe_filter}').strip()
147            # Be like sed and use the first character as the delmiter.
148            if len(f) > 0:
149                delimiter = f[0]
150                pat = ''
151                repl = ''
152                repl_not_pat = False
153                esc = False
154                for c in f[1:]:
155                    add = True
156                    if not esc and c == '\\':
157                        esc = True
158                        add = False
159                    elif esc:
160                        if c == delimiter:
161                            c = delimiter
162                        else:
163                            c = '\\' + c
164                            esc = False
165                    elif c == delimiter:
166                        if repl_not_pat:
167                            exe = re.sub(r'%s' % (pat), repl, exe)
168                            self._capture_console('target exe filter: find:%s subst:%s -> %s' % \
169                                                  (pat, repl, exe))
170                            return exe
171                        repl_not_pat = True
172                        add = False
173                    if add:
174                        if repl_not_pat:
175                            repl += c
176                        else:
177                            pat += c
178                raise error.general('invalid exe filter: %s' % (f))
179
180    def _output_length(self):
181        self._lock()
182        if self.test_started:
183            l = len(self.output)
184            self._unlock()
185            return l
186        self._unlock()
187        return 0
188
189    def _capture_console(self, text):
190        text = [('=> ', l) for l in text.replace(chr(13), '').splitlines()]
191        if self.output is not None:
192            if self.console_trace:
193                self._realtime_trace(text)
194            self.output += text
195
196    def _dir_console(self, data):
197        if self.console is not None:
198            raise error.general(self._name_line_msg('console already configured'))
199        if len(data) == 0:
200            raise error.general(self._name_line_msg('no console configuration provided'))
201        console_trace = trace = self.exe_trace('console')
202        if not self.opts.dry_run():
203            if data[0] == 'stdio':
204                self.console = console.stdio(trace = console_trace)
205            elif data[0] == 'tty':
206                if len(data) < 2 or len(data) >3:
207                    raise error.general(self._name_line_msg('no tty configuration provided'))
208                if len(data) == 3:
209                    settings = data[2]
210                else:
211                    settings = None
212                self.console = console.tty(data[1],
213                                           output = self.capture,
214                                           setup = settings,
215                                           trace = console_trace)
216            else:
217                raise error.general(self._name_line_msg('invalid console type'))
218
219    def _dir_execute(self, data, total, index, exe, bsp_arch, bsp):
220        self.process = execute.execute(output = self.capture)
221        if self.console:
222            self.console.open()
223        if not self.in_error:
224            self.capture_console('run: %s' % (' '.join(data)))
225            if not self.opts.dry_run():
226                ec, proc = self.process.open(data,
227                                             timeout = (int(self.expand('%{timeout}')),
228                                                        self._timeout))
229                self._lock()
230                if not self.kill_good and ec > 0:
231                    self._error('execute failed: %s: exit-code:%d' % (' '.join(data), ec))
232                elif self.timedout:
233                    self.process.kill()
234                self._unlock()
235            if self.console:
236                self.console.close()
237
238    def _dir_gdb(self, data, total, index, exe, bsp_arch, bsp):
239        if len(data) < 3 or len(data) > 4:
240            raise error.general('invalid %gdb arguments')
241        self.process = gdb.gdb(bsp_arch, bsp,
242                               trace = self.exe_trace('gdb'),
243                               mi_trace = self.exe_trace('gdb-mi'))
244        script = self.expand('%%{%s}' % data[2])
245        if script:
246            script = [l.strip() for l in script.splitlines()]
247        if not self.in_error:
248            if self.console:
249                self.console.open()
250            if not self.opts.dry_run():
251                self.process.open(data[0], data[1],
252                                  script = script,
253                                  output = self.capture,
254                                  gdb_console = self.capture_console,
255                                  timeout = int(self.expand('%{timeout}')))
256            if self.console:
257                self.console.close()
258
259    def _dir_tftp(self, data, total, index, exe, bsp_arch, bsp):
260        if len(data) != 2:
261            raise error.general('invalid %tftp arguments')
262        try:
263            port = int(data[1])
264        except:
265            raise error.general('invalid %tftp port')
266        self.kill_on_end = True
267        if not self.opts.dry_run():
268            self.process = tftp.tftp(bsp_arch, bsp,
269                                     trace = self.exe_trace('tftp'))
270            if not self.in_error:
271                if self.console:
272                    self.console.open()
273                self.process.open(executable = exe,
274                                  port = port,
275                                  output_length = self._output_length,
276                                  console = self.capture_console,
277                                  timeout = (int(self.expand('%{timeout}')),
278                                             self._timeout))
279                if self.console:
280                    self.console.close()
281
282    def _directive_filter(self, results, directive, info, data):
283        if results[0] == 'directive':
284            _directive = results[1]
285            _data = results[2]
286            ds = []
287            if len(_data):
288                ds = [_data[0]]
289                if len(_data) > 1:
290                    ds += _data[1].split()
291            ds = self.expand(ds)
292
293            if _directive == '%console':
294                self._dir_console(ds)
295            else:
296                self._lock()
297                try:
298                    self.output = []
299                    total = int(self.expand('%{test_total}'))
300                    index = int(self.expand('%{test_index}'))
301                    exe = self.expand('%{test_executable}')
302                    bsp_arch = self.expand('%{arch}')
303                    bsp = self.expand('%{bsp}')
304                    fexe = self._target_exe_filter(exe)
305                    self.report.start(index, total, exe, fexe,
306                                      bsp_arch, bsp, self.show_header)
307                    if self.index == 1:
308                        self._target_command('on', bsp_arch, bsp, exe, fexe)
309                    self._target_command('pretest', bsp_arch, bsp, exe, fexe)
310                finally:
311                    self._unlock()
312                if _directive == '%execute':
313                    self._dir_execute(ds, total, index, fexe, bsp_arch, bsp)
314                elif _directive == '%gdb':
315                    self._dir_gdb(ds, total, index, fexe, bsp_arch, bsp)
316                elif _directive == '%tftp':
317                    self._dir_tftp(ds, total, index, fexe, bsp_arch, bsp)
318                else:
319                    raise error.general(self._name_line_msg('invalid directive'))
320                self._lock()
321                if self.index == self.total:
322                    self._target_command('off', bsp_arch, bsp, exe, fexe)
323                self._target_command('posttest', bsp_arch, bsp, exe, fexe)
324                try:
325                    status = self.report.end(exe, self.output, self.console_prefix)
326                    version = self.report.get_config('version', not_found = 'n/p')
327                    build = self.report.get_config('build', not_found = 'n/p')
328                    tools = self.report.get_config('tools', not_found = 'n/p')
329                    self._capture_console('test result: %s' % (status))
330                    self._capture_console('test version: %s' % (version))
331                    self._capture_console('test build: %s' % (build))
332                    self._capture_console('test tools: %s' % (tools))
333                    if status == 'timeout':
334                        if self.index != self.total:
335                            self._target_command('reset', bsp_arch, bsp, exe, fexe)
336                    self.process = None
337                    self.output = None
338                finally:
339                    self._unlock()
340        return None, None, None
341
342    def _realtime_trace(self, text):
343        for l in text:
344            print(''.join(l))
345
346    def run(self):
347        self.target_start_regx = self._target_regex('target_start_regex')
348        self.target_reset_regx = self._target_regex('target_reset_regex')
349        self.load(self.name)
350
351    def capture(self, text):
352        self._lock()
353        try:
354            if not self.test_started:
355                s = text.find('*** BEGIN OF TEST ')
356                if s >= 0:
357                    self.test_started = True
358                    e = text[s + 3:].find('***')
359                    if e >= 0:
360                        self.test_label = text[s + len('*** BEGIN OF TEST '):s + e + 3 - 1]
361                    self._capture_console('test start: %s' % (self.test_label))
362            ok_to_kill = '*** TEST STATE: USER_INPUT' in text or \
363                         '*** TEST STATE: BENCHMARK' in text
364            if ok_to_kill:
365                reset_target = True
366            else:
367                reset_target = False
368            if self.test_started and self.target_start_regx is not None:
369                if self.target_start_regx.match(text):
370                    self._capture_console('target start detected')
371                    ok_to_kill = True
372            if not reset_target and self.target_reset_regx is not None:
373                if self.target_reset_regx.match(text):
374                    self._capture_console('target reset condition detected')
375                    self._target_command('reset')
376            if self.kill_on_end:
377                if not ok_to_kill and '*** END OF TEST ' in text:
378                    self._capture_console('test end: %s' % (self.test_label))
379                    if self.test_label is not None:
380                        ok_to_kill = '*** END OF TEST %s ***' % (self.test_label) in text
381            text = [(self.console_prefix, l) for l in text.replace(chr(13), '').splitlines()]
382            if self.output is not None:
383                if self.realtime_trace:
384                    self._realtime_trace(text)
385                self.output += text
386            if reset_target:
387                if self.index == self.total:
388                    self._target_command('off')
389                else:
390                    self._target_command('reset')
391        finally:
392            self._unlock()
393        if ok_to_kill:
394            self._ok_kill()
395
396    def capture_console(self, text):
397        self._lock()
398        try:
399            self._capture_console(text)
400        finally:
401            self._unlock()
402
403    def exe_trace(self, flag):
404        dt = self.macros['exe_trace']
405        if dt:
406            if flag in dt.split(','):
407                return True
408        return False
409
410    def kill(self):
411        if self.process:
412            try:
413                self.process.kill()
414            except:
415                pass
416
417def load(bsp, opts):
418    mandatory = ['bsp', 'arch', 'tester']
419    cfg = configuration.configuration()
420    path_ = opts.defaults.expand('%%{_configdir}/bsps/%s.ini' % (bsp))
421    ini_name = path.basename(path_)
422    for p in path.dirname(path_).split(':'):
423        if path.exists(path.join(p, ini_name)):
424            cfg.load(path.join(p, ini_name))
425            if not cfg.has_section(bsp):
426                raise error.general('bsp section not found in ini: [%s]' % (bsp))
427            item_names = cfg.get_item_names(bsp, err = False)
428            for m in mandatory:
429                if m not in item_names:
430                    raise error.general('mandatory item not found in bsp section: %s' % (m))
431            opts.defaults.set_write_map(bsp, add = True)
432            for i in cfg.get_items(bsp, flatten = False):
433                opts.defaults[i[0]] = i[1]
434            if not opts.defaults.set_read_map(bsp):
435                raise error.general('cannot set BSP read map: %s' % (bsp))
436            # Get a copy of the required fields we need
437            requires = cfg.comma_list(bsp, 'requires', err = False)
438            del cfg
439            user_config = opts.find_arg('--user-config')
440            if user_config is not None:
441                user_config = path.expanduser(user_config[1])
442                if not path.exists(user_config):
443                    raise error.general('cannot find user configuration file: %s' % (user_config))
444            else:
445                if 'HOME' in os.environ:
446                    user_config = path.join(os.environ['HOME'], '.rtemstesterrc')
447            if user_config:
448                if path.exists(user_config):
449                    cfg = configuration.configuration()
450                    cfg.load(user_config)
451                    if cfg.has_section(bsp):
452                        for i in cfg.get_items(bsp, flatten = False):
453                            opts.defaults[i[0]] = i[1]
454            # Check for the required values.
455            for r in requires:
456                if opts.defaults.get(r) is None:
457                    raise error.general('user value missing, BSP %s requires \'%s\': missing: %s' % \
458                                        (bsp, ', '.join(requires), r))
459            return opts.defaults['bsp']
460    raise error.general('cannot find bsp configuration file: %s.ini' % (bsp))
Note: See TracBrowser for help on using the repository browser.