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

5
Last change on this file since 5251361 was 5251361, checked in by Chris Johns <chrisj@…>, on 10/23/17 at 04:22:34

tester: Add an executable file name filter.

The exe filter lets a BSP change the executable file to something
that can be downloaded to the target. For example U-Boot requires the image
format. The tester can now be configured to on-demand generate a specific
image for the target as the tester runs.

  • Property mode set to 100644
File size: 15.0 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 config
43from rtemstoolkit import error
44from rtemstoolkit import execute
45from rtemstoolkit import log
46from rtemstoolkit import path
47
48from . import console
49from . import gdb
50from . import tftp
51
52timeout = 15
53
54class file(config.file):
55    """RTEMS Testing configuration."""
56
57    _directives = ['%execute',
58                   '%gdb',
59                   '%tftp',
60                   '%console']
61
62    def __init__(self, index, total, report, name, opts, _directives = _directives):
63        super(file, self).__init__(name, opts, directives = _directives)
64        self.lock = threading.Lock()
65        self.realtime_trace = self.debug_trace('output')
66        self.process = None
67        self.console = None
68        self.output = None
69        self.index = index
70        self.total = total
71        self.report = report
72        self.name = name
73        self.timedout = False
74        self.test_started = False
75        self.kill_good = False
76        self.kill_on_end = False
77        self.test_label = None
78        self.target_reset_regx = None
79        self.target_start_regx = None
80
81    def __del__(self):
82        if self.console:
83            del self.console
84        super(file, self).__del__()
85
86    def _lock(self):
87        self.lock.acquire()
88
89    def _unlock(self):
90        self.lock.release()
91
92    def _timeout(self):
93        self._lock()
94        self.timedout = True
95        self._unlock()
96        self.capture('*** TIMEOUT TIMEOUT')
97
98    def _ok_kill(self):
99        self._lock()
100        self.kill_good = True
101        self._unlock()
102        try:
103            self.process.kill()
104        except:
105            pass
106
107    def _target_regex(self, label):
108        regex = None
109        if self.defined(label):
110            r = self.expand('%%{%s}' % (label))
111            try:
112                regex = re.compile(r, re.MULTILINE)
113            except:
114                msg = 'invalid target regex: %s: %s' % (label, r)
115                raise error.general(msg)
116        return regex
117
118    def _target_command(self, command, bsp_arch = None, bsp = None, exe = None, fexe = None):
119        if self.defined('target_%s_command' % (command)):
120            cmd = self.expand('%%{target_%s_command}' % (command)).strip()
121            if bsp_arch is not None and '@ARCH@' in cmd:
122                cmd = cmd.replace('@ARCH@', bsp_arch)
123            if bsp is not None and '@BSP@' in cmd:
124                cmd = cmd.replace('@BSP@', bsp)
125            if exe is not None and '@EXE@' in cmd:
126                cmd = cmd.replace('@EXE@', exe)
127            if fexe is not None and '@FEXE@' in cmd:
128                cmd = cmd.replace('@FEXE@', exe)
129            if len(cmd) > 0:
130                rs_proc = execute.capture_execution()
131                ec, proc, output = rs_proc.open(cmd, shell = True)
132                self._capture_console('target %s: %s' % (command, cmd))
133                if len(output) > 0:
134                    output = os.linesep.join([' ' + l for l in output.splitlines()])
135                    self._capture_console(output)
136
137    def _target_exe_filter(self, exe):
138        if self.defined('target_exe_filter_command'):
139            f = self.expand('%{target_exe_filter_command}').strip()
140            # Be like sed and use the first character as the delmiter.
141            if len(f) > 0:
142                delimiter = f[0]
143                pat = ''
144                repl = ''
145                repl_not_pat = False
146                esc = False
147                for c in f[1:]:
148                    add = True
149                    if not esc and c == '\\':
150                        esc = True
151                        add = False
152                    elif esc:
153                        if c == delimiter:
154                            c = delimiter
155                        else:
156                            c = '\\' + c
157                            esc = False
158                    elif c == delimiter:
159                        if repl_not_pat:
160                            exe = re.sub(r'%s' % (pat), repl, exe)
161                            self._capture_console('target exe filter: find:%s subst:%s -> %s' % \
162                                                  (pat, repl, exe))
163                            return exe
164                        repl_not_pat = True
165                        add = False
166                    if add:
167                        if repl_not_pat:
168                            repl += c
169                        else:
170                            pat += c
171                raise error.general('invalid exe filter: %s' % (f))
172
173    def _output_length(self):
174        self._lock()
175        if self.test_started:
176            l = len(self.output)
177            self._unlock()
178            return l
179        self._unlock()
180        return 0
181
182    def _capture_console(self, text):
183        text = [('=>', l) for l in text.replace(chr(13), '').splitlines()]
184        if self.output is not None:
185            self._realtime_trace(text)
186            self.output += text
187
188    def _dir_console(self, data):
189        if self.console is not None:
190            raise error.general(self._name_line_msg('console already configured'))
191        if len(data) == 0:
192            raise error.general(self._name_line_msg('no console configuration provided'))
193        console_trace = trace = self.debug_trace('console')
194        if data[0] == 'stdio':
195            self.console = console.stdio(trace = console_trace)
196        elif data[0] == 'tty':
197            if len(data) < 2 or len(data) >3:
198                raise error.general(self._name_line_msg('no tty configuration provided'))
199            if len(data) == 3:
200                settings = data[2]
201            else:
202                settings = None
203            self.console = console.tty(data[1],
204                                       output = self.capture,
205                                       setup = settings,
206                                       trace = console_trace)
207        else:
208            raise error.general(self._name_line_msg('invalid console type'))
209
210    def _dir_execute(self, data, total, index, exe, bsp_arch, bsp):
211        self.process = execute.execute(output = self.capture)
212        if not self.in_error:
213            if self.console:
214                self.console.open()
215            self.capture_console('run: %s' % (' '.join(data)))
216            ec, proc = self.process.open(data,
217                                         timeout = (int(self.expand('%{timeout}')),
218                                                    self._timeout))
219            self._lock()
220            if not self.kill_good and ec > 0:
221                self._error('execute failed: %s: exit-code:%d' % (' '.join(data), ec))
222            elif self.timedout:
223                self.process.kill()
224            self._unlock()
225            if self.console:
226                self.console.close()
227
228    def _dir_gdb(self, data, total, index, exe, bsp_arch, bsp):
229        if len(data) < 3 or len(data) > 4:
230            raise error.general('invalid %gdb arguments')
231        self.process = gdb.gdb(bsp_arch, bsp,
232                               trace = self.debug_trace('gdb'),
233                               mi_trace = self.debug_trace('gdb-mi'))
234        script = self.expand('%%{%s}' % data[2])
235        if script:
236            script = [l.strip() for l in script.splitlines()]
237        if not self.in_error:
238            if self.console:
239                self.console.open()
240            self.process.open(data[0], data[1],
241                              script = script,
242                              output = self.capture,
243                              gdb_console = self.capture_console,
244                              timeout = int(self.expand('%{timeout}')))
245            if self.console:
246                self.console.close()
247
248    def _dir_tftp(self, data, total, index, exe, bsp_arch, bsp):
249        if len(data) != 2:
250            raise error.general('invalid %tftp arguments')
251        try:
252            port = int(data[1])
253        except:
254            raise error.general('invalid %tftp port')
255        self.kill_on_end = True
256        self.process = tftp.tftp(bsp_arch, bsp,
257                                 trace = self.debug_trace('gdb'))
258        if not self.in_error:
259            if self.console:
260                self.console.open()
261            self.process.open(executable = exe,
262                              port = port,
263                              output_length = self._output_length,
264                              console = self.capture_console,
265                              timeout = (int(self.expand('%{timeout}')),
266                                         self._timeout))
267            if self.console:
268                self.console.close()
269
270    def _directive_filter(self, results, directive, info, data):
271        if results[0] == 'directive':
272            _directive = results[1]
273            _data = results[2]
274            ds = []
275            if len(_data):
276                ds = [_data[0]]
277                if len(_data) > 1:
278                    ds += _data[1].split()
279            ds = self.expand(ds)
280
281            if _directive == '%console':
282                self._dir_console(ds)
283            else:
284                self._lock()
285                try:
286                    self.output = []
287                    total = int(self.expand('%{test_total}'))
288                    index = int(self.expand('%{test_index}'))
289                    exe = self.expand('%{test_executable}')
290                    bsp_arch = self.expand('%{bsp_arch}')
291                    bsp = self.expand('%{bsp}')
292                    fexe = self._target_exe_filter(exe)
293                    self.report.start(index, total, exe, fexe, bsp_arch, bsp)
294                    if self.index == 1:
295                        self._target_command('on', bsp_arch, bsp, exe, fexe)
296                    self._target_command('pretest', bsp_arch, bsp, exe, fexe)
297                finally:
298                    self._unlock()
299                if _directive == '%execute':
300                    self._dir_execute(ds, total, index, fexe, bsp_arch, bsp)
301                elif _directive == '%gdb':
302                    self._dir_gdb(ds, total, index, fexe, bsp_arch, bsp)
303                elif _directive == '%tftp':
304                    self._dir_tftp(ds, total, index, fexe, bsp_arch, bsp)
305                else:
306                    raise error.general(self._name_line_msg('invalid directive'))
307                self._lock()
308                if self.index == self.total:
309                    self._target_command('off', bsp_arch, bsp, exe, fexe)
310                self._target_command('posttest', bsp_arch, bsp, exe, fexe)
311                try:
312                    status = self.report.end(exe, self.output)
313                    self._capture_console('test result: %s' % (status))
314                    if status == 'timeout':
315                        if self.index == self.total:
316                            self._target_command('off', bsp_arch, bsp, exe, fexe)
317                        else:
318                            self._target_command('reset', bsp_arch, bsp, exe, fexe)
319                    self.process = None
320                    self.output = None
321                finally:
322                    self._unlock()
323        return None, None, None
324
325    def _realtime_trace(self, text):
326        if self.realtime_trace:
327            for l in text:
328                print(' '.join(l))
329
330    def run(self):
331        self.target_start_regx = self._target_regex('target_start_regex')
332        self.target_reset_regx = self._target_regex('target_reset_regex')
333        self.load(self.name)
334
335    def capture(self, text):
336        if not self.test_started:
337            s = text.find('*** BEGIN OF TEST ')
338            if s >= 0:
339                self.test_started = True
340                e = text[s + 3:].find('***')
341                if e >= 0:
342                    self.test_label = text[s + len('*** BEGIN OF TEST '):s + e + 3 - 1]
343                self.capture_console('test start: %s' % (self.test_label))
344        ok_to_kill = '*** TEST STATE: USER_INPUT' in text or \
345                     '*** TEST STATE: BENCHMARK' in text
346        if ok_to_kill:
347            reset_target = True
348        else:
349            reset_target = False
350        if self.test_started and self.target_start_regx is not None:
351            if self.target_start_regx.match(text):
352                self.capture_console('target start detected')
353                ok_to_kill = True
354        if not reset_target and self.target_reset_regx is not None:
355            if self.target_reset_regx.match(text):
356                self.capture_console('target reset condition detected')
357                reset_target = True
358        if self.kill_on_end:
359            if not ok_to_kill and '*** END OF TEST ' in text:
360                self.capture_console('test end: %s' % (self.test_label))
361                if self.test_label is not None:
362                    ok_to_kill = '*** END OF TEST %s ***' % (self.test_label) in text
363        text = [(']', l) for l in text.replace(chr(13), '').splitlines()]
364        self._lock()
365        if self.output is not None:
366            self._realtime_trace(text)
367            self.output += text
368        if reset_target:
369            if self.index == self.total:
370                self._target_command('off')
371            else:
372                self._target_command('reset')
373        self._unlock()
374        if ok_to_kill:
375            self._ok_kill()
376
377    def capture_console(self, text):
378        self._lock()
379        self._capture_console(text)
380        self._unlock()
381
382    def debug_trace(self, flag):
383        dt = self.macros['debug_trace']
384        if dt:
385            if flag in dt.split(','):
386                return True
387        return False
388
389    def kill(self):
390        if self.process:
391            try:
392                self.process.kill()
393            except:
394                pass
Note: See TracBrowser for help on using the repository browser.