From b10acebf94aedb5f48a7b89c036f2f328668107b Mon Sep 17 00:00:00 2001 From: Rahix Date: Fri, 16 Feb 2024 20:01:13 +0100 Subject: [PATCH] machine: linux: Proper exception when retcode parsing fails A long standing ugly error that tbot throws from time to time is one of the following nature: ValueError: invalid literal for int() with base 10: 'some random fragments of console output' Of course, this doesn't give any clue into what went wrong. This error is a result of unexpected output on the console while tbot is trying to read the return code integer for the last command that it executed. We can do better and raise a more descriptive exception for this case. This will at least give users some pointer into the direction of the problem. The exception now looks like this instead: tbot.error.InvalidRetcodeError: received string 'some random fragments of console output' instead of a return code integer Signed-off-by: Rahix --- tbot/error.py | 23 +++++++++++++++++++++++ tbot/machine/linux/ash.py | 8 +++----- tbot/machine/linux/bash.py | 8 +++----- tbot/machine/linux/util.py | 9 +++++++++ 4 files changed, 38 insertions(+), 10 deletions(-) diff --git a/tbot/error.py b/tbot/error.py index ca7a2de3..406cac13 100644 --- a/tbot/error.py +++ b/tbot/error.py @@ -169,6 +169,29 @@ class ChannelClosedError(MachineError): """ +class InvalidRetcodeError(MachineError): + """ + While trying to fetch the return code of a command, unexpected output was received. + + This error usually indicates some deeper issue with the machine connection. + For example, there could be kernel log messages being printed in between + command output. + + .. versionadded:: UNRELEASED + """ + + def __init__( + self, + host: "machine.Machine", + retcode_str: str, + ) -> None: + self.host = host + self.retcode_str = retcode_str + super().__init__( + f"received string {retcode_str!r} instead of a return code integer" + ) + + class ChannelBorrowedError(ApiViolationError): """ Error type for exceptions when accessing a channel which is currently borrowed. diff --git a/tbot/machine/linux/ash.py b/tbot/machine/linux/ash.py index 5730ce3b..fc0490b3 100644 --- a/tbot/machine/linux/ash.py +++ b/tbot/machine/linux/ash.py @@ -123,10 +123,9 @@ def exec( out = self.ch.read_until_prompt() ev.data["stdout"] = out - self.ch.sendline("echo $?", read_back=True) - retcode = self.ch.read_until_prompt() + retcode = util.posix_fetch_return_code(self.ch, self) - return (int(retcode), out) + return (retcode, out) def exec0( self: Self, *args: typing.Union[str, special.Special[Self], path.Path[Self]] @@ -192,8 +191,7 @@ def __str__(self) -> str: output = proxy_ch.read_until_prompt() ev.data["stdout"] = ev.getvalue() - proxy_ch.sendline("echo $?", read_back=True) - retcode = int(proxy_ch.read_until_prompt()) + retcode = util.posix_fetch_return_code(proxy_ch, self) return (retcode, output) diff --git a/tbot/machine/linux/bash.py b/tbot/machine/linux/bash.py index ffa61bac..db8a7333 100644 --- a/tbot/machine/linux/bash.py +++ b/tbot/machine/linux/bash.py @@ -130,10 +130,9 @@ def exec( out = self.ch.read_until_prompt() ev.data["stdout"] = out - self.ch.sendline("echo $?", read_back=True) - retcode = self.ch.read_until_prompt() + retcode = util.posix_fetch_return_code(self.ch, self) - return (int(retcode), out) + return (retcode, out) def exec0( self: Self, *args: typing.Union[str, special.Special[Self], path.Path[Self]] @@ -199,8 +198,7 @@ def __str__(self) -> str: output = proxy_ch.read_until_prompt() ev.data["stdout"] = ev.getvalue() - proxy_ch.sendline("echo $?", read_back=True) - retcode = int(proxy_ch.read_until_prompt()) + retcode = util.posix_fetch_return_code(proxy_ch, self) return (retcode, output) diff --git a/tbot/machine/linux/util.py b/tbot/machine/linux/util.py index 583d7cd5..ef0361cd 100644 --- a/tbot/machine/linux/util.py +++ b/tbot/machine/linux/util.py @@ -44,6 +44,15 @@ def wait_for_shell(ch: channel.Channel) -> None: timeout = 3.0 +def posix_fetch_return_code(ch: channel.Channel, mach: M) -> int: + ch.sendline("echo $?", read_back=True) + retcode_str = ch.read_until_prompt() + try: + return int(retcode_str) + except ValueError: + raise tbot.error.InvalidRetcodeError(mach, retcode_str) from None + + def posix_environment( mach: M, var: str, value: "typing.Union[str, linux.Path[M], None]" = None ) -> str: