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

5
Last change on this file since e058db0 was e058db0, checked in by Chris Johns <chrisj@…>, on 11/07/18 at 03:55:20

python: Provide support to select a valid python version.

  • Update imports after wrapping the code.
  • Fix python3 issues.
  • Fix config path issues for in repo and install runs.

Closes #3537

  • Property mode set to 100644
File size: 18.3 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        if not self.test_started:
353            s = text.find('*** BEGIN OF TEST ')
354            if s >= 0:
355                self.test_started = True
356                e = text[s + 3:].find('***')
357                if e >= 0:
358                    self.test_label = text[s + len('*** BEGIN OF TEST '):s + e + 3 - 1]
359                self.capture_console('test start: %s' % (self.test_label))
360        ok_to_kill = '*** TEST STATE: USER_INPUT' in text or \
361                     '*** TEST STATE: BENCHMARK' in text
362        if ok_to_kill:
363            reset_target = True
364        else:
365            reset_target = False
366        if self.test_started and self.target_start_regx is not None:
367            if self.target_start_regx.match(text):
368                self.capture_console('target start detected')
369                ok_to_kill = True
370        if not reset_target and self.target_reset_regx is not None:
371            if self.target_reset_regx.match(text):
372                self.capture_console('target reset condition detected')
373                self._target_command('reset')
374        if self.kill_on_end:
375            if not ok_to_kill and '*** END OF TEST ' in text:
376                self.capture_console('test end: %s' % (self.test_label))
377                if self.test_label is not None:
378                    ok_to_kill = '*** END OF TEST %s ***' % (self.test_label) in text
379        text = [(self.console_prefix, l) for l in text.replace(chr(13), '').splitlines()]
380        self._lock()
381        if self.output is not None:
382            if self.realtime_trace:
383                self._realtime_trace(text)
384            self.output += text
385        if reset_target:
386            if self.index == self.total:
387                self._target_command('off')
388            else:
389                self._target_command('reset')
390        self._unlock()
391        if ok_to_kill:
392            self._ok_kill()
393
394    def capture_console(self, text):
395        self._lock()
396        self._capture_console(text)
397        self._unlock()
398
399    def exe_trace(self, flag):
400        dt = self.macros['exe_trace']
401        if dt:
402            if flag in dt.split(','):
403                return True
404        return False
405
406    def kill(self):
407        if self.process:
408            try:
409                self.process.kill()
410            except:
411                pass
412
413def load(bsp, opts):
414    mandatory = ['bsp', 'arch', 'tester']
415    cfg = configuration.configuration()
416    path_ = opts.defaults.expand('%%{_configdir}/bsps/%s.ini' % (bsp))
417    ini_name = path.basename(path_)
418    for p in path.dirname(path_).split(':'):
419        if path.exists(path.join(p, ini_name)):
420            cfg.load(path.join(p, ini_name))
421            if not cfg.has_section(bsp):
422                raise error.general('bsp section not found in ini: [%s]' % (bsp))
423            item_names = cfg.get_item_names(bsp, err = False)
424            for m in mandatory:
425                if m not in item_names:
426                    raise error.general('mandatory item not found in bsp section: %s' % (m))
427            opts.defaults.set_write_map(bsp, add = True)
428            for i in cfg.get_items(bsp, flatten = False):
429                opts.defaults[i[0]] = i[1]
430            if not opts.defaults.set_read_map(bsp):
431                raise error.general('cannot set BSP read map: %s' % (bsp))
432            # Get a copy of the required fields we need
433            requires = cfg.comma_list(bsp, 'requires', err = False)
434            del cfg
435            user_config = opts.find_arg('--user-config')
436            if user_config is not None:
437                user_config = path.expanduser(user_config[1])
438                if not path.exists(user_config):
439                    raise error.general('cannot find user configuration file: %s' % (user_config))
440            else:
441                if 'HOME' in os.environ:
442                    user_config = path.join(os.environ['HOME'], '.rtemstesterrc')
443            if user_config:
444                if path.exists(user_config):
445                    cfg = configuration.configuration()
446                    cfg.load(user_config)
447                    if cfg.has_section(bsp):
448                        for i in cfg.get_items(bsp, flatten = False):
449                            opts.defaults[i[0]] = i[1]
450            # Check for the required values.
451            for r in requires:
452                if opts.defaults.get(r) is None:
453                    raise error.general('user value missing, BSP %s requires \'%s\': missing: %s' % \
454                                        (bsp, ', '.join(requires), r))
455            return opts.defaults['bsp']
456    raise error.general('cannot find bsp configuration file: %s.ini' % (bsp))
Note: See TracBrowser for help on using the repository browser.