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