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