[PATCH] tester: Add support for test-too-long

chrisj at rtems.org chrisj at rtems.org
Tue Sep 8 07:04:34 UTC 2020


From: Chris Johns <chrisj at rtems.org>

- A test that loops generating output did not timeout. Monitor the
  the session time and set a maximum test period.
---
 tester/rt/config.py             |  45 +++++----
 tester/rt/exe.py                | 172 ++++++++++++++++++++++++++++++++
 tester/rt/gdb.py                | 117 +++++++++++++++++-----
 tester/rt/report.py             |  28 +++++-
 tester/rt/test.py               |   6 +-
 tester/rt/tftp.py               |  33 ++++--
 tester/rtems/testing/testing.mc |   3 +-
 tester/wscript                  |   1 +
 8 files changed, 348 insertions(+), 57 deletions(-)
 create mode 100644 tester/rt/exe.py

diff --git a/tester/rt/config.py b/tester/rt/config.py
index ee639e9..bc9263a 100644
--- a/tester/rt/config.py
+++ b/tester/rt/config.py
@@ -1,6 +1,6 @@
 #
 # RTEMS Tools Project (http://www.rtems.org/)
-# Copyright 2013-2017 Chris Johns (chrisj at rtems.org)
+# Copyright 2013-2020 Chris Johns (chrisj at rtems.org)
 # All rights reserved.
 #
 # This file is part of the RTEMS Tools package in 'rtems-tools'.
@@ -49,6 +49,7 @@ from rtemstoolkit import path
 from rtemstoolkit import stacktraces
 
 import console
+import exe
 import gdb
 import tftp
 
@@ -78,6 +79,7 @@ class file(config.file):
         self.report = report
         self.name = name
         self.timedout = False
+        self.test_too_long = False
         self.test_started = False
         self.kill_good = False
         self.kill_on_end = False
@@ -102,6 +104,12 @@ class file(config.file):
         self._unlock()
         self.capture('*** TIMEOUT TIMEOUT')
 
+    def _test_too_long(self):
+        self._lock()
+        self.test_too_long = True
+        self._unlock()
+        self.capture('*** TEST TOO LONG')
+
     def _ok_kill(self):
         self._lock()
         self.kill_good = True
@@ -219,22 +227,20 @@ class file(config.file):
             else:
                 raise error.general(self._name_line_msg('invalid console type'))
 
-    def _dir_execute(self, data, total, index, exe, bsp_arch, bsp):
-        self.process = execute.execute(output = self.capture)
-        if self.console:
-            self.console.open()
+    def _dir_execute(self, data, total, index, rexe, bsp_arch, bsp):
+        self.process = exe.exe(bsp_arch, bsp, trace = self.exe_trace('exe'))
         if not self.in_error:
-            self.capture_console('run: %s' % (' '.join(data)))
+            if self.console:
+                self.console.open()
             if not self.opts.dry_run():
-                ec, proc = self.process.open(data,
-                                             timeout = (int(self.expand('%{timeout}')),
-                                                        self._timeout))
-                self._lock()
-                if not (self.kill_good or self.defined('exe_ignore_ret')) and ec > 0:
-                    self._error('execute failed: %s: exit-code:%d' % (' '.join(data), ec))
-                elif self.timedout:
-                    self.process.kill()
-                self._unlock()
+                self.process.open(data,
+                                  ignore_exit_code = self.defined('exe_ignore_ret'),
+                                  output = self.capture,
+                                  console = self.capture_console,
+                                  timeout = (int(self.expand('%{timeout}')),
+                                             int(self.expand('%{max_test_period}')),
+                                             self._timeout,
+                                             self._test_too_long))
             if self.console:
                 self.console.close()
 
@@ -255,7 +261,10 @@ class file(config.file):
                                   script = script,
                                   output = self.capture,
                                   gdb_console = self.capture_console,
-                                  timeout = int(self.expand('%{timeout}')))
+                                  timeout = (int(self.expand('%{timeout}')),
+                                             int(self.expand('%{max_test_period}')),
+                                             self._timeout,
+                                             self._test_too_long))
             if self.console:
                 self.console.close()
 
@@ -278,7 +287,9 @@ class file(config.file):
                                   output_length = self._output_length,
                                   console = self.capture_console,
                                   timeout = (int(self.expand('%{timeout}')),
-                                             self._timeout))
+                                             int(self.expand('%{max_test_period}')),
+                                             self._timeout,
+                                             self._test_too_long))
                 if self.console:
                     self.console.close()
 
diff --git a/tester/rt/exe.py b/tester/rt/exe.py
new file mode 100644
index 0000000..5655073
--- /dev/null
+++ b/tester/rt/exe.py
@@ -0,0 +1,172 @@
+# SPDX-License-Identifier: BSD-2-Clause
+'''Executable test target.'''
+
+# Copyright (C) 2013-2020 Chris Johns (chrisj at rtems.org)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+#    notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+#    notice, this list of conditions and the following disclaimer in the
+#    documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+from __future__ import print_function
+
+import datetime
+import os
+import threading
+import time
+
+from rtemstoolkit import execute
+
+
+class exe(object):
+    '''RTEMS Testing EXE base.'''
+
+    # pylint: disable=useless-object-inheritance
+    # pylint: disable=too-many-instance-attributes
+
+    def __init__(self, bsp_arch, bsp, trace=False):
+        self.trace = trace
+        self.lock_trace = False
+        self.lock_locked = None
+        self.lock = threading.RLock()
+        self.bsp = bsp
+        self.bsp_arch = bsp_arch
+        self.output = None
+        self.output_length = 0
+        self.process = None
+        self.ecode = None
+        self.output = None
+        self.output_buffer = ''
+        self.console = None
+        self.timeout = None
+        self.test_too_long = None
+        self.kill_good = True
+
+    def _lock(self, msg):
+        if self.lock_trace:
+            print('|[   LOCK:%s ]|' % (msg))
+            self.lock_locked = datetime.datetime.now()
+        self.lock.acquire()
+
+    def _unlock(self, msg):
+        if self.lock_trace:
+            period = datetime.datetime.now() - self.lock_locked
+            print('|] UNLOCK:%s [| : %s' % (msg, period))
+        self.lock.release()
+
+    def _capture(self, text):
+        self._lock('_capture')
+        self.output_length += len(text)
+        self._unlock('_capture')
+        if self.output is not None:
+            self.output(text)
+
+    def _timeout(self):
+        self._kill()
+        if self.timeout is not None:
+            self.timeout()
+
+    def _test_too_long(self):
+        self._kill()
+        if self.test_too_long is not None:
+            self.test_too_long()
+
+    def _kill(self):
+        self._lock('_kill')
+        self.kill_good = True
+        self._unlock('_kill')
+        if self.process:
+            # pylint: disable=bare-except
+            try:
+                self.process.kill()
+            except:
+                pass
+        self.process = None
+
+    def _execute(self, args):
+        '''Thread to execute the test and to wait for it to finish.'''
+        # pylint: disable=unused-variable
+        cmds = args
+        if self.console is not None:
+            self.console('exe: %s' % (' '.join(cmds)))
+        ecode, proc = self.process.open(cmds)
+        if self.trace:
+            print('gdb done', ecode)
+        self._lock('_execute')
+        self.ecode = ecode
+        self.process = None
+        self._unlock('_execute')
+
+    def _monitor(self, timeout):
+        output_length = self.output_length
+        step = 0.25
+        period = timeout[0]
+        seconds = timeout[1]
+        while self.process and period > 0 and seconds > 0:
+            current_length = self.output_length
+            if output_length != current_length:
+                period = timeout[0]
+            output_length = current_length
+            if seconds < step:
+                seconds = 0
+            else:
+                seconds -= step
+            if period < step:
+                step = period
+                period = 0
+            else:
+                period -= step
+            self._unlock('_monitor')
+            time.sleep(step)
+            self._lock('_monitor')
+        if self.process is not None:
+            if period == 0:
+                self._timeout()
+            elif seconds == 0:
+                self._test_too_long()
+
+    def open(self, command, ignore_exit_code, output, console, timeout):
+        '''Open the execute test run'''
+        # pylint: disable=too-many-arguments
+        self._lock('_open')
+        self.timeout = timeout[2]
+        self.test_too_long = timeout[3]
+        try:
+            cmds = execute.arg_list(command)
+            self.output = output
+            self.console = console
+            self.process = execute.execute(output=self._capture)
+            exec_thread = threading.Thread(target=self._execute, args=[cmds])
+            exec_thread.start()
+            self._monitor(timeout)
+            if self.ecode is not None and \
+               not (self.kill_good or ignore_exit_code) and self.ecode > 0:
+                if self.output:
+                    self.output('*** TARGET ERROR %d %s ***' %
+                                (self.ecode, os.strerror(self.ecode)))
+        finally:
+            self._unlock('_open')
+
+    def kill(self):
+        '''Kill the test run.'''
+        self._lock('_kill')
+        try:
+            self._kill()
+        finally:
+            self._unlock('_kill')
diff --git a/tester/rt/gdb.py b/tester/rt/gdb.py
index bfd3749..d0cf504 100644
--- a/tester/rt/gdb.py
+++ b/tester/rt/gdb.py
@@ -43,6 +43,7 @@ except ImportError:
     import queue
 import sys
 import threading
+import time
 
 from rtemstoolkit import error
 from rtemstoolkit import execute
@@ -67,15 +68,19 @@ class gdb(object):
         self.bsp = bsp
         self.bsp_arch = bsp_arch
         self.output = None
+        self.output_length = 0
         self.gdb_console = None
         self.input = queue.Queue()
         self.commands = queue.Queue()
         self.process = None
+        self.ecode = None
         self.state = {}
         self.running = False
         self.breakpoints = {}
         self.output = None
         self.output_buffer = ''
+        self.timeout = None
+        self.test_too_long = None
         self.lc = 0
 
     def _lock(self, msg):
@@ -153,13 +158,14 @@ class gdb(object):
         return False
 
     def _timeout(self):
-        self._lock('_timeout')
-        try:
-            if self.output:
-                self.output('*** TIMEOUT TIMEOUT')
-                self._gdb_quit(backtrace = True)
-        finally:
-            self._unlock('_timeout')
+        self._stop()
+        if self.timeout is not None:
+            self.timeout()
+
+    def _test_too_long(self):
+        self._stop()
+        if self.test_too_long is not None:
+            self.test_too_long()
 
     def _cleanup(self, proc):
         self._lock('_cleanup')
@@ -181,10 +187,73 @@ class gdb(object):
         finally:
             self._unlock('_gdb_quit')
 
+    def _stop(self):
+        self._gdb_quit(backtrace=True)
+        seconds = 5
+        step = 0.1
+        while self.process and seconds > 0:
+            if seconds > step:
+                seconds -= step
+            else:
+                seconds = 0
+            self._unlock('_stop')
+            time.sleep(step)
+            self._lock('_stop')
+        if self.process and seconds == 0:
+            self._kill()
+
+    def _kill(self):
+        if self.process:
+            self.process.kill()
+        self.process = None
+
+    def _execute_gdb(self, args):
+        '''Thread to execute GDB and to wait for it to finish.'''
+        cmds = args
+        self.gdb_console('gdb: %s' % (' '.join(cmds)))
+        ecode, proc = self.process.open(cmds)
+        if self.trace:
+            print('gdb done', ecode)
+        self._lock('_execute_gdb')
+        self.ecode = ecode
+        self.process = None
+        self.running = False
+        self._unlock('_execute_gdb')
+
+    def _monitor(self, timeout):
+        output_length = self.output_length
+        step = 0.25
+        period = timeout[0]
+        seconds = timeout[1]
+        while self.process and period > 0 and seconds > 0:
+            current_length = self.output_length
+            if output_length != current_length:
+                period = timeout[0]
+            output_length = current_length
+            if seconds < step:
+                seconds = 0
+            else:
+                seconds -= step
+            if period < step:
+                step = period
+                period = 0
+            else:
+                period -= step
+            self._unlock('_monitor')
+            time.sleep(step)
+            self._lock('_monitor')
+        if self.process is not None:
+            if period == 0:
+                self._timeout()
+            elif seconds == 0:
+                self._test_too_long()
+
     def open(self, command, executable,
-             output, gdb_console, script = None, tty = None,
-             timeout = 300):
+             output, gdb_console, timeout,
+             script = None, tty = None):
         self._lock('_open')
+        self.timeout = timeout[2]
+        self.test_too_long = timeout[3]
         try:
             cmds = execute.arg_list(command) + ['-i=mi',
                                                 '--nx',
@@ -196,32 +265,25 @@ class gdb(object):
             self.output = output
             self.gdb_console = gdb_console
             self.script = script
-            self.running = False
             self.process = execute.execute(output = self._reader,
                                            input = self._writer,
                                            cleanup = self._cleanup)
-        finally:
-            self._unlock('_open')
-        self.gdb_console('gdb: %s' % (' '.join(cmds)))
-        ec, proc = self.process.open(cmds, timeout = (timeout, self._timeout))
-        if self.trace:
-            print('gdb done', ec)
-        if ec > 0:
-            raise error.general('gdb exec: %s: %s' % (cmds[0], os.strerror(ec)))
-        self._lock('_open')
-        try:
-            self.process = None
+            exec_thread = threading.Thread(target=self._execute_gdb,
+                                           args=[cmds])
+            exec_thread.start()
+            self._monitor(timeout)
+            if self.ecode is not None and self.ecode > 0:
+                raise error.general('gdb exec: %s: %s' % (cmds[0],
+                                                          os.strerror(self.ecode)))
         finally:
             self._unlock('_open')
 
     def kill(self):
-        self._lock('_open')
+        self._lock('_kill')
         try:
-            if self.process:
-                self.process.kill()
-            self.process = None
+            self._kill()
         finally:
-            self._unlock('_open')
+            self._unlock('_kill')
 
     def gdb_expect(self):
         if self.trace:
@@ -282,8 +344,9 @@ class gdb(object):
                     if last_lf >= 0:
                         lines = self.output_buffer[:last_lf]
                         if self.trace:
-                            print('/// console output')
+                            print('/// console output: ', len(lines))
                         for line in lines.splitlines():
+                            self.output_length += len(line)
                             self.output(line)
                         self.output_buffer = self.output_buffer[last_lf + 1:]
         except:
diff --git a/tester/rt/report.py b/tester/rt/report.py
index c62d553..5f871dc 100644
--- a/tester/rt/report.py
+++ b/tester/rt/report.py
@@ -63,6 +63,7 @@ class report(object):
         self.indeterminate = 0
         self.benchmark = 0
         self.timeouts = 0
+        self.test_too_long = 0
         self.invalids = 0
         self.wrong_version = 0
         self.wrong_build = 0
@@ -79,6 +80,7 @@ class report(object):
         msg += 'Indeterminate: %*d%s' % (self.total_len, self.self.indeterminate, os.linesep)
         msg += 'Benchmark:     %*d%s' % (self.total_len, self.self.benchmark, os.linesep)
         msg += 'Timeout:       %*d%s' % (self.total_len, self.timeouts, os.linesep)
+        msg += 'Test too long: %*d%s' % (self.total_len, self.test_too_long, os.linesep)
         msg += 'Invalid:       %*d%s' % (self.total_len, self.invalids, os.linesep)
         msg += 'Wrong Version  %*d%s' % (self.total_len, self.wrong_version, os.linesep)
         msg += 'Wrong Build    %*d%s' % (self.total_len, self.wrong_build, os.linesep)
@@ -87,7 +89,7 @@ class report(object):
 
     def start(self, index, total, name, executable, bsp_arch, bsp, show_header):
         header = '[%*d/%*d] p:%-*d f:%-*d u:%-*d e:%-*d I:%-*d B:%-*d ' \
-                 't:%-*d i:%-*d W:%-*d | %s/%s: %s' % \
+                 't:%-*d L:%-*d i:%-*d W:%-*d | %s/%s: %s' % \
                  (len(str(total)), index,
                   len(str(total)), total,
                   len(str(total)), self.passed,
@@ -97,6 +99,7 @@ class report(object):
                   len(str(total)), self.indeterminate,
                   len(str(total)), self.benchmark,
                   len(str(total)), self.timeouts,
+                  len(str(total)), self.test_too_long,
                   len(str(total)), self.invalids,
                   len(str(total)), self.wrong_version + self.wrong_build + self.wrong_tools,
                   bsp_arch,
@@ -123,11 +126,13 @@ class report(object):
     def end(self, name, output, output_prefix):
         start = False
         end = False
+        fatal = False
         state = None
         version = None
         build = None
         tools = None
         timeout = False
+        test_too_long = False
         prefixed_output = []
         for line in output:
             if line[0] == output_prefix:
@@ -137,8 +142,12 @@ class report(object):
                         start = True
                     elif line[1][4:].startswith('END OF '):
                         end = True
+                    elif line[1][4:].startswith('FATAL'):
+                        fatal = True
                     elif banner.startswith('TIMEOUT TIMEOUT'):
                         timeout = True
+                    elif banner.startswith('TEST TOO LONG'):
+                        test_too_long = True
                     elif banner.startswith('TEST VERSION:'):
                         version = banner[13:].strip()
                     elif banner.startswith('TEST STATE:'):
@@ -184,9 +193,15 @@ class report(object):
                     if state is None or state == 'EXPECTED_PASS':
                         status = 'passed'
                         self.passed += 1
+                elif fatal:
+                    status = 'fatal error'
+                    self.failed += 1
                 elif timeout:
                     status = 'timeout'
                     self.timeouts += 1
+                elif test_too_long:
+                    status = 'test-too-long'
+                    self.test_too_long += 1
                 elif start:
                     if not end:
                         status = 'failed'
@@ -240,7 +255,7 @@ class report(object):
         return status
 
     def log(self, name, mode):
-        status_fails = ['failed', 'timeout', 'invalid',
+        status_fails = ['failed', 'timeout', 'test-too-long', 'invalid',
                         'wrong-version', 'wrong-build', 'wrong-tools']
         if mode != 'none':
             self.lock.acquire()
@@ -273,8 +288,9 @@ class report(object):
     def score_card(self, mode = 'full'):
         if mode == 'short':
             wrongs = self.wrong_version + self.wrong_build + self.wrong_tools
-            return 'Passed:%d Failed:%d Timeout:%d Invalid:%d Wrong:%d' % \
-                (self.passed, self.failed, self.timeouts, self.invalids, wrongs)
+            return 'Passed:%d Failed:%d Timeout:%d Test-Too-long:%d Invalid:%d Wrong:%d' % \
+                (self.passed, self.failed, self.timeouts, self.test_too_long,
+                 self.invalids, wrongs)
         elif mode == 'full':
             l = []
             l += ['Passed:        %*d' % (self.total_len, self.passed)]
@@ -284,6 +300,7 @@ class report(object):
             l += ['Indeterminate: %*d' % (self.total_len, self.indeterminate)]
             l += ['Benchmark:     %*d' % (self.total_len, self.benchmark)]
             l += ['Timeout:       %*d' % (self.total_len, self.timeouts)]
+            l += ['Test too long: %*d' % (self.total_len, self.test_too_long)]
             l += ['Invalid:       %*d' % (self.total_len, self.invalids)]
             l += ['Wrong Version: %*d' % (self.total_len, self.wrong_version)]
             l += ['Wrong Build:   %*d' % (self.total_len, self.wrong_build)]
@@ -319,6 +336,9 @@ class report(object):
         if self.timeouts:
             l += ['Timeouts:']
             l += show_state(self.results, 'timeout', self.name_max_len)
+        if self.test_too_long:
+            l += ['Test too long:']
+            l += show_state(self.results, 'test-too-long', self.name_max_len)
         if self.invalids:
             l += ['Invalid:']
             l += show_state(self.results, 'invalid', self.name_max_len)
diff --git a/tester/rt/test.py b/tester/rt/test.py
index 16ac352..3ecadf9 100644
--- a/tester/rt/test.py
+++ b/tester/rt/test.py
@@ -235,6 +235,7 @@ def generate_json_report(args, reports, start_time, end_time,
     json_log['summary']['indeterminate_count'] = reports.indeterminate
     json_log['summary']['benchmark_count'] = reports.benchmark
     json_log['summary']['timeout_count'] = reports.timeouts
+    json_log['summary']['too_long_count'] = reports.too_long
     json_log['summary']['invalid_count'] = reports.invalids
     json_log['summary']['wrong-version_count'] = reports.wrong_version
     json_log['summary']['wrong-build_count'] = reports.wrong_build
@@ -246,8 +247,8 @@ def generate_json_report(args, reports, start_time, end_time,
 
     result_types = [
             'failed', 'user-input', 'expected-fail', 'indeterminate',
-            'benchmark', 'timeout', 'invalid', 'wrong-version', 'wrong-build',
-            'wrong-tools'
+            'benchmark', 'timeout', 'too-long', 'invalid', 'wrong-version',
+            'wrong-build', 'wrong-tools'
     ]
     json_results = {}
     for result_type in result_types:
@@ -304,6 +305,7 @@ def generate_junit_report(args, reports, start_time, end_time,
     junit_prop['indeterminate_count'] = reports.indeterminate
     junit_prop['benchmark_count'] = reports.benchmark
     junit_prop['timeout_count'] = reports.timeouts
+    junit_prop['too_long_count'] = reports.too_long
     junit_prop['invalid_count'] = reports.invalids
     junit_prop['wrong-version_count'] = reports.wrong_version
     junit_prop['wrong-build_count'] = reports.wrong_build
diff --git a/tester/rt/tftp.py b/tester/rt/tftp.py
index 7af3a62..7829e8f 100644
--- a/tester/rt/tftp.py
+++ b/tester/rt/tftp.py
@@ -66,6 +66,7 @@ class tftp(object):
         self.port = 0
         self.exe = None
         self.timeout = None
+        self.test_too_long = None
         self.timer = None
         self.running = False
         self.finished = False
@@ -109,6 +110,15 @@ class tftp(object):
         if self.timeout is not None:
             self.timeout()
 
+    def _test_too_long(self):
+        self._stop()
+        while self.running or not self.finished:
+            self._unlock('_test_too_long')
+            time.sleep(0.1)
+            self._lock('_test_too_long')
+        if self.test_too_long is not None:
+            self.test_too_long()
+
     def _exe_handle(self, req_file, raddress, rport):
         self._lock('_exe_handle')
         exe = self.exe
@@ -166,29 +176,40 @@ class tftp(object):
         self.exe = executable
         if self.console:
             self.console('tftp: exe: %s' % (executable))
-        self.timeout = timeout[1]
+        self.timeout = timeout[2]
+        self.test_too_long = timeout[3]
         self.listener = threading.Thread(target = self._runner,
                                          name = 'tftp-listener')
         self.listener.start()
-        step = 0.5
+        step = 0.25
         period = timeout[0]
+        seconds = timeout[1]
         output_len = self.output_length()
-        while not self.finished and period > 0:
+        while not self.finished and period > 0 and seconds > 0:
+            if not self.running and self.caught:
+                break
             current_length = self.output_length()
             if output_length != current_length:
                 period = timeout[0]
             output_length = current_length
+            if seconds < step:
+                seconds = 0
+            else:
+                seconds -= step
             if period < step:
+                step = period
                 period = 0
             else:
                 period -= step
             self._unlock('_open')
             time.sleep(step)
             self._lock('_open')
-        if not self.finished and period == 0:
-            self._timeout()
+        if not self.finished:
+            if period == 0:
+                self._timeout()
+            elif seconds == 0:
+                self._test_too_long()
         caught = self.caught
-        self.caught = None
         self._init()
         self._unlock('_open')
         if caught is not None:
diff --git a/tester/rtems/testing/testing.mc b/tester/rtems/testing/testing.mc
index 662b352..d03ea6d 100644
--- a/tester/rtems/testing/testing.mc
+++ b/tester/rtems/testing/testing.mc
@@ -51,7 +51,8 @@ _rtbase:              none,    none,     '%{_rtdir}'
 _rtscripts:           none,    none,     '%{_rtbase}/rtems/testing'
 
 # Defaults
-timeout:              none,    none,     '180'
+timeout:              none,    none,     '180'  # seconds
+max_test_period:      none,    none,     '300'  # seconds
 
 # Tests detected as invalid that are valid
 invalid_tests:        none,    none,     '''minimum.exe'''
diff --git a/tester/wscript b/tester/wscript
index e0e4693..ad1cf2d 100644
--- a/tester/wscript
+++ b/tester/wscript
@@ -60,6 +60,7 @@ def build(bld):
                   'rt/config.py',
                   'rt/console.py',
                   'rt/coverage.py',
+                  'rt/exe.py',
                   'rt/gdb.py',
                   'rt/options.py',
                   'rt/report.py',
-- 
2.24.1



More information about the devel mailing list