diff --git a/install.py b/install.py index e92b92e6..e857d7ac 100644 --- a/install.py +++ b/install.py @@ -12969,7 +12969,7 @@ def run_tui(args: argparse.Namespace) -> int: # The result is printed in the terminal on the way out. state.exit_code = state.code if state.code is not None else 1 except Exception as exc: # noqa: BLE001 - if flags["term"] or _closed_terminal(exc, terminal.fd): + if flags["term"] or _closed_terminal(exc, terminal): # The window closed under a screen that was changing, or the # SIGCONT that comes with its SIGHUP put the screen back: the # write failed before the signal was read. That is its exit. @@ -13033,28 +13033,30 @@ def _tui_startup(args: argparse.Namespace) -> tuple[TuiState, list[tuple]] | Non _CLOSED_TERMINAL = frozenset({errno.EIO, errno.EPIPE, errno.ENXIO}) -def _closed_terminal(exc: BaseException, fd: int | None = None) -> bool: +def _closed_terminal(exc: BaseException, terminal: _TuiTerminal | None = None) -> bool: """Whether exc is the terminal gone: an OSError, or a termios.error, whose first argument is the errno, answering EIO, EPIPE or ENXIO, and, - given the terminal's fd, that terminal no longer answering. A failing - SD card answers EIO too, and that read error is kept in a crash log.""" + given the terminal, that terminal no longer answering. A failing SD + card answers EIO too, and that read error is kept in a crash log.""" if isinstance(exc, OSError): closed = exc.errno in _CLOSED_TERMINAL elif type(exc).__name__ == "error" and type(exc).__module__ == "termios": closed = bool(exc.args) and exc.args[0] in _CLOSED_TERMINAL else: closed = False - return closed and (fd is None or _terminal_gone(fd)) + return closed and (terminal is None or _terminal_gone(terminal)) -def _terminal_gone(fd: int) -> bool: - """Whether the terminal at fd hung up: a closed window answers EIO to - tcgetattr, a live one its settings.""" +def _terminal_gone(terminal: _TuiTerminal) -> bool: + """Whether the terminal hung up: a closed window answers EIO to + tcgetattr and to a write, a live one its settings.""" import termios # noqa: PLC0415 try: - termios.tcgetattr(fd) - except (termios.error, OSError): + termios.tcgetattr(terminal.fd) + terminal.stdout.write("") + terminal.stdout.flush() + except (termios.error, OSError, ValueError): return True return False diff --git a/tests/test_install_tui.py b/tests/test_install_tui.py index 1b004c19..8233fc59 100644 --- a/tests/test_install_tui.py +++ b/tests/test_install_tui.py @@ -2576,16 +2576,21 @@ class AReadErrorIsNotAClosedWindow(unittest.TestCase): import pty master, slave = pty.openpty() + screen = open(os.dup(slave), "w", encoding="utf-8") # noqa: SIM115 + terminal = mock.Mock(fd=slave, stdout=screen) try: card = OSError(errno.EIO, "Input/output error", "/media/card/bios/x.bin") - self.assertFalse(install._closed_terminal(card, slave)) + self.assertFalse(install._closed_terminal(card, terminal)) self.assertFalse(install._closed_terminal( - OSError(errno.ENOENT, "No such file"), slave)) + OSError(errno.ENOENT, "No such file"), terminal)) os.close(master) master = -1 - self.assertTrue(install._closed_terminal(card, slave)) - self.assertTrue(install._closed_terminal(termios.error(errno.EIO, "gone"), slave)) + self.assertTrue(install._closed_terminal(card, terminal)) + self.assertTrue(install._closed_terminal(termios.error(errno.EIO, "gone"), + terminal)) finally: + with contextlib.suppress(OSError): + screen.close() os.close(slave) if master >= 0: os.close(master)