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

5
Last change on this file since f1e8fd4 was f1e8fd4, checked in by Chris Johns <chrisj@…>, on 10/21/17 at 05:59:14

tester: Add pretest and posttest target commands with @ARCH@, @BSP@ and @EXE@ substitution.

  • Property mode set to 100644
File size: 13.1 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):
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 len(cmd) > 0:
128                rs_proc = execute.capture_execution()
129                ec, proc, output = rs_proc.open(cmd, shell = True)
130                self._capture_console('target %s: %s: %s' % (command, cmd, output))
131
132    def _output_length(self):
133        self._lock()
134        if self.test_started:
135            l = len(self.output)
136            self._unlock()
137            return l
138        self._unlock()
139        return 0
140
141    def _capture_console(self, text):
142        text = [('=>', l) for l in text.replace(chr(13), '').splitlines()]
143        if self.output is not None:
144            self._realtime_trace(text)
145            self.output += text
146
147    def _dir_console(self, data):
148        if self.console is not None:
149            raise error.general(self._name_line_msg('console already configured'))
150        if len(data) == 0:
151            raise error.general(self._name_line_msg('no console configuration provided'))
152        console_trace = trace = self.debug_trace('console')
153        if data[0] == 'stdio':
154            self.console = console.stdio(trace = console_trace)
155        elif data[0] == 'tty':
156            if len(data) < 2 or len(data) >3:
157                raise error.general(self._name_line_msg('no tty configuration provided'))
158            if len(data) == 3:
159                settings = data[2]
160            else:
161                settings = None
162            self.console = console.tty(data[1],
163                                       output = self.capture,
164                                       setup = settings,
165                                       trace = console_trace)
166        else:
167            raise error.general(self._name_line_msg('invalid console type'))
168
169    def _dir_execute(self, data, total, index, exe, bsp_arch, bsp):
170        self.process = execute.execute(output = self.capture)
171        if not self.in_error:
172            if self.console:
173                self.console.open()
174            self.capture_console('run: %s' % (' '.join(data)))
175            ec, proc = self.process.open(data,
176                                         timeout = (int(self.expand('%{timeout}')),
177                                                    self._timeout))
178            self._lock()
179            if not self.kill_good and ec > 0:
180                self._error('execute failed: %s: exit-code:%d' % (' '.join(data), ec))
181            elif self.timedout:
182                self.process.kill()
183            self._unlock()
184            if self.console:
185                self.console.close()
186
187    def _dir_gdb(self, data, total, index, exe, bsp_arch, bsp):
188        if len(data) < 3 or len(data) > 4:
189            raise error.general('invalid %gdb arguments')
190        self.process = gdb.gdb(bsp_arch, bsp,
191                               trace = self.debug_trace('gdb'),
192                               mi_trace = self.debug_trace('gdb-mi'))
193        script = self.expand('%%{%s}' % data[2])
194        if script:
195            script = [l.strip() for l in script.splitlines()]
196        if not self.in_error:
197            if self.console:
198                self.console.open()
199            self.process.open(data[0], data[1],
200                              script = script,
201                              output = self.capture,
202                              gdb_console = self.capture_console,
203                              timeout = int(self.expand('%{timeout}')))
204            if self.console:
205                self.console.close()
206
207    def _dir_tftp(self, data, total, index, exe, bsp_arch, bsp):
208        if len(data) != 2:
209            raise error.general('invalid %tftp arguments')
210        try:
211            port = int(data[1])
212        except:
213            raise error.general('invalid %tftp port')
214        self.kill_on_end = True
215        self.process = tftp.tftp(bsp_arch, bsp,
216                                 trace = self.debug_trace('gdb'))
217        if not self.in_error:
218            if self.console:
219                self.console.open()
220            self.process.open(executable = data[0],
221                              port = port,
222                              output_length = self._output_length,
223                              console = self.capture_console,
224                              timeout = (int(self.expand('%{timeout}')),
225                                         self._timeout))
226            if self.console:
227                self.console.close()
228
229    def _directive_filter(self, results, directive, info, data):
230        if results[0] == 'directive':
231            _directive = results[1]
232            _data = results[2]
233            ds = []
234            if len(_data):
235                ds = [_data[0]]
236                if len(_data) > 1:
237                    ds += _data[1].split()
238            ds = self.expand(ds)
239
240            if _directive == '%console':
241                self._dir_console(ds)
242            else:
243                self._lock()
244                try:
245                    self.output = []
246                    total = int(self.expand('%{test_total}'))
247                    index = int(self.expand('%{test_index}'))
248                    exe = self.expand('%{test_executable}')
249                    bsp_arch = self.expand('%{bsp_arch}')
250                    bsp = self.expand('%{bsp}')
251                    self.report.start(index, total, exe, exe, bsp_arch, bsp)
252                    if self.index == 1:
253                        self._target_command('on', bsp_arch, bsp, exe)
254                    self._target_command('pretest', bsp_arch, bsp, exe)
255                finally:
256                    self._unlock()
257                if _directive == '%execute':
258                    self._dir_execute(ds, total, index, exe, bsp_arch, bsp)
259                elif _directive == '%gdb':
260                    self._dir_gdb(ds, total, index, exe, bsp_arch, bsp)
261                elif _directive == '%tftp':
262                    self._dir_tftp(ds, total, index, exe, bsp_arch, bsp)
263                else:
264                    raise error.general(self._name_line_msg('invalid directive'))
265                self._lock()
266                if self.index == self.total:
267                    self._target_command('off', bsp_arch, bsp, exe)
268                self._target_command('posttest', bsp_arch, bsp, exe)
269                try:
270                    status = self.report.end(exe, self.output)
271                    self._capture_console('test result: %s' % (status))
272                    if status == 'timeout':
273                        if self.index == self.total:
274                            self._target_command('off', bsp_arch, bsp, exe)
275                        else:
276                            self._target_command('reset', bsp_arch, bsp, exe)
277                    self.process = None
278                    self.output = None
279                finally:
280                    self._unlock()
281        return None, None, None
282
283    def _realtime_trace(self, text):
284        if self.realtime_trace:
285            for l in text:
286                print(' '.join(l))
287
288    def run(self):
289        self.target_start_regx = self._target_regex('target_start_regex')
290        self.target_reset_regx = self._target_regex('target_reset_regex')
291        self.load(self.name)
292
293    def capture(self, text):
294        if not self.test_started:
295            s = text.find('*** BEGIN OF TEST ')
296            if s >= 0:
297                self.test_started = True
298                e = text[s + 3:].find('***')
299                if e >= 0:
300                    self.test_label = text[s + len('*** BEGIN OF TEST '):s + e + 3 - 1]
301                self.capture_console('test start: %s' % (self.test_label))
302        ok_to_kill = '*** TEST STATE: USER_INPUT' in text or \
303                     '*** TEST STATE: BENCHMARK' in text
304        if ok_to_kill:
305            reset_target = True
306        else:
307            reset_target = False
308        if self.test_started and self.target_start_regx is not None:
309            if self.target_start_regx.match(text):
310                self.capture_console('target start detected')
311                ok_to_kill = True
312        if not reset_target and self.target_reset_regx is not None:
313            if self.target_reset_regx.match(text):
314                self.capture_console('target reset condition detected')
315                reset_target = True
316        if self.kill_on_end:
317            if not ok_to_kill and '*** END OF TEST ' in text:
318                self.capture_console('test end: %s' % (self.test_label))
319                if self.test_label is not None:
320                    ok_to_kill = '*** END OF TEST %s ***' % (self.test_label) in text
321        text = [(']', l) for l in text.replace(chr(13), '').splitlines()]
322        self._lock()
323        if self.output is not None:
324            self._realtime_trace(text)
325            self.output += text
326        if reset_target:
327            if self.index == self.total:
328                self._target_command('off')
329            else:
330                self._target_command('reset')
331        self._unlock()
332        if ok_to_kill:
333            self._ok_kill()
334
335    def capture_console(self, text):
336        self._lock()
337        self._capture_console(text)
338        self._unlock()
339
340    def debug_trace(self, flag):
341        dt = self.macros['debug_trace']
342        if dt:
343            if flag in dt.split(','):
344                return True
345        return False
346
347    def kill(self):
348        if self.process:
349            try:
350                self.process.kill()
351            except:
352                pass
Note: See TracBrowser for help on using the repository browser.