mirror of
https://github.com/ARM-software/devlib.git
synced 2025-01-31 02:00:45 +00:00
Target.execute(): Add .returncode and .output exception attributes
Add Target{Stable,Transient}CalledProcessError exceptions, with an .returncode and .output attributes, raised by Target.execute(), mirroring subprocess.CalledProcessError. This is very useful in client code that uses "exit N" to signal an abnormal condition, and then inspects the output to find out more.
This commit is contained in:
parent
ff57e785f8
commit
0c1878786b
@ -13,6 +13,8 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
|
||||||
class DevlibError(Exception):
|
class DevlibError(Exception):
|
||||||
"""Base class for all Devlib exceptions."""
|
"""Base class for all Devlib exceptions."""
|
||||||
|
|
||||||
@ -69,6 +71,42 @@ class TargetStableError(TargetError, DevlibStableError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TargetCalledProcessError(subprocess.CalledProcessError, TargetError):
|
||||||
|
"""Exception raised when a command executed on the target fails."""
|
||||||
|
def __str__(self):
|
||||||
|
msg = super().__str__()
|
||||||
|
def decode(s):
|
||||||
|
try:
|
||||||
|
s = s.decode()
|
||||||
|
except AttributeError:
|
||||||
|
s = str(s)
|
||||||
|
|
||||||
|
return s.strip()
|
||||||
|
|
||||||
|
if self.stdout is not None and self.stderr is None:
|
||||||
|
out = ['OUTPUT: {}'.format(decode(self.output))]
|
||||||
|
else:
|
||||||
|
out = [
|
||||||
|
'STDOUT: {}'.format(decode(self.output)) if self.output is not None else '',
|
||||||
|
'STDERR: {}'.format(decode(self.stderr)) if self.stderr is not None else '',
|
||||||
|
]
|
||||||
|
|
||||||
|
return '\n'.join((
|
||||||
|
msg,
|
||||||
|
*out,
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
class TargetStableCalledProcessError(TargetCalledProcessError, TargetStableError):
|
||||||
|
"""Variant of :exc:`devlib.exception.TargetCalledProcessError` that indicates a stable error"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TargetTransientCalledProcessError(TargetCalledProcessError, TargetTransientError):
|
||||||
|
"""Variant of :exc:`devlib.exception.TargetCalledProcessError` that indicates a transient error"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class TargetNotRespondingError(TargetTransientError):
|
class TargetNotRespondingError(TargetTransientError):
|
||||||
"""The target is unresponsive."""
|
"""The target is unresponsive."""
|
||||||
pass
|
pass
|
||||||
|
@ -22,7 +22,10 @@ from distutils.dir_util import copy_tree
|
|||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from pipes import quote
|
from pipes import quote
|
||||||
|
|
||||||
from devlib.exception import TargetTransientError, TargetStableError
|
from devlib.exception import (
|
||||||
|
TargetTransientError, TargetStableError,
|
||||||
|
TargetTransientCalledProcessError, TargetStableCalledProcessError
|
||||||
|
)
|
||||||
from devlib.utils.misc import check_output
|
from devlib.utils.misc import check_output
|
||||||
from devlib.connection import ConnectionBase, PopenBackgroundCommand
|
from devlib.connection import ConnectionBase, PopenBackgroundCommand
|
||||||
|
|
||||||
@ -102,12 +105,13 @@ class LocalConnection(ConnectionBase):
|
|||||||
try:
|
try:
|
||||||
stdout, stderr = check_output(command, shell=True, timeout=timeout, ignore=ignore)
|
stdout, stderr = check_output(command, shell=True, timeout=timeout, ignore=ignore)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'.format(
|
cls = TargetTransientCalledProcessError if will_succeed else TargetStableCalledProcessError
|
||||||
e.returncode, command, e.output)
|
raise cls(
|
||||||
if will_succeed:
|
e.returncode,
|
||||||
raise TargetTransientError(message)
|
command,
|
||||||
else:
|
e.output,
|
||||||
raise TargetStableError(message)
|
e.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
# Remove the one-character prompt of sudo -S -p
|
# Remove the one-character prompt of sudo -S -p
|
||||||
if use_sudo and stderr:
|
if use_sudo and stderr:
|
||||||
|
@ -40,7 +40,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from pipes import quote
|
from pipes import quote
|
||||||
|
|
||||||
from devlib.exception import TargetTransientError, TargetStableError, HostError
|
from devlib.exception import TargetTransientError, TargetStableError, HostError, TargetTransientCalledProcessError, TargetStableCalledProcessError
|
||||||
from devlib.utils.misc import check_output, which, ABI_MAP, redirect_streams, get_subprocess
|
from devlib.utils.misc import check_output, which, ABI_MAP, redirect_streams, get_subprocess
|
||||||
from devlib.connection import ConnectionBase, AdbBackgroundCommand, PopenBackgroundCommand, PopenTransferManager
|
from devlib.connection import ConnectionBase, AdbBackgroundCommand, PopenBackgroundCommand, PopenTransferManager
|
||||||
|
|
||||||
@ -336,6 +336,14 @@ class AdbConnection(ConnectionBase):
|
|||||||
try:
|
try:
|
||||||
return adb_shell(self.device, command, timeout, check_exit_code,
|
return adb_shell(self.device, command, timeout, check_exit_code,
|
||||||
as_root, adb_server=self.adb_server, su_cmd=self.su_cmd)
|
as_root, adb_server=self.adb_server, su_cmd=self.su_cmd)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
cls = TargetTransientCalledProcessError if will_succeed else TargetStableCalledProcessError
|
||||||
|
raise cls(
|
||||||
|
e.returncode,
|
||||||
|
command,
|
||||||
|
e.output,
|
||||||
|
e.stderr,
|
||||||
|
)
|
||||||
except TargetStableError as e:
|
except TargetStableError as e:
|
||||||
if will_succeed:
|
if will_succeed:
|
||||||
raise TargetTransientError(e)
|
raise TargetTransientError(e)
|
||||||
@ -559,10 +567,15 @@ def adb_shell(device, command, timeout=None, check_exit_code=False,
|
|||||||
exit_code = exit_code.strip()
|
exit_code = exit_code.strip()
|
||||||
re_search = AM_START_ERROR.findall(output)
|
re_search = AM_START_ERROR.findall(output)
|
||||||
if exit_code.isdigit():
|
if exit_code.isdigit():
|
||||||
if int(exit_code):
|
exit_code = int(exit_code)
|
||||||
message = ('Got exit code {}\nfrom target command: {}\n'
|
if exit_code:
|
||||||
'OUTPUT: {}\nSTDERR: {}\n')
|
raise subprocess.CalledProcessError(
|
||||||
raise TargetStableError(message.format(exit_code, command, output, error))
|
exit_code,
|
||||||
|
command,
|
||||||
|
output,
|
||||||
|
error,
|
||||||
|
)
|
||||||
|
|
||||||
elif re_search:
|
elif re_search:
|
||||||
message = 'Could not start activity; got the following:\n{}'
|
message = 'Could not start activity; got the following:\n{}'
|
||||||
raise TargetStableError(message.format(re_search[0]))
|
raise TargetStableError(message.format(re_search[0]))
|
||||||
|
@ -197,7 +197,7 @@ def check_subprocess_output(process, timeout=None, ignore=None, inputtext=None):
|
|||||||
|
|
||||||
retcode = process.poll()
|
retcode = process.poll()
|
||||||
if retcode and ignore != 'all' and retcode not in ignore:
|
if retcode and ignore != 'all' and retcode not in ignore:
|
||||||
raise subprocess.CalledProcessError(retcode, process.args, output='\n'.join([output, error]))
|
raise subprocess.CalledProcessError(retcode, process.args, output, error)
|
||||||
|
|
||||||
return output, error
|
return output, error
|
||||||
|
|
||||||
|
@ -52,7 +52,10 @@ from pexpect import EOF, TIMEOUT, spawn
|
|||||||
|
|
||||||
# pylint: disable=redefined-builtin,wrong-import-position
|
# pylint: disable=redefined-builtin,wrong-import-position
|
||||||
from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
|
from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
|
||||||
TimeoutError, TargetTransientError)
|
TimeoutError, TargetTransientError,
|
||||||
|
TargetCalledProcessError,
|
||||||
|
TargetTransientCalledProcessError,
|
||||||
|
TargetStableCalledProcessError)
|
||||||
from devlib.utils.misc import (which, strip_bash_colors, check_output,
|
from devlib.utils.misc import (which, strip_bash_colors, check_output,
|
||||||
sanitize_cmd_template, memoized, redirect_streams)
|
sanitize_cmd_template, memoized, redirect_streams)
|
||||||
from devlib.utils.types import boolean
|
from devlib.utils.types import boolean
|
||||||
@ -524,6 +527,8 @@ class SshConnection(SshConnectionBase):
|
|||||||
try:
|
try:
|
||||||
with _handle_paramiko_exceptions(command):
|
with _handle_paramiko_exceptions(command):
|
||||||
exit_code, output = self._execute(command, timeout, as_root, strip_colors)
|
exit_code, output = self._execute(command, timeout, as_root, strip_colors)
|
||||||
|
except TargetCalledProcessError:
|
||||||
|
raise
|
||||||
except TargetStableError as e:
|
except TargetStableError as e:
|
||||||
if will_succeed:
|
if will_succeed:
|
||||||
raise TargetTransientError(e)
|
raise TargetTransientError(e)
|
||||||
@ -531,8 +536,13 @@ class SshConnection(SshConnectionBase):
|
|||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
if check_exit_code and exit_code:
|
if check_exit_code and exit_code:
|
||||||
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'
|
cls = TargetTransientCalledProcessError if will_succeed else TargetStableCalledProcessError
|
||||||
raise TargetStableError(message.format(exit_code, command, output))
|
raise cls(
|
||||||
|
exit_code,
|
||||||
|
command,
|
||||||
|
output,
|
||||||
|
None,
|
||||||
|
)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
|
def background(self, command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, as_root=False):
|
||||||
@ -843,16 +853,23 @@ class TelnetConnection(SshConnectionBase):
|
|||||||
if check_exit_code:
|
if check_exit_code:
|
||||||
try:
|
try:
|
||||||
exit_code = int(exit_code_text)
|
exit_code = int(exit_code_text)
|
||||||
if exit_code:
|
|
||||||
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'
|
|
||||||
raise TargetStableError(message.format(exit_code, command, output))
|
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
logger.warning(
|
raise ValueError(
|
||||||
'Could not get exit code for "{}",\ngot: "{}"'\
|
'Could not get exit code for "{}",\ngot: "{}"'\
|
||||||
.format(command, exit_code_text))
|
.format(command, exit_code_text))
|
||||||
|
if exit_code:
|
||||||
|
cls = TargetTransientCalledProcessError if will_succeed else TargetStableCalledProcessError
|
||||||
|
raise cls(
|
||||||
|
exit_code,
|
||||||
|
command,
|
||||||
|
output,
|
||||||
|
None,
|
||||||
|
)
|
||||||
return output
|
return output
|
||||||
except EOF:
|
except EOF:
|
||||||
raise TargetNotRespondingError('Connection lost.')
|
raise TargetNotRespondingError('Connection lost.')
|
||||||
|
except TargetCalledProcessError:
|
||||||
|
raise
|
||||||
except TargetStableError as e:
|
except TargetStableError as e:
|
||||||
if will_succeed:
|
if will_succeed:
|
||||||
raise TargetTransientError(e)
|
raise TargetTransientError(e)
|
||||||
@ -1130,7 +1147,10 @@ class Gem5Connection(TelnetConnection):
|
|||||||
try:
|
try:
|
||||||
output = self._gem5_shell(command,
|
output = self._gem5_shell(command,
|
||||||
check_exit_code=check_exit_code,
|
check_exit_code=check_exit_code,
|
||||||
as_root=as_root)
|
as_root=as_root,
|
||||||
|
will_succeed=will_succeed)
|
||||||
|
except TargetCalledProcessError:
|
||||||
|
raise
|
||||||
except TargetStableError as e:
|
except TargetStableError as e:
|
||||||
if will_succeed:
|
if will_succeed:
|
||||||
raise TargetTransientError(e)
|
raise TargetTransientError(e)
|
||||||
@ -1364,7 +1384,7 @@ class Gem5Connection(TelnetConnection):
|
|||||||
raise TargetStableError('Path to m5 binary on simulated system is not set!')
|
raise TargetStableError('Path to m5 binary on simulated system is not set!')
|
||||||
self._gem5_shell('{} {}'.format(self.m5_path, command))
|
self._gem5_shell('{} {}'.format(self.m5_path, command))
|
||||||
|
|
||||||
def _gem5_shell(self, command, as_root=False, timeout=None, check_exit_code=True, sync=True): # pylint: disable=R0912
|
def _gem5_shell(self, command, as_root=False, timeout=None, check_exit_code=True, sync=True, will_succeed=False): # pylint: disable=R0912
|
||||||
"""
|
"""
|
||||||
Execute a command in the gem5 shell
|
Execute a command in the gem5 shell
|
||||||
|
|
||||||
@ -1429,11 +1449,17 @@ class Gem5Connection(TelnetConnection):
|
|||||||
sync=False)
|
sync=False)
|
||||||
try:
|
try:
|
||||||
exit_code = int(exit_code_text.split()[0])
|
exit_code = int(exit_code_text.split()[0])
|
||||||
if exit_code:
|
|
||||||
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'
|
|
||||||
raise TargetStableError(message.format(exit_code, command, output))
|
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
gem5_logger.warning('Could not get exit code for "{}",\ngot: "{}"'.format(command, exit_code_text))
|
raise ValueError('Could not get exit code for "{}",\ngot: "{}"'.format(command, exit_code_text))
|
||||||
|
else:
|
||||||
|
if exit_code:
|
||||||
|
cls = TragetTransientCalledProcessError if will_succeed else TargetStableCalledProcessError
|
||||||
|
raise cls(
|
||||||
|
exit_code,
|
||||||
|
command,
|
||||||
|
output,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user