1# Copyright 2014 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4""" 5Exception classes raised by AdbWrapper and DeviceUtils. 6 7The class hierarchy for device exceptions is: 8 9 base_error.BaseError 10 +-- CommandFailedError 11 | +-- AdbCommandFailedError 12 | | +-- AdbShellCommandFailedError 13 | +-- AdbVersionError 14 | +-- FastbootCommandFailedError 15 | +-- DeviceVersionError 16 | +-- DeviceChargingError 17 | +-- RootUserBuildError 18 +-- CommandTimeoutError 19 +-- DeviceUnreachableError 20 +-- NoDevicesError 21 +-- MultipleDevicesError 22 +-- NoAdbError 23 24""" 25 26import six 27 28from devil import base_error 29from devil.utils import cmd_helper 30from devil.utils import parallelizer 31 32 33class CommandFailedError(base_error.BaseError): 34 """Exception for command failures.""" 35 36 def __init__(self, message, device_serial=None): 37 device_leader = '(device: %s)' % device_serial 38 if device_serial is not None and not message.startswith(device_leader): 39 message = '%s %s' % (device_leader, message) 40 self.device_serial = device_serial 41 super(CommandFailedError, self).__init__(message) 42 43 def __eq__(self, other): 44 return (super(CommandFailedError, self).__eq__(other) 45 and self.device_serial == other.device_serial) 46 47 def __ne__(self, other): 48 return not self == other 49 50 51class _BaseCommandFailedError(CommandFailedError): 52 """Base Exception for adb and fastboot command failures.""" 53 54 def __init__(self, 55 args, 56 output, 57 status=None, 58 device_serial=None, 59 message=None): 60 self.args = args 61 self.output = output 62 self.status = status 63 if not message: 64 adb_cmd = ' '.join(cmd_helper.SingleQuote(arg) for arg in self.args) 65 segments = ['adb %s: failed ' % adb_cmd] 66 if status: 67 segments.append('with exit status %s ' % self.status) 68 if output: 69 segments.append('and output:\n') 70 segments.extend('- %s\n' % line for line in output.splitlines()) 71 else: 72 segments.append('and no output.') 73 message = ''.join(segments) 74 super(_BaseCommandFailedError, self).__init__(message, device_serial) 75 76 def __eq__(self, other): 77 return (super(_BaseCommandFailedError, self).__eq__(other) 78 and self.args == other.args and self.output == other.output 79 and self.status == other.status) 80 81 def __ne__(self, other): 82 return not self == other 83 84 def __reduce__(self): 85 """Support pickling.""" 86 result = [None, None, None, None, None] 87 super_result = super(_BaseCommandFailedError, self).__reduce__() 88 result[:len(super_result)] = super_result 89 90 # Update the args used to reconstruct this exception. 91 result[1] = (self.args, self.output, self.status, self.device_serial, 92 self.message) 93 return tuple(result) 94 95 96class AdbCommandFailedError(_BaseCommandFailedError): 97 """Exception for adb command failures.""" 98 99 def __init__(self, 100 args, 101 output, 102 status=None, 103 device_serial=None, 104 message=None): 105 super(AdbCommandFailedError, self).__init__( 106 args, 107 output, 108 status=status, 109 message=message, 110 device_serial=device_serial) 111 112 113class FastbootCommandFailedError(_BaseCommandFailedError): 114 """Exception for fastboot command failures.""" 115 116 def __init__(self, 117 args, 118 output, 119 status=None, 120 device_serial=None, 121 message=None): 122 super(FastbootCommandFailedError, self).__init__( 123 args, 124 output, 125 status=status, 126 message=message, 127 device_serial=device_serial) 128 129 130class DeviceVersionError(CommandFailedError): 131 """Exception for device version failures.""" 132 133 def __init__(self, message, device_serial=None): 134 super(DeviceVersionError, self).__init__(message, device_serial) 135 136 137class AdbVersionError(CommandFailedError): 138 """Exception for running a command on an incompatible version of adb.""" 139 140 def __init__(self, args, desc=None, actual_version=None, min_version=None): 141 adb_cmd = ' '.join(cmd_helper.SingleQuote(arg) for arg in args) 142 desc = desc or 'not supported' 143 if min_version: 144 desc += ' prior to %s' % min_version 145 if actual_version: 146 desc += ' (actual: %s)' % actual_version 147 super(AdbVersionError, 148 self).__init__(message='adb %s: %s' % (adb_cmd, desc)) 149 150 151class AdbShellCommandFailedError(AdbCommandFailedError): 152 """Exception for shell command failures run via adb.""" 153 154 def __init__(self, command, output, status, device_serial=None): 155 self.command = command 156 segments = [ 157 'shell command run via adb failed on the device:\n', 158 ' command: %s\n' % command 159 ] 160 segments.append(' exit status: %s\n' % status) 161 if output: 162 segments.append(' output:\n') 163 if isinstance(output, six.string_types): 164 output_lines = output.splitlines() 165 else: 166 output_lines = output 167 segments.extend(' - %s\n' % line for line in output_lines) 168 else: 169 segments.append(" output: ''\n") 170 message = ''.join(segments) 171 super(AdbShellCommandFailedError, self).__init__( 172 ['shell', command], output, status, device_serial, message) 173 174 def __reduce__(self): 175 """Support pickling.""" 176 result = [None, None, None, None, None] 177 super_result = super(AdbShellCommandFailedError, self).__reduce__() 178 result[:len(super_result)] = super_result 179 180 # Update the args used to reconstruct this exception. 181 result[1] = (self.command, self.output, self.status, self.device_serial) 182 return tuple(result) 183 184 185class CommandTimeoutError(base_error.BaseError): 186 """Exception for command timeouts.""" 187 188 def __init__(self, message, is_infra_error=False, output=None): 189 super(CommandTimeoutError, self).__init__(message, is_infra_error) 190 self.output = output 191 192 193class DeviceUnreachableError(base_error.BaseError): 194 """Exception for device unreachable failures.""" 195 pass 196 197 198class NoDevicesError(base_error.BaseError): 199 """Exception for having no devices attached.""" 200 201 def __init__(self, msg=None): 202 super(NoDevicesError, self).__init__( 203 msg or 'No devices attached.', is_infra_error=True) 204 205 206class MultipleDevicesError(base_error.BaseError): 207 """Exception for having multiple attached devices without selecting one.""" 208 209 def __init__(self, devices): 210 parallel_devices = parallelizer.Parallelizer(devices) 211 descriptions = parallel_devices.pMap(lambda d: d.build_description).pGet( 212 None) 213 msg = ('More than one device available. Use -d/--device to select a device ' 214 'by serial.\n\nAvailable devices:\n') 215 for d, desc in zip(devices, descriptions): 216 msg += ' %s (%s)\n' % (d, desc) 217 218 super(MultipleDevicesError, self).__init__(msg, is_infra_error=True) 219 220 221class NoAdbError(base_error.BaseError): 222 """Exception for being unable to find ADB.""" 223 224 def __init__(self, msg=None): 225 super(NoAdbError, self).__init__( 226 msg or 'Unable to find adb.', is_infra_error=True) 227 228 229class DeviceChargingError(CommandFailedError): 230 """Exception for device charging errors.""" 231 232 def __init__(self, message, device_serial=None): 233 super(DeviceChargingError, self).__init__(message, device_serial) 234 235 236class RootUserBuildError(CommandFailedError): 237 """Exception for being unable to root a device with "user" build.""" 238 239 def __init__(self, message=None, device_serial=None): 240 super(RootUserBuildError, self).__init__( 241 message or 'Unable to root device with user build.', device_serial) 242