From a22b396a32f1f1ce9b2d7c2c51dd9ea4d6db12fe Mon Sep 17 00:00:00 2001 From: Chris Johns Date: Sat, 7 Oct 2023 14:41:05 +1100 Subject: tester/tftp: Add a session timeout - 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(-) (limited to 'tester') 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() -- cgit v1.2.3