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: