1
0
mirror of https://github.com/ARM-software/devlib.git synced 2025-01-31 10:10:46 +00:00

exceptions: Classify transient exceptions

Exceptions such as TargetError can sometimes be raised because of a
network issue, which is useful to distinguish from errors caused by a
missing feature for automated testing environments.

The following exceptions are introduced:
* DevlibStableError: raised when a non-transient error is encountered
    * TargetStableError

* DevlibTransientError: raised when a transient error is encountered,
including timeouts.
    * TargetTransientError

When there is an ambiguity on the type of exception to use, it can be
assumed that the configuration is correct, and therefore it is a
transient error, unless the function is specifically designed to probe a
property of the system. In that case, ambiguity is allowed to be lifted
by assuming a non-transient error, since we expect it to raise an
exception when that property is not met. Such ambiguous case can appear
when checking Android has booted, since we cannot know if this is a
timeout/connection issue, or an actual issue with the Android build or
configuration. Another case are the execute() methods, which can be
expected to fail on purpose. A new parameter will_succeed=False is
added, to automatically turn non transient errors into transient ones if
the caller is 100% sure that the command cannot fail unless there is an
environment issue that is outside of the scope controlled by the user.

devlib now never raises TargetError directly, but one of
TargetStableError or TargetTransientError. External code can therefore
rely on all (indirect) instances TargetError to be in either category.
Most existing uses of TargetError are replaced by TargetStableError.
This commit is contained in:
Douglas RAILLARD 2018-06-20 15:04:12 +01:00 committed by Marc Bonnici
parent d6d322c8ac
commit 511d478164
20 changed files with 209 additions and 155 deletions

View File

@ -15,7 +15,7 @@
from devlib.target import Target, LinuxTarget, AndroidTarget, LocalLinuxTarget, ChromeOsTarget from devlib.target import Target, LinuxTarget, AndroidTarget, LocalLinuxTarget, ChromeOsTarget
from devlib.host import PACKAGE_BIN_DIRECTORY from devlib.host import PACKAGE_BIN_DIRECTORY
from devlib.exception import DevlibError, TargetError, HostError, TargetNotRespondingError from devlib.exception import DevlibError, DevlibTransientError, DevlibStableError, TargetError, TargetTransientError, TargetStableError, TargetNotRespondingError, HostError
from devlib.module import Module, HardRestModule, BootModule, FlashModule from devlib.module import Module, HardRestModule, BootModule, FlashModule
from devlib.module import get_module, register_module from devlib.module import get_module, register_module

View File

@ -22,12 +22,43 @@ class DevlibError(Exception):
return str(self) return str(self)
class DevlibStableError(DevlibError):
"""Non transient target errors, that are not subject to random variations
in the environment and can be reliably linked to for example a missing
feature on a target."""
pass
class DevlibTransientError(DevlibError):
"""Exceptions inheriting from ``DevlibTransientError`` represent random
transient events that are usually related to issues in the environment, as
opposed to programming errors, for example network failures or
timeout-related exceptions. When the error could come from
indistinguishable transient or non-transient issue, it can generally be
assumed that the configuration is correct and therefore, a transient
exception is raised."""
pass
class TargetError(DevlibError): class TargetError(DevlibError):
"""An error has occured on the target""" """An error has occured on the target"""
pass pass
class TargetNotRespondingError(DevlibError): class TargetTransientError(TargetError, DevlibTransientError):
"""Transient target errors that can happen randomly when everything is
properly configured."""
pass
class TargetStableError(TargetError, DevlibStableError):
"""Non-transient target errors that can be linked to a programming error or
a configuration issue, and is not influenced by non-controllable parameters
such as network issues."""
pass
class TargetNotRespondingError(TargetTransientError):
"""The target is unresponsive.""" """The target is unresponsive."""
pass pass
@ -38,7 +69,7 @@ class HostError(DevlibError):
# pylint: disable=redefined-builtin # pylint: disable=redefined-builtin
class TimeoutError(DevlibError): class TimeoutError(DevlibTransientError):
"""Raised when a subprocess command times out. This is basically a ``DevlibError``-derived version """Raised when a subprocess command times out. This is basically a ``DevlibError``-derived version
of ``subprocess.CalledProcessError``, the thinking being that while a timeout could be due to of ``subprocess.CalledProcessError``, the thinking being that while a timeout could be due to
programming error (e.g. not setting long enough timers), it is often due to some failure in the programming error (e.g. not setting long enough timers), it is often due to some failure in the

View File

@ -20,7 +20,7 @@ import subprocess
import logging import logging
from getpass import getpass from getpass import getpass
from devlib.exception import TargetError from devlib.exception import TargetTransientError, TargetStableError
from devlib.utils.misc import check_output from devlib.utils.misc import check_output
PACKAGE_BIN_DIRECTORY = os.path.join(os.path.dirname(__file__), 'bin') PACKAGE_BIN_DIRECTORY = os.path.join(os.path.dirname(__file__), 'bin')
@ -59,11 +59,11 @@ class LocalConnection(object):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def execute(self, command, timeout=None, check_exit_code=True, def execute(self, command, timeout=None, check_exit_code=True,
as_root=False, strip_colors=True): as_root=False, strip_colors=True, will_succeed=False):
self.logger.debug(command) self.logger.debug(command)
if as_root: if as_root:
if self.unrooted: if self.unrooted:
raise TargetError('unrooted') raise TargetStableError('unrooted')
password = self._get_password() password = self._get_password()
command = 'echo \'{}\' | sudo -S '.format(password) + command command = 'echo \'{}\' | sudo -S '.format(password) + command
ignore = None if check_exit_code else 'all' ignore = None if check_exit_code else 'all'
@ -72,12 +72,15 @@ class LocalConnection(object):
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'.format( message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'.format(
e.returncode, command, e.output) e.returncode, command, e.output)
raise TargetError(message) if will_succeed:
raise TargetTransientError(message)
else:
raise TargetStableError(message)
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):
if as_root: if as_root:
if self.unrooted: if self.unrooted:
raise TargetError('unrooted') raise TargetStableError('unrooted')
password = self._get_password() password = self._get_password()
command = 'echo \'{}\' | sudo -S '.format(password) + command command = 'echo \'{}\' | sudo -S '.format(password) + command
return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True)

View File

@ -16,7 +16,7 @@ from __future__ import division
from devlib.platform.gem5 import Gem5SimulationPlatform from devlib.platform.gem5 import Gem5SimulationPlatform
from devlib.instrument import Instrument, CONTINUOUS, MeasurementsCsv from devlib.instrument import Instrument, CONTINUOUS, MeasurementsCsv
from devlib.exception import TargetError from devlib.exception import TargetStableError
from devlib.utils.csvutil import csvwriter from devlib.utils.csvutil import csvwriter
@ -36,9 +36,9 @@ class Gem5PowerInstrument(Instrument):
system.cluster0.cores0.power_model.static_power system.cluster0.cores0.power_model.static_power
''' '''
if not isinstance(target.platform, Gem5SimulationPlatform): if not isinstance(target.platform, Gem5SimulationPlatform):
raise TargetError('Gem5PowerInstrument requires a gem5 platform') raise TargetStableError('Gem5PowerInstrument requires a gem5 platform')
if not target.has('gem5stats'): if not target.has('gem5stats'):
raise TargetError('Gem5StatsModule is not loaded') raise TargetStableError('Gem5StatsModule is not loaded')
super(Gem5PowerInstrument, self).__init__(target) super(Gem5PowerInstrument, self).__init__(target)
# power_sites is assumed to be a list later # power_sites is assumed to be a list later

View File

@ -16,7 +16,7 @@ from __future__ import division
import re import re
from devlib.instrument import Instrument, Measurement, INSTANTANEOUS from devlib.instrument import Instrument, Measurement, INSTANTANEOUS
from devlib.exception import TargetError from devlib.exception import TargetStableError
class HwmonInstrument(Instrument): class HwmonInstrument(Instrument):
@ -35,7 +35,7 @@ class HwmonInstrument(Instrument):
def __init__(self, target): def __init__(self, target):
if not hasattr(target, 'hwmon'): if not hasattr(target, 'hwmon'):
raise TargetError('Target does not support HWMON') raise TargetStableError('Target does not support HWMON')
super(HwmonInstrument, self).__init__(target) super(HwmonInstrument, self).__init__(target)
self.logger.debug('Discovering available HWMON sensors...') self.logger.debug('Discovering available HWMON sensors...')

View File

@ -22,7 +22,7 @@ from collections import defaultdict
from future.moves.itertools import zip_longest from future.moves.itertools import zip_longest
from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS from devlib.instrument import Instrument, MeasurementsCsv, CONTINUOUS
from devlib.exception import TargetError, HostError from devlib.exception import TargetStableError, HostError
from devlib.utils.android import ApkInfo from devlib.utils.android import ApkInfo
from devlib.utils.csvutil import csvwriter from devlib.utils.csvutil import csvwriter
@ -84,7 +84,7 @@ class NetstatsInstrument(Instrument):
""" """
if target.os != 'android': if target.os != 'android':
raise TargetError('netstats insturment only supports Android targets') raise TargetStableError('netstats insturment only supports Android targets')
if apk is None: if apk is None:
apk = os.path.join(THIS_DIR, 'netstats.apk') apk = os.path.join(THIS_DIR, 'netstats.apk')
if not os.path.isfile(apk): if not os.path.isfile(apk):

View File

@ -18,7 +18,7 @@ import re
from collections import namedtuple from collections import namedtuple
from devlib.module import Module from devlib.module import Module
from devlib.exception import TargetError from devlib.exception import TargetStableError
from devlib.utils.misc import list_to_ranges, isiterable from devlib.utils.misc import list_to_ranges, isiterable
from devlib.utils.types import boolean from devlib.utils.types import boolean
@ -281,7 +281,7 @@ class CGroup(object):
self.target.execute('[ -d {0} ]'\ self.target.execute('[ -d {0} ]'\
.format(self.directory), as_root=True) .format(self.directory), as_root=True)
return True return True
except TargetError: except TargetStableError:
return False return False
def get(self): def get(self):
@ -319,7 +319,7 @@ class CGroup(object):
# Set the attribute value # Set the attribute value
try: try:
self.target.write_value(path, attrs[idx]) self.target.write_value(path, attrs[idx])
except TargetError: except TargetStableError:
# Check if the error is due to a non-existing attribute # Check if the error is due to a non-existing attribute
attrs = self.get() attrs = self.get()
if idx not in attrs: if idx not in attrs:
@ -389,9 +389,9 @@ class CgroupsModule(Module):
controller = Controller(ss.name, hid, hierarchy[hid]) controller = Controller(ss.name, hid, hierarchy[hid])
try: try:
controller.mount(self.target, self.cgroup_root) controller.mount(self.target, self.cgroup_root)
except TargetError: except TargetStableError:
message = 'Failed to mount "{}" controller' message = 'Failed to mount "{}" controller'
raise TargetError(message.format(controller.kind)) raise TargetStableError(message.format(controller.kind))
self.logger.info(' %-12s : %s', controller.kind, self.logger.info(' %-12s : %s', controller.kind,
controller.mount_point) controller.mount_point)
self.controllers[ss.name] = controller self.controllers[ss.name] = controller

View File

@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
# #
from devlib.module import Module from devlib.module import Module
from devlib.exception import TargetError from devlib.exception import TargetStableError
from devlib.utils.misc import memoized from devlib.utils.misc import memoized
@ -82,7 +82,7 @@ class CpufreqModule(Module):
Setting the governor on any core in a cluster will also set it on all Setting the governor on any core in a cluster will also set it on all
other cores in that cluster. other cores in that cluster.
:raises: TargetError if governor is not supported by the CPU, or if, :raises: TargetStableError if governor is not supported by the CPU, or if,
for some reason, the governor could not be set. for some reason, the governor could not be set.
""" """
@ -90,7 +90,7 @@ class CpufreqModule(Module):
cpu = 'cpu{}'.format(cpu) cpu = 'cpu{}'.format(cpu)
supported = self.list_governors(cpu) supported = self.list_governors(cpu)
if governor not in supported: if governor not in supported:
raise TargetError('Governor {} not supported for cpu {}'.format(governor, cpu)) raise TargetStableError('Governor {} not supported for cpu {}'.format(governor, cpu))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_governor'.format(cpu)
self.target.write_value(sysfile, governor) self.target.write_value(sysfile, governor)
self.set_governor_tunables(cpu, governor, **kwargs) self.set_governor_tunables(cpu, governor, **kwargs)
@ -104,11 +104,11 @@ class CpufreqModule(Module):
try: try:
tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor) tunables_path = '/sys/devices/system/cpu/{}/cpufreq/{}'.format(cpu, governor)
self._governor_tunables[governor] = self.target.list_directory(tunables_path) self._governor_tunables[governor] = self.target.list_directory(tunables_path)
except TargetError: # probably an older kernel except TargetStableError: # probably an older kernel
try: try:
tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor) tunables_path = '/sys/devices/system/cpu/cpufreq/{}'.format(governor)
self._governor_tunables[governor] = self.target.list_directory(tunables_path) self._governor_tunables[governor] = self.target.list_directory(tunables_path)
except TargetError: # governor does not support tunables except TargetStableError: # governor does not support tunables
self._governor_tunables[governor] = [] self._governor_tunables[governor] = []
return self._governor_tunables[governor] return self._governor_tunables[governor]
@ -122,7 +122,7 @@ class CpufreqModule(Module):
try: try:
path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
tunables[tunable] = self.target.read_value(path) tunables[tunable] = self.target.read_value(path)
except TargetError: # May be an older kernel except TargetStableError: # May be an older kernel
path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable) path = '/sys/devices/system/cpu/cpufreq/{}/{}'.format(governor, tunable)
tunables[tunable] = self.target.read_value(path) tunables[tunable] = self.target.read_value(path)
return tunables return tunables
@ -140,7 +140,7 @@ class CpufreqModule(Module):
The rest should be keyword parameters mapping tunable name onto the value to The rest should be keyword parameters mapping tunable name onto the value to
be set for it. be set for it.
:raises: TargetError if governor specified is not a valid governor name, or if :raises: TargetStableError if governor specified is not a valid governor name, or if
a tunable specified is not valid for the governor, or if could not set a tunable specified is not valid for the governor, or if could not set
tunable. tunable.
@ -155,7 +155,7 @@ class CpufreqModule(Module):
path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable) path = '/sys/devices/system/cpu/{}/cpufreq/{}/{}'.format(cpu, governor, tunable)
try: try:
self.target.write_value(path, value) self.target.write_value(path, value)
except TargetError: except TargetStableError:
if self.target.file_exists(path): if self.target.file_exists(path):
# File exists but we did something wrong # File exists but we did something wrong
raise raise
@ -165,7 +165,7 @@ class CpufreqModule(Module):
else: else:
message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu) message = 'Unexpected tunable {} for governor {} on {}.\n'.format(tunable, governor, cpu)
message += 'Available tunables are: {}'.format(valid_tunables) message += 'Available tunables are: {}'.format(valid_tunables)
raise TargetError(message) raise TargetStableError(message)
@memoized @memoized
def list_frequencies(self, cpu): def list_frequencies(self, cpu):
@ -177,14 +177,14 @@ class CpufreqModule(Module):
cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu) cmd = 'cat /sys/devices/system/cpu/{}/cpufreq/scaling_available_frequencies'.format(cpu)
output = self.target.execute(cmd) output = self.target.execute(cmd)
available_frequencies = list(map(int, output.strip().split())) # pylint: disable=E1103 available_frequencies = list(map(int, output.strip().split())) # pylint: disable=E1103
except TargetError: except TargetStableError:
# On some devices scaling_frequencies is not generated. # On some devices scaling_frequencies is not generated.
# http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html # http://adrynalyne-teachtofish.blogspot.co.uk/2011/11/how-to-enable-scalingavailablefrequenci.html
# Fall back to parsing stats/time_in_state # Fall back to parsing stats/time_in_state
path = '/sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu) path = '/sys/devices/system/cpu/{}/cpufreq/stats/time_in_state'.format(cpu)
try: try:
out_iter = iter(self.target.read_value(path).split()) out_iter = iter(self.target.read_value(path).split())
except TargetError: except TargetStableError:
if not self.target.file_exists(path): if not self.target.file_exists(path):
# Probably intel_pstate. Can't get available freqs. # Probably intel_pstate. Can't get available freqs.
return [] return []
@ -219,7 +219,7 @@ class CpufreqModule(Module):
try to read the minimum frequency and the following exception will be try to read the minimum frequency and the following exception will be
raised :: raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
if isinstance(cpu, int): if isinstance(cpu, int):
@ -239,7 +239,7 @@ class CpufreqModule(Module):
on the device. on the device.
:raises: TargetError if the frequency is not supported by the CPU, or if, for :raises: TargetStableError if the frequency is not supported by the CPU, or if, for
some reason, frequency could not be set. some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer. :raises: ValueError if ``frequency`` is not an integer.
@ -250,7 +250,7 @@ class CpufreqModule(Module):
try: try:
value = int(frequency) value = int(frequency)
if exact and available_frequencies and value not in available_frequencies: if exact and available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, raise TargetStableError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
value, value,
available_frequencies)) available_frequencies))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_min_freq'.format(cpu)
@ -266,7 +266,7 @@ class CpufreqModule(Module):
try to read the current frequency and the following exception will be try to read the current frequency and the following exception will be
raised :: raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
if isinstance(cpu, int): if isinstance(cpu, int):
@ -288,7 +288,7 @@ class CpufreqModule(Module):
on the device (if it exists). on the device (if it exists).
:raises: TargetError if the frequency is not supported by the CPU, or if, for :raises: TargetStableError if the frequency is not supported by the CPU, or if, for
some reason, frequency could not be set. some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer. :raises: ValueError if ``frequency`` is not an integer.
@ -300,11 +300,11 @@ class CpufreqModule(Module):
if exact: if exact:
available_frequencies = self.list_frequencies(cpu) available_frequencies = self.list_frequencies(cpu)
if available_frequencies and value not in available_frequencies: if available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, raise TargetStableError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
value, value,
available_frequencies)) available_frequencies))
if self.get_governor(cpu) != 'userspace': if self.get_governor(cpu) != 'userspace':
raise TargetError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu)) raise TargetStableError('Can\'t set {} frequency; governor must be "userspace"'.format(cpu))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_setspeed'.format(cpu)
self.target.write_value(sysfile, value, verify=False) self.target.write_value(sysfile, value, verify=False)
except ValueError: except ValueError:
@ -318,7 +318,7 @@ class CpufreqModule(Module):
try to read the maximum frequency and the following exception will be try to read the maximum frequency and the following exception will be
raised :: raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
if isinstance(cpu, int): if isinstance(cpu, int):
cpu = 'cpu{}'.format(cpu) cpu = 'cpu{}'.format(cpu)
@ -337,7 +337,7 @@ class CpufreqModule(Module):
on the device. on the device.
:raises: TargetError if the frequency is not supported by the CPU, or if, for :raises: TargetStableError if the frequency is not supported by the CPU, or if, for
some reason, frequency could not be set. some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer. :raises: ValueError if ``frequency`` is not an integer.
@ -348,7 +348,7 @@ class CpufreqModule(Module):
try: try:
value = int(frequency) value = int(frequency)
if exact and available_frequencies and value not in available_frequencies: if exact and available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu, raise TargetStableError('Can\'t set {} frequency to {}\nmust be in {}'.format(cpu,
value, value,
available_frequencies)) available_frequencies))
sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu) sysfile = '/sys/devices/system/cpu/{}/cpufreq/scaling_max_freq'.format(cpu)
@ -409,13 +409,13 @@ class CpufreqModule(Module):
return self.target._execute_util( return self.target._execute_util(
'cpufreq_set_all_governors {}'.format(governor), 'cpufreq_set_all_governors {}'.format(governor),
as_root=True) as_root=True)
except TargetError as e: except TargetStableError as e:
if ("echo: I/O error" in str(e) or if ("echo: I/O error" in str(e) or
"write error: Invalid argument" in str(e)): "write error: Invalid argument" in str(e)):
cpus_unsupported = [c for c in self.target.list_online_cpus() cpus_unsupported = [c for c in self.target.list_online_cpus()
if governor not in self.list_governors(c)] if governor not in self.list_governors(c)]
raise TargetError("Governor {} unsupported for CPUs {}".format( raise TargetStableError("Governor {} unsupported for CPUs {}".format(
governor, cpus_unsupported)) governor, cpus_unsupported))
else: else:
raise raise

View File

@ -13,7 +13,7 @@
# limitations under the License. # limitations under the License.
# #
from devlib.module import Module from devlib.module import Module
from devlib.exception import TargetError from devlib.exception import TargetStableError
from devlib.utils.misc import memoized from devlib.utils.misc import memoized
class DevfreqModule(Module): class DevfreqModule(Module):
@ -64,13 +64,13 @@ class DevfreqModule(Module):
Additional keyword arguments can be used to specify governor tunables for Additional keyword arguments can be used to specify governor tunables for
governors that support them. governors that support them.
:raises: TargetError if governor is not supported by the device, or if, :raises: TargetStableError if governor is not supported by the device, or if,
for some reason, the governor could not be set. for some reason, the governor could not be set.
""" """
supported = self.list_governors(device) supported = self.list_governors(device)
if governor not in supported: if governor not in supported:
raise TargetError('Governor {} not supported for device {}'.format(governor, device)) raise TargetStableError('Governor {} not supported for device {}'.format(governor, device))
sysfile = '/sys/class/devfreq/{}/governor'.format(device) sysfile = '/sys/class/devfreq/{}/governor'.format(device)
self.target.write_value(sysfile, governor) self.target.write_value(sysfile, governor)
@ -94,7 +94,7 @@ class DevfreqModule(Module):
will try to read the minimum frequency and the following exception will will try to read the minimum frequency and the following exception will
be raised :: be raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
sysfile = '/sys/class/devfreq/{}/min_freq'.format(device) sysfile = '/sys/class/devfreq/{}/min_freq'.format(device)
@ -112,7 +112,7 @@ class DevfreqModule(Module):
on the device. on the device.
:raises: TargetError if the frequency is not supported by the device, or if, for :raises: TargetStableError if the frequency is not supported by the device, or if, for
some reason, frequency could not be set. some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer. :raises: ValueError if ``frequency`` is not an integer.
@ -121,7 +121,7 @@ class DevfreqModule(Module):
try: try:
value = int(frequency) value = int(frequency)
if exact and available_frequencies and value not in available_frequencies: if exact and available_frequencies and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(device, raise TargetStableError('Can\'t set {} frequency to {}\nmust be in {}'.format(device,
value, value,
available_frequencies)) available_frequencies))
sysfile = '/sys/class/devfreq/{}/min_freq'.format(device) sysfile = '/sys/class/devfreq/{}/min_freq'.format(device)
@ -137,7 +137,7 @@ class DevfreqModule(Module):
will try to read the current frequency and the following exception will will try to read the current frequency and the following exception will
be raised :: be raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
sysfile = '/sys/class/devfreq/{}/cur_freq'.format(device) sysfile = '/sys/class/devfreq/{}/cur_freq'.format(device)
@ -151,7 +151,7 @@ class DevfreqModule(Module):
try to read the maximum frequency and the following exception will be try to read the maximum frequency and the following exception will be
raised :: raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
sysfile = '/sys/class/devfreq/{}/max_freq'.format(device) sysfile = '/sys/class/devfreq/{}/max_freq'.format(device)
return self.target.read_int(sysfile) return self.target.read_int(sysfile)
@ -168,7 +168,7 @@ class DevfreqModule(Module):
on the device. on the device.
:raises: TargetError if the frequency is not supported by the device, or :raises: TargetStableError if the frequency is not supported by the device, or
if, for some reason, frequency could not be set. if, for some reason, frequency could not be set.
:raises: ValueError if ``frequency`` is not an integer. :raises: ValueError if ``frequency`` is not an integer.
@ -180,7 +180,7 @@ class DevfreqModule(Module):
raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency)) raise ValueError('Frequency must be an integer; got: "{}"'.format(frequency))
if exact and value not in available_frequencies: if exact and value not in available_frequencies:
raise TargetError('Can\'t set {} frequency to {}\nmust be in {}'.format(device, raise TargetStableError('Can\'t set {} frequency to {}\nmust be in {}'.format(device,
value, value,
available_frequencies)) available_frequencies))
sysfile = '/sys/class/devfreq/{}/max_freq'.format(device) sysfile = '/sys/class/devfreq/{}/max_freq'.format(device)
@ -202,13 +202,13 @@ class DevfreqModule(Module):
try: try:
return self.target._execute_util( # pylint: disable=protected-access return self.target._execute_util( # pylint: disable=protected-access
'devfreq_set_all_governors {}'.format(governor), as_root=True) 'devfreq_set_all_governors {}'.format(governor), as_root=True)
except TargetError as e: except TargetStableError as e:
if ("echo: I/O error" in str(e) or if ("echo: I/O error" in str(e) or
"write error: Invalid argument" in str(e)): "write error: Invalid argument" in str(e)):
devs_unsupported = [d for d in self.target.list_devices() devs_unsupported = [d for d in self.target.list_devices()
if governor not in self.list_governors(d)] if governor not in self.list_governors(d)]
raise TargetError("Governor {} unsupported for devices {}".format( raise TargetStableError("Governor {} unsupported for devices {}".format(
governor, devs_unsupported)) governor, devs_unsupported))
else: else:
raise raise

View File

@ -17,7 +17,7 @@ import sys
import os.path import os.path
from collections import defaultdict from collections import defaultdict
from devlib.exception import TargetError, HostError from devlib.exception import TargetStableError, HostError
from devlib.module import Module from devlib.module import Module
from devlib.platform.gem5 import Gem5SimulationPlatform from devlib.platform.gem5 import Gem5SimulationPlatform
from devlib.utils.gem5 import iter_statistics_dump, GEM5STATS_ROI_NUMBER from devlib.utils.gem5 import iter_statistics_dump, GEM5STATS_ROI_NUMBER
@ -87,13 +87,13 @@ class Gem5StatsModule(Module):
if label not in self.rois: if label not in self.rois:
raise KeyError('Incorrect ROI label: {}'.format(label)) raise KeyError('Incorrect ROI label: {}'.format(label))
if not self.rois[label].start(): if not self.rois[label].start():
raise TargetError('ROI {} was already running'.format(label)) raise TargetStableError('ROI {} was already running'.format(label))
def roi_end(self, label): def roi_end(self, label):
if label not in self.rois: if label not in self.rois:
raise KeyError('Incorrect ROI label: {}'.format(label)) raise KeyError('Incorrect ROI label: {}'.format(label))
if not self.rois[label].stop(): if not self.rois[label].stop():
raise TargetError('ROI {} was not running'.format(label)) raise TargetStableError('ROI {} was not running'.format(label))
def start_periodic_dump(self, delay_ns=0, period_ns=10000000): def start_periodic_dump(self, delay_ns=0, period_ns=10000000):
# Default period is 10ms because it's roughly what's needed to have # Default period is 10ms because it's roughly what's needed to have

View File

@ -29,7 +29,7 @@
import re import re
from devlib.module import Module from devlib.module import Module
from devlib.exception import TargetError from devlib.exception import TargetStableError
from devlib.utils.misc import memoized from devlib.utils.misc import memoized
class GpufreqModule(Module): class GpufreqModule(Module):
@ -56,7 +56,7 @@ class GpufreqModule(Module):
def set_governor(self, governor): def set_governor(self, governor):
if governor not in self.governors: if governor not in self.governors:
raise TargetError('Governor {} not supported for gpu'.format(governor)) raise TargetStableError('Governor {} not supported for gpu'.format(governor))
self.target.write_value("/sys/kernel/gpu/gpu_governor", governor) self.target.write_value("/sys/kernel/gpu/gpu_governor", governor)
def get_frequencies(self): def get_frequencies(self):
@ -73,7 +73,7 @@ class GpufreqModule(Module):
try to read the current frequency and the following exception will be try to read the current frequency and the following exception will be
raised :: raised ::
:raises: TargetError if for some reason the frequency could not be read. :raises: TargetStableError if for some reason the frequency could not be read.
""" """
return int(self.target.read_value("/sys/kernel/gpu/gpu_clock")) return int(self.target.read_value("/sys/kernel/gpu/gpu_clock"))

View File

@ -15,7 +15,7 @@
import re import re
from collections import defaultdict from collections import defaultdict
from devlib import TargetError from devlib import TargetStableError
from devlib.module import Module from devlib.module import Module
from devlib.utils.types import integer from devlib.utils.types import integer
@ -118,7 +118,7 @@ class HwmonModule(Module):
def probe(target): def probe(target):
try: try:
target.list_directory(HWMON_ROOT, as_root=target.is_rooted) target.list_directory(HWMON_ROOT, as_root=target.is_rooted)
except TargetError: except TargetStableError:
# Doesn't exist or no permissions # Doesn't exist or no permissions
return False return False
return True return True

View File

@ -20,7 +20,7 @@ import shutil
from subprocess import CalledProcessError from subprocess import CalledProcessError
from devlib.module import HardRestModule, BootModule, FlashModule from devlib.module import HardRestModule, BootModule, FlashModule
from devlib.exception import TargetError, HostError from devlib.exception import TargetError, TargetStableError, HostError
from devlib.utils.serial_port import open_serial_connection, pulse_dtr, write_characters from devlib.utils.serial_port import open_serial_connection, pulse_dtr, write_characters
from devlib.utils.uefi import UefiMenu, UefiConfig from devlib.utils.uefi import UefiMenu, UefiConfig
from devlib.utils.uboot import UbootMenu from devlib.utils.uboot import UbootMenu
@ -89,7 +89,7 @@ class VexpressReboottxtHardReset(HardRestModule):
try: try:
if self.target.is_connected: if self.target.is_connected:
self.target.execute('sync') self.target.execute('sync')
except TargetError: except (TargetError, CalledProcessError):
pass pass
if not os.path.exists(self.path): if not os.path.exists(self.path):
@ -225,7 +225,7 @@ class VexpressUefiShellBoot(VexpressBootModule):
try: try:
menu.select(self.uefi_entry) menu.select(self.uefi_entry)
except LookupError: except LookupError:
raise TargetError('Did not see "{}" UEFI entry.'.format(self.uefi_entry)) raise TargetStableError('Did not see "{}" UEFI entry.'.format(self.uefi_entry))
tty.expect(self.efi_shell_prompt, timeout=self.timeout) tty.expect(self.efi_shell_prompt, timeout=self.timeout)
if self.bootargs: if self.bootargs:
tty.sendline('') # stop default boot tty.sendline('') # stop default boot
@ -344,7 +344,7 @@ class VersatileExpressFlashModule(FlashModule):
os.system('sync') os.system('sync')
except (IOError, OSError) as e: except (IOError, OSError) as e:
msg = 'Could not deploy images to {}; got: {}' msg = 'Could not deploy images to {}; got: {}'
raise TargetError(msg.format(self.vemsd_mount, e)) raise TargetStableError(msg.format(self.vemsd_mount, e))
self.target.boot() self.target.boot()
self.target.connect(timeout=30) self.target.connect(timeout=30)
@ -390,4 +390,4 @@ def wait_for_vemsd(vemsd_mount, tty, mcc_prompt=DEFAULT_MCC_PROMPT, short_delay=
time.sleep(short_delay * 3) time.sleep(short_delay * 3)
if os.path.exists(path): if os.path.exists(path):
return return
raise TargetError('Could not mount {}'.format(vemsd_mount)) raise TargetStableError('Could not mount {}'.format(vemsd_mount))

View File

@ -19,7 +19,7 @@ import tempfile
import time import time
import pexpect import pexpect
from devlib.exception import TargetError, HostError from devlib.exception import TargetStableError, HostError
from devlib.host import PACKAGE_BIN_DIRECTORY from devlib.host import PACKAGE_BIN_DIRECTORY
from devlib.instrument import (Instrument, InstrumentChannel, MeasurementsCsv, from devlib.instrument import (Instrument, InstrumentChannel, MeasurementsCsv,
Measurement, CONTINUOUS, INSTANTANEOUS) Measurement, CONTINUOUS, INSTANTANEOUS)
@ -123,7 +123,7 @@ class VersatileExpressPlatform(Platform):
except pexpect.TIMEOUT: except pexpect.TIMEOUT:
pass # We have our own timeout -- see below. pass # We have our own timeout -- see below.
if (time.time() - wait_start_time) > self.ready_timeout: if (time.time() - wait_start_time) > self.ready_timeout:
raise TargetError('Could not acquire IP address.') raise TargetTransientError('Could not acquire IP address.')
finally: finally:
tty.sendline('exit') # exit shell created by "su" call at the start tty.sendline('exit') # exit shell created by "su" call at the start

View File

@ -19,7 +19,7 @@ import shutil
import time import time
import types import types
from devlib.exception import TargetError from devlib.exception import TargetStableError
from devlib.host import PACKAGE_BIN_DIRECTORY from devlib.host import PACKAGE_BIN_DIRECTORY
from devlib.platform import Platform from devlib.platform import Platform
from devlib.utils.ssh import AndroidGem5Connection, LinuxGem5Connection from devlib.utils.ssh import AndroidGem5Connection, LinuxGem5Connection
@ -86,12 +86,12 @@ class Gem5SimulationPlatform(Platform):
Check if the command to start gem5 makes sense Check if the command to start gem5 makes sense
""" """
if self.gem5args_binary is None: if self.gem5args_binary is None:
raise TargetError('Please specify a gem5 binary.') raise TargetStableError('Please specify a gem5 binary.')
if self.gem5args_args is None: if self.gem5args_args is None:
raise TargetError('Please specify the arguments passed on to gem5.') raise TargetStableError('Please specify the arguments passed on to gem5.')
self.gem5args_virtio = str(self.gem5args_virtio).format(self.gem5_interact_dir) self.gem5args_virtio = str(self.gem5args_virtio).format(self.gem5_interact_dir)
if self.gem5args_virtio is None: if self.gem5args_virtio is None:
raise TargetError('Please specify arguments needed for virtIO.') raise TargetStableError('Please specify arguments needed for virtIO.')
def _start_interaction_gem5(self): def _start_interaction_gem5(self):
""" """
@ -110,7 +110,7 @@ class Gem5SimulationPlatform(Platform):
if not os.path.exists(self.stats_directory): if not os.path.exists(self.stats_directory):
os.mkdir(self.stats_directory) os.mkdir(self.stats_directory)
if os.path.exists(self.gem5_out_dir): if os.path.exists(self.gem5_out_dir):
raise TargetError("The gem5 stats directory {} already " raise TargetStableError("The gem5 stats directory {} already "
"exists.".format(self.gem5_out_dir)) "exists.".format(self.gem5_out_dir))
else: else:
os.mkdir(self.gem5_out_dir) os.mkdir(self.gem5_out_dir)
@ -153,7 +153,7 @@ class Gem5SimulationPlatform(Platform):
e.g. pid, input directory etc e.g. pid, input directory etc
""" """
self.logger("This functionality is not yet implemented") self.logger("This functionality is not yet implemented")
raise TargetError() raise TargetStableError()
def _intercept_telnet_port(self): def _intercept_telnet_port(self):
""" """
@ -161,13 +161,13 @@ class Gem5SimulationPlatform(Platform):
""" """
if self.gem5 is None: if self.gem5 is None:
raise TargetError('The platform has no gem5 simulation! ' raise TargetStableError('The platform has no gem5 simulation! '
'Something went wrong') 'Something went wrong')
while self.gem5_port is None: while self.gem5_port is None:
# Check that gem5 is running! # Check that gem5 is running!
if self.gem5.poll(): if self.gem5.poll():
message = "The gem5 process has crashed with error code {}!\n\tPlease see {} for details." message = "The gem5 process has crashed with error code {}!\n\tPlease see {} for details."
raise TargetError(message.format(self.gem5.poll(), self.stderr_file.name)) raise TargetStableError(message.format(self.gem5.poll(), self.stderr_file.name))
# Open the stderr file # Open the stderr file
with open(self.stderr_filename, 'r') as f: with open(self.stderr_filename, 'r') as f:
@ -185,7 +185,7 @@ class Gem5SimulationPlatform(Platform):
# Check if the sockets are not disabled # Check if the sockets are not disabled
m = re.search(r"Sockets disabled, not accepting terminal connections", line) m = re.search(r"Sockets disabled, not accepting terminal connections", line)
if m: if m:
raise TargetError("The sockets have been disabled!" raise TargetStableError("The sockets have been disabled!"
"Pass --listener-mode=on to gem5") "Pass --listener-mode=on to gem5")
else: else:
time.sleep(1) time.sleep(1)
@ -287,10 +287,10 @@ class Gem5SimulationPlatform(Platform):
# Methods that will be monkey-patched onto the target # Methods that will be monkey-patched onto the target
def _overwritten_reset(self): # pylint: disable=unused-argument def _overwritten_reset(self): # pylint: disable=unused-argument
raise TargetError('Resetting is not allowed on gem5 platforms!') raise TargetStableError('Resetting is not allowed on gem5 platforms!')
def _overwritten_reboot(self): # pylint: disable=unused-argument def _overwritten_reboot(self): # pylint: disable=unused-argument
raise TargetError('Rebooting is not allowed on gem5 platforms!') raise TargetStableError('Rebooting is not allowed on gem5 platforms!')
def _overwritten_capture_screen(self, filepath): def _overwritten_capture_screen(self, filepath):
connection_screencapped = self.platform.gem5_capture_screen(filepath) connection_screencapped = self.platform.gem5_capture_screen(filepath)

View File

@ -29,7 +29,7 @@ from collections import namedtuple
from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY from devlib.host import LocalConnection, PACKAGE_BIN_DIRECTORY
from devlib.module import get_module from devlib.module import get_module
from devlib.platform import Platform from devlib.platform import Platform
from devlib.exception import TargetError, TargetNotRespondingError, TimeoutError # pylint: disable=redefined-builtin from devlib.exception import DevlibTransientError, TargetStableError, TargetNotRespondingError, TimeoutError # pylint: disable=redefined-builtin
from devlib.utils.ssh import SshConnection from devlib.utils.ssh import SshConnection
from devlib.utils.android import AdbConnection, AndroidProperties, LogcatMonitor, adb_command, adb_disconnect, INTENT_FLAGS from devlib.utils.android import AdbConnection, AndroidProperties, LogcatMonitor, adb_command, adb_disconnect, INTENT_FLAGS
from devlib.utils.misc import memoized, isiterable, convert_new_lines from devlib.utils.misc import memoized, isiterable, convert_new_lines
@ -104,7 +104,7 @@ class Target(object):
try: try:
self.execute('ls /', timeout=5, as_root=True) self.execute('ls /', timeout=5, as_root=True)
return True return True
except (TargetError, TimeoutError): except (TargetStableError, TimeoutError):
return False return False
@property @property
@ -150,11 +150,11 @@ class Target(object):
def config(self): def config(self):
try: try:
return KernelConfig(self.execute('zcat /proc/config.gz')) return KernelConfig(self.execute('zcat /proc/config.gz'))
except TargetError: except TargetStableError:
for path in ['/boot/config', '/boot/config-$(uname -r)']: for path in ['/boot/config', '/boot/config-$(uname -r)']:
try: try:
return KernelConfig(self.execute('cat {}'.format(path))) return KernelConfig(self.execute('cat {}'.format(path)))
except TargetError: except TargetStableError:
pass pass
return KernelConfig('') return KernelConfig('')
@ -203,7 +203,7 @@ class Target(object):
# Check if the user hasn't given two different platforms # Check if the user hasn't given two different platforms
if 'platform' in self.connection_settings: if 'platform' in self.connection_settings:
if connection_settings['platform'] is not platform: if connection_settings['platform'] is not platform:
raise TargetError('Platform specified in connection_settings ' raise TargetStableError('Platform specified in connection_settings '
'({}) differs from that directly passed ' '({}) differs from that directly passed '
'({})!)' '({})!)'
.format(connection_settings['platform'], .format(connection_settings['platform'],
@ -282,14 +282,14 @@ class Target(object):
def reboot(self, hard=False, connect=True, timeout=180): def reboot(self, hard=False, connect=True, timeout=180):
if hard: if hard:
if not self.has('hard_reset'): if not self.has('hard_reset'):
raise TargetError('Hard reset not supported for this target.') raise TargetStableError('Hard reset not supported for this target.')
self.hard_reset() # pylint: disable=no-member self.hard_reset() # pylint: disable=no-member
else: else:
if not self.is_connected: if not self.is_connected:
message = 'Cannot reboot target becuase it is disconnected. ' +\ message = 'Cannot reboot target becuase it is disconnected. ' +\
'Either connect() first, or specify hard=True ' +\ 'Either connect() first, or specify hard=True ' +\
'(in which case, a hard_reset module must be installed)' '(in which case, a hard_reset module must be installed)'
raise TargetError(message) raise TargetTransientError(message)
self.reset() self.reset()
# Wait a fixed delay before starting polling to give the target time to # Wait a fixed delay before starting polling to give the target time to
# shut down, otherwise, might create the connection while it's still shutting # shut down, otherwise, might create the connection while it's still shutting
@ -346,7 +346,7 @@ class Target(object):
try: try:
self.execute('{} tar -cvf {} {}'.format(self.busybox, tar_file_name, self.execute('{} tar -cvf {} {}'.format(self.busybox, tar_file_name,
source_dir), as_root=as_root) source_dir), as_root=as_root)
except TargetError: except TargetStableError:
self.logger.debug('Failed to run tar command on target! ' \ self.logger.debug('Failed to run tar command on target! ' \
'Not pulling directory {}'.format(source_dir)) 'Not pulling directory {}'.format(source_dir))
# Pull the file # Pull the file
@ -360,8 +360,10 @@ class Target(object):
# execution # execution
def execute(self, command, timeout=None, check_exit_code=True, as_root=False): def execute(self, command, timeout=None, check_exit_code=True,
return self.conn.execute(command, timeout, check_exit_code, as_root) as_root=False, will_succeed=False):
return self.conn.execute(command, timeout, check_exit_code, as_root,
will_succeed)
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):
return self.conn.background(command, stdout, stderr, as_root) return self.conn.background(command, stdout, stderr, as_root)
@ -459,12 +461,12 @@ class Target(object):
output = self.read_value(path) output = self.read_value(path)
if not output == value: if not output == value:
message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output) message = 'Could not set the value of {} to "{}" (read "{}")'.format(path, value, output)
raise TargetError(message) raise TargetStableError(message)
def reset(self): def reset(self):
try: try:
self.execute('reboot', as_root=self.needs_su, timeout=2) self.execute('reboot', as_root=self.needs_su, timeout=2)
except (TargetError, TimeoutError, subprocess.CalledProcessError): except (DevlibTransientError, subprocess.CalledProcessError):
# on some targets "reboot" doesn't return gracefully # on some targets "reboot" doesn't return gracefully
pass pass
self._connected_as_root = None self._connected_as_root = None
@ -473,7 +475,7 @@ class Target(object):
try: try:
self.conn.execute('ls /', timeout=5) self.conn.execute('ls /', timeout=5)
return 1 return 1
except (TimeoutError, subprocess.CalledProcessError, TargetError): except (DevlibTransientError, subprocess.CalledProcessError):
if explode: if explode:
raise TargetNotRespondingError('Target {} is not responding'.format(self.conn.name)) raise TargetNotRespondingError('Target {} is not responding'.format(self.conn.name))
return 0 return 0
@ -488,7 +490,7 @@ class Target(object):
for pid in self.get_pids_of(process_name): for pid in self.get_pids_of(process_name):
try: try:
self.kill(pid, signal=signal, as_root=as_root) self.kill(pid, signal=signal, as_root=as_root)
except TargetError: except TargetStableError:
pass pass
def get_pids_of(self, process_name): def get_pids_of(self, process_name):
@ -588,7 +590,7 @@ class Target(object):
try: try:
if name in self.list_directory(path): if name in self.list_directory(path):
return self.path.join(path, name) return self.path.join(path, name)
except TargetError: except TargetStableError:
pass # directory does not exist or no executable permissions pass # directory does not exist or no executable permissions
which = get_installed which = get_installed
@ -779,7 +781,7 @@ class Target(object):
try: try:
self.execute(command) self.execute(command)
return True return True
except TargetError as e: except TargetStableError as e:
err = str(e).lower() err = str(e).lower()
if '100% packet loss' in err: if '100% packet loss' in err:
# We sent a packet but got no response. # We sent a packet but got no response.
@ -823,15 +825,12 @@ class LinuxTarget(Target):
@memoized @memoized
def os_version(self): def os_version(self):
os_version = {} os_version = {}
try:
command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null' command = 'ls /etc/*-release /etc*-version /etc/*_release /etc/*_version 2>/dev/null'
version_files = self.execute(command, check_exit_code=False).strip().split() version_files = self.execute(command, check_exit_code=False).strip().split()
for vf in version_files: for vf in version_files:
name = self.path.basename(vf) name = self.path.basename(vf)
output = self.read_value(vf) output = self.read_value(vf)
os_version[name] = convert_new_lines(output.strip()).replace('\n', ' ') os_version[name] = convert_new_lines(output.strip()).replace('\n', ' ')
except TargetError:
raise
return os_version return os_version
@property @property
@ -938,7 +937,7 @@ class LinuxTarget(Target):
filepath = filepath.format(ts=ts) filepath = filepath.format(ts=ts)
self.pull(tmpfile, filepath) self.pull(tmpfile, filepath)
self.remove(tmpfile) self.remove(tmpfile)
except TargetError as e: except TargetStableError as e:
if "Can't open X dispay." not in e.message: if "Can't open X dispay." not in e.message:
raise e raise e
message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member message = e.message.split('OUTPUT:', 1)[1].strip() # pylint: disable=no-member
@ -1079,7 +1078,7 @@ class AndroidTarget(Target):
try: try:
self.execute('reboot {}'.format(fastboot and 'fastboot' or ''), self.execute('reboot {}'.format(fastboot and 'fastboot' or ''),
as_root=self.needs_su, timeout=2) as_root=self.needs_su, timeout=2)
except (TargetError, TimeoutError, subprocess.CalledProcessError): except (DevlibTransientError, subprocess.CalledProcessError):
# on some targets "reboot" doesn't return gracefully # on some targets "reboot" doesn't return gracefully
pass pass
self._connected_as_root = None self._connected_as_root = None
@ -1091,7 +1090,9 @@ class AndroidTarget(Target):
time.sleep(5) time.sleep(5)
boot_completed = boolean(self.getprop('sys.boot_completed')) boot_completed = boolean(self.getprop('sys.boot_completed'))
if not boot_completed: if not boot_completed:
raise TargetError('Connected but Android did not fully boot.') # Raise a TargetStableError as this usually happens because of
# an issue with Android more than a timeout that is too small.
raise TargetStableError('Connected but Android did not fully boot.')
def connect(self, timeout=30, check_boot_completed=True): # pylint: disable=arguments-differ def connect(self, timeout=30, check_boot_completed=True): # pylint: disable=arguments-differ
device = self.connection_settings.get('device') device = self.connection_settings.get('device')
@ -1128,7 +1129,7 @@ class AndroidTarget(Target):
self.ls_command = 'ls -1' self.ls_command = 'ls -1'
try: try:
self.execute('ls -1 {}'.format(self.working_directory), as_root=False) self.execute('ls -1 {}'.format(self.working_directory), as_root=False)
except TargetError: except TargetStableError:
self.ls_command = 'ls' self.ls_command = 'ls'
def list_directory(self, path, as_root=False): def list_directory(self, path, as_root=False):
@ -1240,7 +1241,7 @@ class AndroidTarget(Target):
swipe_height = height * 2 // 3 swipe_height = height * 2 // 3
self.input_swipe(swipe_middle, swipe_height, swipe_middle, 0) self.input_swipe(swipe_middle, swipe_height, swipe_middle, 0)
else: else:
raise TargetError("Invalid swipe direction: {}".format(direction)) raise TargetStableError("Invalid swipe direction: {}".format(direction))
def getprop(self, prop=None): def getprop(self, prop=None):
props = AndroidProperties(self.execute('getprop')) props = AndroidProperties(self.execute('getprop'))
@ -1307,12 +1308,12 @@ class AndroidTarget(Target):
self.logger.debug("Replace APK = {}, ADB flags = '{}'".format(replace, ' '.join(flags))) self.logger.debug("Replace APK = {}, ADB flags = '{}'".format(replace, ' '.join(flags)))
return adb_command(self.adb_name, "install {} '{}'".format(' '.join(flags), filepath), timeout=timeout) return adb_command(self.adb_name, "install {} '{}'".format(' '.join(flags), filepath), timeout=timeout)
else: else:
raise TargetError('Can\'t install {}: unsupported format.'.format(filepath)) raise TargetStableError('Can\'t install {}: unsupported format.'.format(filepath))
def grant_package_permission(self, package, permission): def grant_package_permission(self, package, permission):
try: try:
return self.execute('pm grant {} {}'.format(package, permission)) return self.execute('pm grant {} {}'.format(package, permission))
except TargetError as e: except TargetStableError as e:
if 'is not a changeable permission type' in e.message: if 'is not a changeable permission type' in e.message:
pass # Ignore if unchangeable pass # Ignore if unchangeable
elif 'Unknown permission' in e.message: elif 'Unknown permission' in e.message:
@ -1411,7 +1412,7 @@ class AndroidTarget(Target):
if match: if match:
return boolean(match.group(1)) return boolean(match.group(1))
else: else:
raise TargetError('Could not establish screen state.') raise TargetStableError('Could not establish screen state.')
def ensure_screen_is_on(self): def ensure_screen_is_on(self):
if not self.is_screen_on(): if not self.is_screen_on():
@ -1456,7 +1457,7 @@ class AndroidTarget(Target):
def set_airplane_mode(self, mode): def set_airplane_mode(self, mode):
root_required = self.get_sdk_version() > 23 root_required = self.get_sdk_version() > 23
if root_required and not self.is_rooted: if root_required and not self.is_rooted:
raise TargetError('Root is required to toggle airplane mode on Android 7+') raise TargetStableError('Root is required to toggle airplane mode on Android 7+')
mode = int(boolean(mode)) mode = int(boolean(mode))
cmd = 'settings put global airplane_mode_on {}' cmd = 'settings put global airplane_mode_on {}'
self.execute(cmd.format(mode)) self.execute(cmd.format(mode))
@ -1540,7 +1541,7 @@ class AndroidTarget(Target):
as_root=True) as_root=True)
else: else:
message = 'Could not find mount point for executables directory {}' message = 'Could not find mount point for executables directory {}'
raise TargetError(message.format(self.executables_directory)) raise TargetStableError(message.format(self.executables_directory))
_charging_enabled_path = '/sys/class/power_supply/battery/charging_enabled' _charging_enabled_path = '/sys/class/power_supply/battery/charging_enabled'

View File

@ -23,7 +23,7 @@ import sys
from devlib.trace import TraceCollector from devlib.trace import TraceCollector
from devlib.host import PACKAGE_BIN_DIRECTORY from devlib.host import PACKAGE_BIN_DIRECTORY
from devlib.exception import TargetError, HostError from devlib.exception import TargetStableError, HostError
from devlib.utils.misc import check_output, which from devlib.utils.misc import check_output, which
@ -99,7 +99,7 @@ class FtraceCollector(TraceCollector):
self.kernelshark = which('kernelshark') self.kernelshark = which('kernelshark')
if not self.target.is_rooted: if not self.target.is_rooted:
raise TargetError('trace-cmd instrument cannot be used on an unrooted device.') raise TargetStableError('trace-cmd instrument cannot be used on an unrooted device.')
if self.autoreport and not self.report_on_target and self.host_binary is None: if self.autoreport and not self.report_on_target and self.host_binary is None:
raise HostError('trace-cmd binary must be installed on the host if autoreport=True.') raise HostError('trace-cmd binary must be installed on the host if autoreport=True.')
if self.autoview and self.kernelshark is None: if self.autoview and self.kernelshark is None:
@ -109,7 +109,7 @@ class FtraceCollector(TraceCollector):
self.target_binary = self.target.install(host_file) self.target_binary = self.target.install(host_file)
else: else:
if not self.target.is_installed('trace-cmd'): if not self.target.is_installed('trace-cmd'):
raise TargetError('No trace-cmd found on device and no_install=True is specified.') raise TargetStableError('No trace-cmd found on device and no_install=True is specified.')
self.target_binary = 'trace-cmd' self.target_binary = 'trace-cmd'
# Validate required events to be traced # Validate required events to be traced
@ -127,7 +127,7 @@ class FtraceCollector(TraceCollector):
if not list(filter(event_re.match, available_events)): if not list(filter(event_re.match, available_events)):
message = 'Event [{}] not available for tracing'.format(event) message = 'Event [{}] not available for tracing'.format(event)
if strict: if strict:
raise TargetError(message) raise TargetStableError(message)
self.target.logger.warning(message) self.target.logger.warning(message)
else: else:
selected_events.append(event) selected_events.append(event)
@ -142,7 +142,7 @@ class FtraceCollector(TraceCollector):
# Check for function tracing support # Check for function tracing support
if self.functions: if self.functions:
if not self.target.file_exists(self.function_profile_file): if not self.target.file_exists(self.function_profile_file):
raise TargetError('Function profiling not supported. '\ raise TargetStableError('Function profiling not supported. '\
'A kernel build with CONFIG_FUNCTION_PROFILER enable is required') 'A kernel build with CONFIG_FUNCTION_PROFILER enable is required')
# Validate required functions to be traced # Validate required functions to be traced
available_functions = self.target.execute( available_functions = self.target.execute(
@ -153,7 +153,7 @@ class FtraceCollector(TraceCollector):
if function not in available_functions: if function not in available_functions:
message = 'Function [{}] not available for profiling'.format(function) message = 'Function [{}] not available for profiling'.format(function)
if strict: if strict:
raise TargetError(message) raise TargetStableError(message)
self.target.logger.warning(message) self.target.logger.warning(message)
else: else:
selected_functions.append(function) selected_functions.append(function)
@ -283,7 +283,7 @@ class FtraceCollector(TraceCollector):
if sys.version_info[0] == 3: if sys.version_info[0] == 3:
error = error.decode(sys.stdout.encoding, 'replace') error = error.decode(sys.stdout.encoding, 'replace')
if process.returncode: if process.returncode:
raise TargetError('trace-cmd returned non-zero exit code {}'.format(process.returncode)) raise TargetStableError('trace-cmd returned non-zero exit code {}'.format(process.returncode))
if error: if error:
# logged at debug level, as trace-cmd always outputs some # logged at debug level, as trace-cmd always outputs some
# errors that seem benign. # errors that seem benign.

View File

@ -34,7 +34,7 @@ import subprocess
from shutil import copyfile from shutil import copyfile
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from devlib.exception import TargetError, HostError from devlib.exception import TargetStableError, HostError
from devlib.trace import TraceCollector from devlib.trace import TraceCollector
from devlib.utils.android import platform_tools from devlib.utils.android import platform_tools
from devlib.utils.misc import memoized from devlib.utils.misc import memoized
@ -109,12 +109,12 @@ class SystraceCollector(TraceCollector):
if category not in self.available_categories: if category not in self.available_categories:
message = 'Category [{}] not available for tracing'.format(category) message = 'Category [{}] not available for tracing'.format(category)
if strict: if strict:
raise TargetError(message) raise TargetStableError(message)
self.logger.warning(message) self.logger.warning(message)
self.categories = list(set(self.categories) & set(self.available_categories)) self.categories = list(set(self.categories) & set(self.available_categories))
if not self.categories: if not self.categories:
raise TargetError('None of the requested categories are available') raise TargetStableError('None of the requested categories are available')
def __del__(self): def __del__(self):
self.reset() self.reset()

View File

@ -29,7 +29,7 @@ import subprocess
from collections import defaultdict from collections import defaultdict
import pexpect import pexpect
from devlib.exception import TargetError, HostError from devlib.exception import TargetTransientError, TargetStableError, HostError, DevlibError
from devlib.utils.misc import check_output, which, ABI_MAP from devlib.utils.misc import check_output, which, ABI_MAP
from devlib.utils.misc import escape_single_quotes, escape_double_quotes from devlib.utils.misc import escape_single_quotes, escape_double_quotes
@ -257,9 +257,15 @@ class AdbConnection(object):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def execute(self, command, timeout=None, check_exit_code=False, def execute(self, command, timeout=None, check_exit_code=False,
as_root=False, strip_colors=True): as_root=False, strip_colors=True, will_succeed=False):
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) as_root, adb_server=self.adb_server)
except TargetStableError as e:
if will_succeed:
raise TargetTransientError(e)
else:
raise
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):
return adb_background_shell(self.device, command, stdout, stderr, as_root) return adb_background_shell(self.device, command, stdout, stderr, as_root)
@ -357,7 +363,7 @@ def adb_disconnect(device):
logger.debug(command) logger.debug(command)
retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True) retval = subprocess.call(command, stdout=open(os.devnull, 'wb'), shell=True)
if retval: if retval:
raise TargetError('"{}" returned {}'.format(command, retval)) raise TargetTransientError('"{}" returned {}'.format(command, retval))
def _ping(device): def _ping(device):
@ -394,7 +400,7 @@ def adb_shell(device, command, timeout=None, check_exit_code=False,
try: try:
raw_output, _ = check_output(actual_command, timeout, shell=False, combined_output=True) raw_output, _ = check_output(actual_command, timeout, shell=False, combined_output=True)
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
raise TargetError(str(e)) raise TargetStableError(str(e))
if raw_output: if raw_output:
try: try:
@ -413,19 +419,19 @@ def adb_shell(device, command, timeout=None, check_exit_code=False,
if int(exit_code): if int(exit_code):
message = ('Got exit code {}\nfrom target command: {}\n' message = ('Got exit code {}\nfrom target command: {}\n'
'OUTPUT: {}') 'OUTPUT: {}')
raise TargetError(message.format(exit_code, command, output)) raise TargetStableError(message.format(exit_code, command, output))
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 TargetError(message.format(re_search[0])) raise TargetStableError(message.format(re_search[0]))
else: # not all digits else: # not all digits
if re_search: if re_search:
message = 'Could not start activity; got the following:\n{}' message = 'Could not start activity; got the following:\n{}'
raise TargetError(message.format(re_search[0])) raise TargetStableError(message.format(re_search[0]))
else: else:
message = 'adb has returned early; did not get an exit code. '\ message = 'adb has returned early; did not get an exit code. '\
'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\ 'Was kill-server invoked?\nOUTPUT:\n-----\n{}\n'\
'-----' '-----'
raise TargetError(message.format(raw_output)) raise TargetTransientError(message.format(raw_output))
return output return output
@ -484,7 +490,7 @@ def grant_app_permissions(target, package):
for permission in permissions: for permission in permissions:
try: try:
target.execute('pm grant {} {}'.format(package, permission)) target.execute('pm grant {} {}'.format(package, permission))
except TargetError: except TargetStableError:
logger.debug('Cannot grant {}'.format(permission)) logger.debug('Cannot grant {}'.format(permission))

View File

@ -36,7 +36,8 @@ else:
from pexpect import EOF, TIMEOUT, spawn 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, TargetError, TimeoutError from devlib.exception import (HostError, TargetStableError, TargetNotRespondingError,
TimeoutError)
from devlib.utils.misc import which, strip_bash_colors, check_output from devlib.utils.misc import which, strip_bash_colors, check_output
from devlib.utils.misc import (escape_single_quotes, escape_double_quotes, from devlib.utils.misc import (escape_single_quotes, escape_double_quotes,
escape_spaces) escape_spaces)
@ -72,7 +73,7 @@ def ssh_get_shell(host, username, password=None, keyfile=None, port=None, timeou
timeout -= time.time() - start_time timeout -= time.time() - start_time
if timeout <= 0: if timeout <= 0:
message = 'Could not connect to {}; is the host name correct?' message = 'Could not connect to {}; is the host name correct?'
raise TargetError(message.format(host)) raise TargetTransientError(message.format(host))
time.sleep(5) time.sleep(5)
conn.setwinsize(500, 200) conn.setwinsize(500, 200)
@ -194,7 +195,7 @@ class SshConnection(object):
return self._scp(source, dest, timeout) return self._scp(source, dest, timeout)
def execute(self, command, timeout=None, check_exit_code=True, def execute(self, command, timeout=None, check_exit_code=True,
as_root=False, strip_colors=True): #pylint: disable=unused-argument as_root=False, strip_colors=True, will_succeed=False): #pylint: disable=unused-argument
if command == '': if command == '':
# Empty command is valid but the __devlib_ec stuff below will # Empty command is valid but the __devlib_ec stuff below will
# produce a syntax error with bash. Treat as a special case. # produce a syntax error with bash. Treat as a special case.
@ -210,14 +211,19 @@ class SshConnection(object):
exit_code = int(exit_code_text) exit_code = int(exit_code_text)
if exit_code: if exit_code:
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}' message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'
raise TargetError(message.format(exit_code, command, output)) raise TargetStableError(message.format(exit_code, command, output))
except (ValueError, IndexError): except (ValueError, IndexError):
logger.warning( logger.warning(
'Could not get exit code for "{}",\ngot: "{}"'\ 'Could not get exit code for "{}",\ngot: "{}"'\
.format(command, exit_code_text)) .format(command, exit_code_text))
return output return output
except EOF: except EOF:
raise TargetError('Connection lost.') raise TargetNotRespondingError('Connection lost.')
except TargetStableError as e:
if will_succeed:
raise TargetTransientError(e)
else:
raise
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):
try: try:
@ -231,7 +237,7 @@ class SshConnection(object):
command = _give_password(self.password, command) command = _give_password(self.password, command)
return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True) return subprocess.Popen(command, stdout=stdout, stderr=stderr, shell=True)
except EOF: except EOF:
raise TargetError('Connection lost.') raise TargetNotRespondingError('Connection lost.')
def close(self): def close(self):
logger.debug('Logging out {}@{}'.format(self.username, self.host)) logger.debug('Logging out {}@{}'.format(self.username, self.host))
@ -352,7 +358,7 @@ class Gem5Connection(TelnetConnection):
if host is not None: if host is not None:
host_system = socket.gethostname() host_system = socket.gethostname()
if host_system != host: if host_system != host:
raise TargetError("Gem5Connection can only connect to gem5 " raise TargetStableError("Gem5Connection can only connect to gem5 "
"simulations on your current host {}, which " "simulations on your current host {}, which "
"differs from the one given {}!" "differs from the one given {}!"
.format(host_system, host)) .format(host_system, host))
@ -497,16 +503,23 @@ class Gem5Connection(TelnetConnection):
logger.debug("Pull complete.") logger.debug("Pull complete.")
def execute(self, command, timeout=1000, check_exit_code=True, def execute(self, command, timeout=1000, check_exit_code=True,
as_root=False, strip_colors=True): as_root=False, strip_colors=True, will_succeed=False):
""" """
Execute a command on the gem5 platform Execute a command on the gem5 platform
""" """
# First check if the connection is set up to interact with gem5 # First check if the connection is set up to interact with gem5
self._check_ready() self._check_ready()
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)
except TargetStableError as e:
if will_succeed:
raise TargetTransientError(e)
else:
raise
if strip_colors: if strip_colors:
output = strip_bash_colors(output) output = strip_bash_colors(output)
return output return output
@ -576,7 +589,7 @@ class Gem5Connection(TelnetConnection):
# rather than spewing out pexpect errors. # rather than spewing out pexpect errors.
if gem5_simulation.poll(): if gem5_simulation.poll():
message = "The gem5 process has crashed with error code {}!\n\tPlease see {} for details." message = "The gem5 process has crashed with error code {}!\n\tPlease see {} for details."
raise TargetError(message.format(gem5_simulation.poll(), gem5_out_dir)) raise TargetNotRespondingError(message.format(gem5_simulation.poll(), gem5_out_dir))
else: else:
# Let's re-throw the exception in this case. # Let's re-throw the exception in this case.
raise err raise err
@ -604,7 +617,7 @@ class Gem5Connection(TelnetConnection):
lock_file_name = '{}{}_{}.LOCK'.format(self.lock_directory, host, port) lock_file_name = '{}{}_{}.LOCK'.format(self.lock_directory, host, port)
if os.path.isfile(lock_file_name): if os.path.isfile(lock_file_name):
# There is already a connection to this gem5 simulation # There is already a connection to this gem5 simulation
raise TargetError('There is already a connection to the gem5 ' raise TargetStableError('There is already a connection to the gem5 '
'simulation using port {} on {}!' 'simulation using port {} on {}!'
.format(port, host)) .format(port, host))
@ -623,7 +636,7 @@ class Gem5Connection(TelnetConnection):
self._gem5_EOF_handler(gem5_simulation, gem5_out_dir, err) self._gem5_EOF_handler(gem5_simulation, gem5_out_dir, err)
else: else:
gem5_simulation.kill() gem5_simulation.kill()
raise TargetError("Failed to connect to the gem5 telnet session.") raise TargetNotRespondingError("Failed to connect to the gem5 telnet session.")
gem5_logger.info("Connected! Waiting for prompt...") gem5_logger.info("Connected! Waiting for prompt...")
@ -718,7 +731,7 @@ class Gem5Connection(TelnetConnection):
def _gem5_util(self, command): def _gem5_util(self, command):
""" Execute a gem5 utility command using the m5 binary on the device """ """ Execute a gem5 utility command using the m5 binary on the device """
if self.m5_path is None: if self.m5_path is None:
raise TargetError('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): # pylint: disable=R0912
@ -732,7 +745,7 @@ class Gem5Connection(TelnetConnection):
fails, warn, but continue with the potentially wrong output. fails, warn, but continue with the potentially wrong output.
The exit code is also checked by default, and non-zero exit codes will The exit code is also checked by default, and non-zero exit codes will
raise a TargetError. raise a TargetStableError.
""" """
if sync: if sync:
self._sync_gem5_shell() self._sync_gem5_shell()
@ -788,7 +801,7 @@ class Gem5Connection(TelnetConnection):
exit_code = int(exit_code_text.split()[0]) exit_code = int(exit_code_text.split()[0])
if exit_code: if exit_code:
message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}' message = 'Got exit code {}\nfrom: {}\nOUTPUT: {}'
raise TargetError(message.format(exit_code, command, output)) 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)) gem5_logger.warning('Could not get exit code for "{}",\ngot: "{}"'.format(command, exit_code_text))
@ -843,7 +856,7 @@ class Gem5Connection(TelnetConnection):
Check if the gem5 platform is ready Check if the gem5 platform is ready
""" """
if not self.ready: if not self.ready:
raise TargetError('Gem5 is not ready to interact yet') raise TargetTransientError('Gem5 is not ready to interact yet')
def _wait_for_boot(self): def _wait_for_boot(self):
pass pass