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

Last change on this file since fce29b0 was fce29b0, checked in by Chris Johns <chrisj@…>, on Oct 11, 2017 at 7:13:13 PM

tester: Change the capture console prompt to avoid email reply processing in clients.

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