diff --git a/src/fenrirscreenreader/screenDriver/vcsaDriver.py b/src/fenrirscreenreader/screenDriver/vcsaDriver.py index fa3bd550..7013e8d3 100644 --- a/src/fenrirscreenreader/screenDriver/vcsaDriver.py +++ b/src/fenrirscreenreader/screenDriver/vcsaDriver.py @@ -10,6 +10,7 @@ # blink = 5 if attr & 1 else 0 # bold = 1 if attr & 16 else 0 +import errno import fcntl import glob import os @@ -53,6 +54,9 @@ class driver(screenDriver): fgColorValues: Foreground color value mappings hichar: High character mask for Unicode support """ + read_retry_attempts = 10 + read_retry_delay = 0.05 + def __init__(self): screenDriver.__init__(self) self.ListSessions = None @@ -251,24 +255,40 @@ class driver(screenDriver): bytes: File content as bytes """ d = b"" - file.seek(0) - try: - d = file.read() - except Exception as e: - self.env["runtime"]["DebugManager"].write_debug_out( - "vcsaDriver get_screen_text: Error reading file: " + str(e), - debug.DebugLevel.ERROR, - ) + for attempt in range(self.read_retry_attempts + 1): file.seek(0) - while True: - # Read from file - try: - chunk = file.readline(1) - if not chunk: - break - d += chunk - except Exception as e: + try: + return file.read() + except OSError as e: + if ( + e.errno == errno.EINVAL + and attempt < self.read_retry_attempts + ): + time.sleep(self.read_retry_delay) + continue + self.env["runtime"]["DebugManager"].write_debug_out( + "vcsaDriver get_screen_text: Error reading file: " + + str(e), + debug.DebugLevel.ERROR, + ) + break + except Exception as e: + self.env["runtime"]["DebugManager"].write_debug_out( + "vcsaDriver get_screen_text: Error reading file: " + + str(e), + debug.DebugLevel.ERROR, + ) + break + + file.seek(0) + while True: + try: + chunk = file.readline(1) + if not chunk: break + d += chunk + except Exception: + break return d def update_watchdog(self, active, event_queue): diff --git a/tests/unit/test_vcsa_driver.py b/tests/unit/test_vcsa_driver.py index 24b075bb..a2f1609d 100644 --- a/tests/unit/test_vcsa_driver.py +++ b/tests/unit/test_vcsa_driver.py @@ -22,14 +22,49 @@ class BulkReadFailure: return next(self.chunks) -def test_vcsa_read_fallback_stops_at_eof(): +class TransientInvalidArgumentRead: + def __init__(self, payload): + self.payload = payload + self.read_calls = 0 + self.readline_calls = 0 + + def seek(self, offset): + assert offset == 0 + + def read(self): + self.read_calls += 1 + if self.read_calls == 1: + raise OSError(22, "Invalid argument") + return self.payload + + def readline(self, size): + self.readline_calls += 1 + return b"" + + +def build_vcsa_driver(): vcsa_driver = VcsaDriver.__new__(VcsaDriver) vcsa_driver.env = { "runtime": { "DebugManager": Mock(), } } + vcsa_driver.read_retry_delay = 0 + return vcsa_driver + + +def test_vcsa_read_fallback_stops_at_eof(): + vcsa_driver = build_vcsa_driver() screen_file = BulkReadFailure([b"a", b"b", b""]) assert vcsa_driver.read_file(screen_file) == b"ab" assert screen_file.readline_calls == 3 + + +def test_vcsa_read_retries_transient_invalid_argument(): + vcsa_driver = build_vcsa_driver() + screen_file = TransientInvalidArgumentRead(b"\x01\x02\x00\x00x") + + assert vcsa_driver.read_file(screen_file) == b"\x01\x02\x00\x00x" + assert screen_file.read_calls == 2 + assert screen_file.readline_calls == 0