lib.Result: Fill "cmd" ctor parameter
Commands executed by ExecContext and its derived classes don't populate the "cmd" parameter of "Result"'s constructor. Fixing that makes for nicer error messages.
Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
parent
0386c351a9
commit
048726a1aa
5 changed files with 66 additions and 54 deletions
|
|
@ -313,7 +313,7 @@ class ExecContext(Base):
|
||||||
await self.open()
|
await self.open()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ret = Result()
|
ret = Result(cmd = cmd)
|
||||||
with self.CallContext(
|
with self.CallContext(
|
||||||
self,
|
self,
|
||||||
title = title,
|
title = title,
|
||||||
|
|
@ -421,7 +421,7 @@ class ExecContext(Base):
|
||||||
# be returned by CallContext and is very much allowed
|
# be returned by CallContext and is very much allowed
|
||||||
assert cmd_input is not None, 'Invalid: cmd_input is None'
|
assert cmd_input is not None, 'Invalid: cmd_input is None'
|
||||||
|
|
||||||
ret = Result()
|
ret = Result(cmd = cmd)
|
||||||
with self.CallContext(
|
with self.CallContext(
|
||||||
self,
|
self,
|
||||||
title = title,
|
title = title,
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,64 @@ class Result:
|
||||||
return ret.strip()
|
return ret.strip()
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
def __try_decode(
|
||||||
|
self,
|
||||||
|
stdxxx: bytes | None,
|
||||||
|
quote = False,
|
||||||
|
truncate: int | None = None,
|
||||||
|
annotate: bool = True,
|
||||||
|
label: str | None = None,
|
||||||
|
) -> str:
|
||||||
|
if label is None:
|
||||||
|
label = ''
|
||||||
|
else:
|
||||||
|
label = f'{label}: '
|
||||||
|
if stdxxx is None:
|
||||||
|
return f'{label}None'
|
||||||
|
try:
|
||||||
|
ret = stdxxx.decode()[:truncate].strip()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
chunk = stdxxx[:truncate]
|
||||||
|
ret = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
|
||||||
|
if (not annotate) or truncate is None or len(stdxxx) <= truncate:
|
||||||
|
return f'{label}"{ret}"' if quote else label + ret
|
||||||
|
ret = '{labe}"{ret} ..."' if quote else '{labe}{ret} ...'
|
||||||
|
ret += f' + {len(stdxxx) - truncate} more bytes'
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __summarize(
|
||||||
|
self,
|
||||||
|
cmd: list[str] | None = None,
|
||||||
|
wd: str | None = None,
|
||||||
|
verbose = True
|
||||||
|
) -> str:
|
||||||
|
if not verbose:
|
||||||
|
ret = f'{self.__status}: '
|
||||||
|
else:
|
||||||
|
from .util import pretty_cmd
|
||||||
|
if cmd is None:
|
||||||
|
cmd = self.__cmd
|
||||||
|
call = ''
|
||||||
|
if cmd is not None:
|
||||||
|
if wd is None:
|
||||||
|
wd = self.__wd
|
||||||
|
call = f'"{pretty_cmd(cmd, wd)}" '
|
||||||
|
ret = (
|
||||||
|
f'Command {call}has not yet exited' if self.__status is None else
|
||||||
|
f'Command {call}has exited with status {self.__status}'
|
||||||
|
)
|
||||||
|
label, stdxxx, truncate = (
|
||||||
|
('stdout', self.__stdout, 40) if self.status == 0
|
||||||
|
else ('stderr', self.__stderr, None)
|
||||||
|
)
|
||||||
|
ret += self.__try_decode(
|
||||||
|
stdxxx, quote = True, truncate = truncate, annotate = True, label = label
|
||||||
|
)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return self.__summarize(verbose = False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self) -> int | None:
|
def status(self) -> int | None:
|
||||||
return self.__status
|
return self.__status
|
||||||
|
|
@ -40,29 +98,6 @@ class Result:
|
||||||
def encoding(self, value: str) -> None:
|
def encoding(self, value: str) -> None:
|
||||||
self.__encoding = value
|
self.__encoding = value
|
||||||
|
|
||||||
def __stdout_footprint(self, quote = False) -> str:
|
|
||||||
if self.__stdout is None:
|
|
||||||
ret = ''
|
|
||||||
else:
|
|
||||||
max_len = 40
|
|
||||||
try:
|
|
||||||
ret = self.stdout_str[:max_len]
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
chunk = self.__stdout[:max_len]
|
|
||||||
ret = ''.join(chr(b) if 32 <= b <= 126 else '.' for b in chunk)
|
|
||||||
if quote:
|
|
||||||
ret = f'"{ret}"'
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
ret = f'{self.__status}:'
|
|
||||||
if self.__status is not None:
|
|
||||||
if self.status != 0:
|
|
||||||
ret += f' err: {self.stderr_str_or_none}'
|
|
||||||
else:
|
|
||||||
ret += f' out: {self.__stdout_footprint(quote=True)}'
|
|
||||||
return ret
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def strip(self) -> bool:
|
def strip(self) -> bool:
|
||||||
return self.__strip
|
return self.__strip
|
||||||
|
|
@ -96,29 +131,6 @@ class Result:
|
||||||
import re
|
import re
|
||||||
return re.search(pattern, err) is not None
|
return re.search(pattern, err) is not None
|
||||||
|
|
||||||
def __summarize(self, cmd: list[str] | None, wd: str | None = None) -> str:
|
|
||||||
from .util import pretty_cmd
|
|
||||||
if cmd is None:
|
|
||||||
cmd = self.__cmd
|
|
||||||
call = ''
|
|
||||||
if cmd is not None:
|
|
||||||
if wd is None:
|
|
||||||
wd = self.__wd
|
|
||||||
call = f'"{pretty_cmd(cmd, wd)}" '
|
|
||||||
ret = (
|
|
||||||
f'Command {call}has not yet exited' if self.__status is None else
|
|
||||||
f'Command {call}has exited with status {self.__status}'
|
|
||||||
)
|
|
||||||
call = pretty_cmd(cmd, wd)
|
|
||||||
if self.status != 0:
|
|
||||||
ret += f' -> stderr="{self.__stderr!r}"'
|
|
||||||
else:
|
|
||||||
if self.__stdout:
|
|
||||||
ret += f' -> stdout has {len(self.__stdout)} bytes'
|
|
||||||
else:
|
|
||||||
ret += ' -> stdout = None'
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def summarize(self, cmd: list[str] | None = None, wd: str | None = None) -> str:
|
def summarize(self, cmd: list[str] | None = None, wd: str | None = None) -> str:
|
||||||
return self.__summarize(cmd, wd)
|
return self.__summarize(cmd, wd)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,7 +83,7 @@ class Local(Base):
|
||||||
|
|
||||||
# PTY merges stdout/stderr
|
# PTY merges stdout/stderr
|
||||||
stdout = b''.join(stdout_chunks) if stdout_chunks else None
|
stdout = b''.join(stdout_chunks) if stdout_chunks else None
|
||||||
return Result(stdout, None, exit_code)
|
return Result(stdout, None, exit_code, cmd = cmd)
|
||||||
|
|
||||||
# -- non-interactive mode
|
# -- non-interactive mode
|
||||||
|
|
||||||
|
|
@ -149,7 +149,7 @@ class Local(Base):
|
||||||
stdout = b''.join(stdout_parts) if stdout_parts else None
|
stdout = b''.join(stdout_parts) if stdout_parts else None
|
||||||
stderr = b''.join(stderr_parts) if stderr_parts else None
|
stderr = b''.join(stderr_parts) if stderr_parts else None
|
||||||
|
|
||||||
return Result(stdout, stderr, exit_code)
|
return Result(stdout, stderr, exit_code, cmd = cmd)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if cwd is not None:
|
if cwd is not None:
|
||||||
|
|
|
||||||
|
|
@ -273,7 +273,7 @@ class AsyncSSH(Base):
|
||||||
)
|
)
|
||||||
|
|
||||||
stdout = b''.join(stdout_parts) if stdout_parts else None
|
stdout = b''.join(stdout_parts) if stdout_parts else None
|
||||||
return Result(stdout, None, exit_code)
|
return Result(stdout, None, exit_code, cmd = cmd)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
if stdin_reader_installed:
|
if stdin_reader_installed:
|
||||||
|
|
@ -353,7 +353,7 @@ class AsyncSSH(Base):
|
||||||
exit_code = completed.returncode if completed.returncode is not None else -1
|
exit_code = completed.returncode if completed.returncode is not None else -1
|
||||||
|
|
||||||
stdout = b''.join(stdout_parts) if stdout_parts else None
|
stdout = b''.join(stdout_parts) if stdout_parts else None
|
||||||
return Result(stdout, None, exit_code)
|
return Result(stdout, None, exit_code, cmd = cmd)
|
||||||
|
|
||||||
async def _run_ssh(
|
async def _run_ssh(
|
||||||
self,
|
self,
|
||||||
|
|
@ -446,7 +446,7 @@ class AsyncSSH(Base):
|
||||||
completed.returncode if completed.returncode is not None else -1
|
completed.returncode if completed.returncode is not None else -1
|
||||||
)
|
)
|
||||||
|
|
||||||
return Result(stdout, stderr, exit_code)
|
return Result(stdout, stderr, exit_code, cmd = cmd)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log(ERR, f'Failed to run command {" ".join(cmd)} ({e})')
|
log(ERR, f'Failed to run command {" ".join(cmd)} ({e})')
|
||||||
|
|
|
||||||
|
|
@ -84,4 +84,4 @@ class Paramiko(Base):
|
||||||
if cmd_input is not None:
|
if cmd_input is not None:
|
||||||
stdin.write(cmd_input)
|
stdin.write(cmd_input)
|
||||||
exit_status = stdout.channel.recv_exit_status()
|
exit_status = stdout.channel.recv_exit_status()
|
||||||
return Result(stdout.read(), stderr.read(), exit_status)
|
return Result(stdout.read(), stderr.read(), exit_status, cmd = cmd)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue