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