[rtems-tools PATCH v2] tester/tftp: Add a session timeout

chrisj at rtems.org chrisj at rtems.org
Sun Oct 8 06:11:36 UTC 2023


From: Chris Johns <chrisj at rtems.org>

- Fix listener done state

- Finish open with the state as finished

Closes #4959
---
 tester/rt/config.py     | 42 +++++++++++++++++++++---------
 tester/rt/report.py     |  2 +-
 tester/rt/test.py       |  1 +
 tester/rt/tftp.py       | 44 +++++++++++++++++++------------
 tester/rt/tftpserver.py | 57 ++++++++++++++++++++++++++++++++++++-----
 5 files changed, 110 insertions(+), 36 deletions(-)

diff --git a/tester/rt/config.py b/tester/rt/config.py
index 3b12c6c..139e1fa 100644
--- a/tester/rt/config.py
+++ b/tester/rt/config.py
@@ -284,7 +284,12 @@ class file(config.file):
             raise error.general('invalid %tftp port')
         self.kill_on_end = True
         if not self.opts.dry_run():
+            if self.defined('session_timeout'):
+                session_timeout = int(self.expand('%{session_timeout}'))
+            else:
+                session_timeout = 120
             self.process = tester.rt.tftp.tftp(bsp_arch, bsp,
+                                               session_timeout = session_timeout,
                                                trace = self.exe_trace('tftp'))
             if not self.in_error:
                 if self.console:
@@ -415,28 +420,41 @@ class file(config.file):
                 reset_target = True
             else:
                 reset_target = False
-            if self.target_start_regx is not None:
-                if self.target_start_regx.match(text):
-                    if self.test_started:
-                        self._capture_console('target start detected')
+            if ('*** TIMEOUT TIMEOUT' in text or \
+                '*** TEST TOO LONG' in text) and \
+                self.defined('target_reset_on_timeout'):
+                reset_target = True
+            restart = \
+                (self.target_start_regx is not None and self.target_start_regx is not None)
+            if restart:
+                if self.test_started:
+                    self._capture_console('target start detected')
+                    ok_to_kill = True
+                else:
+                    self.restarts += 1
+                    if self.restarts > self.max_restarts:
+                        self._capture_console('target restart maximum count reached')
                         ok_to_kill = True
                     else:
-                        self.restarts += 1
-                        if self.restarts > self.max_restarts:
-                            self._capture_console('target restart maximum count reached')
-                            ok_to_kill = True
-                        else:
-                            self.process.target_restart(self.test_started)
+                        self.process.target_restart(self.test_started)
             if not reset_target and self.target_reset_regx is not None:
                 if self.target_reset_regx.match(text):
                     self._capture_console('target reset condition detected')
                     self._target_command('reset')
                     self.process.target_reset(self.test_started)
             if self.kill_on_end:
-                if not ok_to_kill and '*** END OF TEST ' in text:
+                if not ok_to_kill and \
+                   ('*** END OF TEST ' in text or \
+                    '*** FATAL ***' in text or \
+                    '*** TIMEOUT TIMEOUT' in text or \
+                    '*** TEST TOO LONG' in text):
                     self._capture_console('test end: %s' % (self.test_label))
                     if self.test_label is not None:
-                        ok_to_kill = '*** END OF TEST %s ***' % (self.test_label) in text
+                        ok_to_kill = \
+                            '*** END OF TEST %s ***' % (self.test_label) in text or \
+                            '*** FATAL ***' in text or \
+                            '*** TIMEOUT TIMEOUT' in text or \
+                            '*** TEST TOO LONG' in text
                     self.process.target_end()
             text = [(self.console_prefix, l) for l in text.replace(chr(13), '').splitlines()]
             if self.output is not None:
diff --git a/tester/rt/report.py b/tester/rt/report.py
index a688dc8..642ae73 100644
--- a/tester/rt/report.py
+++ b/tester/rt/report.py
@@ -327,7 +327,7 @@ class report(object):
             for name in results:
                 if results[name]['result'] == state:
                     l += [' %s' % (path.basename(name))]
-            return l
+            return sorted(l)
         l = []
         if self.failed:
             l += ['Failures:']
diff --git a/tester/rt/test.py b/tester/rt/test.py
index 0e22002..db5939b 100644
--- a/tester/rt/test.py
+++ b/tester/rt/test.py
@@ -151,6 +151,7 @@ class test_run(object):
         name = 'test[%s]' % path.basename(self.executable)
         self.thread = threading.Thread(target = self.runner,
                                        name = name)
+        self.thread.daemon = True
         self.thread.start()
 
     def is_alive(self):
diff --git a/tester/rt/tftp.py b/tester/rt/tftp.py
index 5a1c7b7..5d2e74a 100644
--- a/tester/rt/tftp.py
+++ b/tester/rt/tftp.py
@@ -49,7 +49,8 @@ import tester.rt.tftpserver
 class tftp(object):
     '''RTEMS Testing TFTP base.'''
 
-    def __init__(self, bsp_arch, bsp, trace = False):
+    def __init__(self, bsp_arch, bsp, session_timeout, trace = False):
+        self.session_timeout = session_timeout
         self.trace = trace
         self.lock_trace = False
         self.lock = threading.RLock()
@@ -60,7 +61,7 @@ class tftp(object):
     def __del__(self):
         self.kill()
 
-    def _init(self):
+    def _init(self, state = 'reset'):
         self.output_length = None
         self.console = None
         self.server = None
@@ -73,7 +74,7 @@ class tftp(object):
         self.running = False
         self.finished = False
         self.caught = None
-        self.target_state = 'reset'
+        self.target_state = state
 
     def _lock(self, msg):
         if self.lock_trace:
@@ -109,7 +110,8 @@ class tftp(object):
         try:
             if self.server is not None:
                 self.server.stop()
-            self.finished = Finished
+            self.finished = finished
+            self._set_target_state('finished')
         except:
             pass
 
@@ -150,14 +152,15 @@ class tftp(object):
     def _listener(self, exe):
         self.server = tester.rt.tftpserver.tftp_server(host = 'all',
                                                        port = self.port,
+                                                       session_timeout = self.session_timeout,
                                                        timeout = 10,
                                                        forced_file = exe,
                                                        sessions = 1)
         try:
-            if log.tracing:
+            if False and log.tracing:
                 self.server.trace_packets()
             self.server.start()
-            self.server.run()
+            return self.server.run() == 1
         except:
             self.server.stop()
             raise
@@ -169,22 +172,30 @@ class tftp(object):
         self.exe = None
         self._unlock('_runner')
         caught = None
+        retry = 0
+        target_loaded = False
         try:
             self._lock('_runner')
             state = self.target_state
             self._unlock('_runner')
             self._trace('runner: ' + state)
-            while state not in ['shutdown', 'finished']:
-                if state != 'running':
+            while state not in ['shutdown', 'finished', 'timeout']:
+                if state in ['booting', 'running']:
+                    time.sleep(0.25)
+                else:
                     self._trace('listening: begin: ' + state)
-                    self._listener(exe)
+                    target_loaded = self._listener(exe)
                     self._lock('_runner')
-                    if self.target_state == 'booting':
-                        self._set_target_state('loaded')
+                    if target_loaded:
+                        self._set_target_state('booting')
+                    else:
+                        retry += 1
+                        if retry > 1:
+                            self._set_target_state('timeout')
+                            self._timeout()
+                    state = self.target_state
                     self._unlock('_runner')
                     self._trace('listening: end: ' + state)
-                else:
-                    time.sleep(0.25)
                 self._lock('_runner')
                 state = self.target_state
                 self._unlock('_runner')
@@ -214,16 +225,17 @@ class tftp(object):
         self.test_too_long = timeout[3]
         self.opened = True
         self.running = True
+        self._console('tftp: exe: %s' % (executable))
         self.listener = threading.Thread(target = self._runner,
                                          name = 'tftp-listener')
+        self.listener.daemon = True
         self._unlock('_open: start listner')
-        self._console('tftp: exe: %s' % (executable))
         self.listener.start()
         self._lock('_open: start listner')
         step = 0.25
         period = timeout[0]
         seconds = timeout[1]
-        output_len = self.output_length()
+        output_length = self.output_length()
         while not self.finished and period > 0 and seconds > 0:
             if not self.running and self.caught:
                 break
@@ -250,7 +262,7 @@ class tftp(object):
             elif seconds == 0:
                 self._test_too_long()
         caught = self.caught
-        self._init()
+        self._init('finished')
         self._unlock('_open')
         if caught is not None:
             reraise.reraise(*caught)
diff --git a/tester/rt/tftpserver.py b/tester/rt/tftpserver.py
index 92cd1fd..c200dad 100644
--- a/tester/rt/tftpserver.py
+++ b/tester/rt/tftpserver.py
@@ -453,14 +453,13 @@ class udp_handler(socketserver.BaseRequestHandler):
                 raise
             self._notice('] tftp: %d: error: %s: %s' % (index, type(exp), exp))
         self._notice('] tftp: %d: end: %s' % (index, client))
+        self.server.tftp.session_done()
 
     def handle(self):
         '''The UDP server handle method.'''
-        if self.server.tftp.sessions is None \
-           or self.server.tftp.session < self.server.tftp.sessions:
+        if self.server.tftp.sessions_available():
             self.handle_session(self.server.tftp.next_session())
 
-
 class udp_server(socketserver.ThreadingMixIn, socketserver.UDPServer):
     '''UDP server. Default behaviour.'''
 
@@ -474,6 +473,7 @@ class tftp_server(object):
     def __init__(self,
                  host,
                  port,
+                 session_timeout=None,
                  timeout=10,
                  base=None,
                  forced_file=None,
@@ -484,6 +484,7 @@ class tftp_server(object):
         self.notices = False
         self.packet_trace = False
         self.exception_is_raise = False
+        self.session_timeout = session_timeout
         self.timeout = timeout
         self.host = host
         self.port = port
@@ -497,6 +498,7 @@ class tftp_server(object):
             raise error.general('tftp session count is not a number')
         self.sessions = sessions
         self.session = 0
+        self.sessions_done = 0
         self.reader = reader
 
     def __del__(self):
@@ -542,6 +544,8 @@ class tftp_server(object):
     def run(self):
         '''Run the TFTP server for the specified number of sessions.'''
         running = True
+        session_timeout = self.session_timeout
+        last_session = 0
         while running:
             period = 1
             self._lock()
@@ -549,7 +553,7 @@ class tftp_server(object):
                 running = False
                 period = 0
             elif self.sessions is not None:
-                if self.sessions == 0:
+                if self.sessions_done >= self.sessions:
                     running = False
                     period = 0
                 else:
@@ -557,7 +561,24 @@ class tftp_server(object):
             self._unlock()
             if period > 0:
                 time.sleep(period)
+                if session_timeout is not None:
+                    session = self.get_session()
+                    if last_session != session:
+                        last_session = session
+                        session_timeout = self.session_timeout
+                    else:
+                        if session_timeout < period:
+                            session_timeout = 0
+                        else:
+                            session_timeout -= period
+                        if session_timeout == 0:
+                            log.trace('] tftp: server: session timeout')
+                            running = False
         self.stop()
+        self._lock()
+        sessions_done = self.sessions_done
+        self._unlock()
+        return sessions_done
 
     def get_session(self):
         '''Return the session count.'''
@@ -580,6 +601,24 @@ class tftp_server(object):
             self._unlock()
         return count
 
+    def sessions_available(self):
+        '''Return True is there are available sessions.'''
+        available = False
+        self._lock()
+        try:
+            available = self.sessions is None or self.session < self.sessions
+        finally:
+            self._unlock()
+        return available
+
+    def session_done(self):
+        '''Call when a session is done.'''
+        self._lock()
+        try:
+            self.sessions_done += 1
+        finally:
+            self._unlock()
+
     def enable_notices(self):
         '''Call to enable notices. The server is quiet without this call.'''
         self._lock()
@@ -654,10 +693,14 @@ def run(args=sys.argv, command_path=None):
             help='port to bind the server too (default: %(default)s).',
             type=int,
             default='69')
+        argsp.add_argument('-S', '--session-timeout',
+                           help='timeout in seconds, client can override ' \
+                           '(default: %(default)s).',
+                           type = int, default=None)
         argsp.add_argument('-t', '--timeout',
-                           help = 'timeout in seconds, client can override ' \
+                           help='timeout in seconds, client can override ' \
                            '(default: %(default)s).',
-                           type = int, default = '10')
+                           type=int, default='10')
         argsp.add_argument(
             '-b',
             '--base',
@@ -682,7 +725,7 @@ def run(args=sys.argv, command_path=None):
         log.output(log.info(args))
         log.tracing = argopts.trace
 
-        server = tftp_server(argopts.bind, argopts.port, argopts.timeout,
+        server = tftp_server(argopts.bind, argopts.port, argopts.session_timeout, argopts.timeout,
                              argopts.base, argopts.force_file,
                              argopts.sessions)
         server.enable_notices()
-- 
2.37.1



More information about the devel mailing list