adb_interface.py revision 6c6c1ab5fd5b7559c271438f50b22edbea1e1f05
1#!/usr/bin/python2.4
2#
3#
4# Copyright 2008, The Android Open Source Project
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18"""Provides an interface to communicate with the device via the adb command.
19
20Assumes adb binary is currently on system path.
21"""
22# Python imports
23import os
24import string
25import time
26
27# local imports
28import am_instrument_parser
29import errors
30import logger
31import run_command
32
33
34class AdbInterface:
35  """Helper class for communicating with Android device via adb."""
36
37  # argument to pass to adb, to direct command to specific device
38  _target_arg = ""
39
40  DEVICE_TRACE_DIR = "/data/test_results/"
41
42  def SetEmulatorTarget(self):
43    """Direct all future commands to the only running emulator."""
44    self._target_arg = "-e"
45
46  def SetDeviceTarget(self):
47    """Direct all future commands to the only connected USB device."""
48    self._target_arg = "-d"
49
50  def SetTargetSerial(self, serial):
51    """Direct all future commands to Android target with the given serial."""
52    self._target_arg = "-s %s" % serial
53
54  def SendCommand(self, command_string, timeout_time=20, retry_count=3):
55    """Send a command via adb.
56
57    Args:
58      command_string: adb command to run
59      timeout_time: number of seconds to wait for command to respond before
60        retrying
61      retry_count: number of times to retry command before raising
62        WaitForResponseTimedOutError
63    Returns:
64      string output of command
65
66    Raises:
67      WaitForResponseTimedOutError if device does not respond to command within time
68    """
69    adb_cmd = "adb %s %s" % (self._target_arg, command_string)
70    logger.SilentLog("about to run %s" % adb_cmd)
71    return run_command.RunCommand(adb_cmd, timeout_time=timeout_time,
72                                  retry_count=retry_count)
73
74  def SendShellCommand(self, cmd, timeout_time=20, retry_count=3):
75    """Send a adb shell command.
76
77    Args:
78      cmd: adb shell command to run
79      timeout_time: number of seconds to wait for command to respond before
80        retrying
81      retry_count: number of times to retry command before raising
82        WaitForResponseTimedOutError
83
84    Returns:
85      string output of command
86
87    Raises:
88      WaitForResponseTimedOutError: if device does not respond to command
89    """
90    return self.SendCommand("shell %s" % cmd, timeout_time=timeout_time,
91                            retry_count=retry_count)
92
93  def BugReport(self, path):
94    """Dumps adb bugreport to the file specified by the path.
95
96    Args:
97      path: Path of the file where adb bugreport is dumped to.
98    """
99    bug_output = self.SendShellCommand("bugreport", timeout_time=60)
100    bugreport_file = open(path, "w")
101    bugreport_file.write(bug_output)
102    bugreport_file.close()
103
104  def Push(self, src, dest):
105    """Pushes the file src onto the device at dest.
106
107    Args:
108      src: file path of host file to push
109      dest: destination absolute file path on device
110    """
111    self.SendCommand("push %s %s" % (src, dest), timeout_time=60)
112
113  def Pull(self, src, dest):
114    """Pulls the file src on the device onto dest on the host.
115
116    Args:
117      src: absolute file path of file on device to pull
118      dest: destination file path on host
119
120    Returns:
121      True if success and False otherwise.
122    """
123    # Create the base dir if it doesn't exist already
124    if not os.path.exists(os.path.dirname(dest)):
125      os.makedirs(os.path.dirname(dest))
126
127    if self.DoesFileExist(src):
128      self.SendCommand("pull %s %s" % (src, dest), timeout_time=60)
129      return True
130    else:
131      logger.Log("ADB Pull Failed: Source file %s does not exist." % src)
132      return False
133
134  def DoesFileExist(self, src):
135    """Checks if the given path exists on device target.
136
137    Args:
138      src: file path to be checked.
139
140    Returns:
141      True if file exists
142    """
143
144    output = self.SendShellCommand("ls %s" % src)
145    error = "No such file or directory"
146
147    if error in output:
148      return False
149    return True
150
151  def StartInstrumentationForPackage(
152      self, package_name, runner_name, timeout_time=60*10,
153      no_window_animation=False, instrumentation_args={}):
154    """Run instrumentation test for given package and runner.
155
156    Equivalent to StartInstrumentation, except instrumentation path is
157    separated into its package and runner components.
158    """
159    instrumentation_path = "%s/%s" % (package_name, runner_name)
160    return self.StartInstrumentation(self, instrumentation_path, timeout_time,
161                                     no_window_animation, instrumentation_args)
162
163  def StartInstrumentation(
164      self, instrumentation_path, timeout_time=60*10, no_window_animation=False,
165      profile=False, instrumentation_args={}):
166
167    """Runs an instrumentation class on the target.
168
169    Returns a dictionary containing the key value pairs from the
170    instrumentations result bundle and a list of TestResults. Also handles the
171    interpreting of error output from the device and raises the necessary
172    exceptions.
173
174    Args:
175      instrumentation_path: string. It should be the fully classified package
176      name, and instrumentation test runner, separated by "/"
177        e.g. com.android.globaltimelaunch/.GlobalTimeLaunch
178      timeout_time: Timeout value for the am command.
179      no_window_animation: boolean, Whether you want window animations enabled
180        or disabled
181      profile: If True, profiling will be turned on for the instrumentation.
182      instrumentation_args: Dictionary of key value bundle arguments to pass to
183      instrumentation.
184
185    Returns:
186      (test_results, inst_finished_bundle)
187
188      test_results: a list of TestResults
189      inst_finished_bundle (dict): Key/value pairs contained in the bundle that
190        is passed into ActivityManager.finishInstrumentation(). Included in this
191        bundle is the return code of the Instrumentation process, any error
192        codes reported by the activity manager, and any results explicitly added
193        by the instrumentation code.
194
195     Raises:
196       WaitForResponseTimedOutError: if timeout occurred while waiting for
197         response to adb instrument command
198       DeviceUnresponsiveError: if device system process is not responding
199       InstrumentationError: if instrumentation failed to run
200    """
201
202    command_string = self._BuildInstrumentationCommandPath(
203        instrumentation_path, no_window_animation=no_window_animation,
204        profile=profile, raw_mode=True,
205        instrumentation_args=instrumentation_args)
206
207    (test_results, inst_finished_bundle) = (
208        am_instrument_parser.ParseAmInstrumentOutput(
209            self.SendShellCommand(command_string, timeout_time=timeout_time,
210                                  retry_count=2)))
211
212    if "code" not in inst_finished_bundle:
213      raise errors.InstrumentationError("no test results... device setup "
214                                        "correctly?")
215
216    if inst_finished_bundle["code"] == "0":
217      short_msg_result = "no error message"
218      if "shortMsg" in inst_finished_bundle:
219        short_msg_result = inst_finished_bundle["shortMsg"]
220        logger.Log(short_msg_result)
221      raise errors.InstrumentationError(short_msg_result)
222
223    if "INSTRUMENTATION_ABORTED" in inst_finished_bundle:
224      logger.Log("INSTRUMENTATION ABORTED!")
225      raise errors.DeviceUnresponsiveError
226
227    return (test_results, inst_finished_bundle)
228
229  def StartInstrumentationNoResults(
230      self, package_name, runner_name, no_window_animation=False,
231      raw_mode=False, instrumentation_args={}):
232    """Runs instrumentation and dumps output to stdout.
233
234    Equivalent to StartInstrumentation, but will dump instrumentation
235    'normal' output to stdout, instead of parsing return results. Command will
236    never timeout.
237    """
238    adb_command_string = self.PreviewInstrumentationCommand(
239        package_name, runner_name, no_window_animation=no_window_animation,
240        raw_mode=raw_mode, instrumentation_args=instrumentation_args)
241    logger.Log(adb_command_string)
242    run_command.RunCommand(adb_command_string, return_output=False)
243
244  def PreviewInstrumentationCommand(
245      self, package_name, runner_name, no_window_animation=False,
246      raw_mode=False, instrumentation_args={}):
247    """Returns a string of adb command that will be executed."""
248    inst_command_string = self._BuildInstrumentationCommand(
249        package_name, runner_name, no_window_animation=no_window_animation,
250        raw_mode=raw_mode, instrumentation_args=instrumentation_args)
251    command_string = "adb %s shell %s" % (self._target_arg, inst_command_string)
252    return command_string
253
254  def _BuildInstrumentationCommand(
255      self, package, runner_name, no_window_animation=False, profile=False,
256      raw_mode=True, instrumentation_args={}):
257    instrumentation_path = "%s/%s" % (package, runner_name)
258
259    return self._BuildInstrumentationCommandPath(
260        instrumentation_path, no_window_animation=no_window_animation,
261        profile=profile, raw_mode=raw_mode,
262        instrumentation_args=instrumentation_args)
263
264  def _BuildInstrumentationCommandPath(
265      self, instrumentation_path, no_window_animation=False, profile=False,
266      raw_mode=True, instrumentation_args={}):
267    command_string = "am instrument"
268    if no_window_animation:
269      command_string += " --no_window_animation"
270    if profile:
271      self._CreateTraceDir()
272      command_string += (
273          " -p %s/%s.dmtrace" %
274          (self.DEVICE_TRACE_DIR, instrumentation_path.split(".")[-1]))
275
276    for key, value in instrumentation_args.items():
277      command_string += " -e %s %s" % (key, value)
278    if raw_mode:
279      command_string += " -r"
280    command_string += " -w %s" % instrumentation_path
281    return command_string
282
283  def _CreateTraceDir(self):
284    ls_response = self.SendShellCommand("ls /data/trace")
285    if ls_response.strip("#").strip(string.whitespace) != "":
286      self.SendShellCommand("create /data/trace", "mkdir /data/trace")
287      self.SendShellCommand("make /data/trace world writeable",
288                            "chmod 777 /data/trace")
289
290  def WaitForDevicePm(self, wait_time=120):
291    """Waits for targeted device's package manager to be up.
292
293    Args:
294      wait_time: time in seconds to wait
295
296    Raises:
297      WaitForResponseTimedOutError if wait_time elapses and pm still does not
298      respond.
299    """
300    logger.Log("Waiting for device package manager...")
301    self.SendCommand("wait-for-device")
302    # Now the device is there, but may not be running.
303    # Query the package manager with a basic command
304    pm_found = False
305    attempts = 0
306    wait_period = 5
307    while not pm_found and (attempts*wait_period) < wait_time:
308      # assume the 'adb shell pm path android' command will always
309      # return 'package: something' in the success case
310      output = self.SendShellCommand("pm path android", retry_count=1)
311      if "package:" in output:
312        pm_found = True
313      else:
314        time.sleep(wait_period)
315        attempts += 1
316    if not pm_found:
317      raise errors.WaitForResponseTimedOutError(
318          "Package manager did not respond after %s seconds" % wait_time)
319
320  def Sync(self, retry_count=3):
321    """Perform a adb sync.
322
323    Blocks until device package manager is responding.
324
325    Args:
326      retry_count: number of times to retry sync before failing
327
328    Raises:
329      WaitForResponseTimedOutError if package manager does not respond
330      AbortError if unrecoverable error occurred
331    """
332    output = ""
333    error = None
334    try:
335      output = self.SendCommand("sync", retry_count=retry_count)
336    except errors.AbortError, e:
337      error = e
338      output = e.msg
339    if "Read-only file system" in output:
340      logger.SilentLog(output)
341      logger.Log("Remounting read-only filesystem")
342      self.SendCommand("remount")
343      output = self.SendCommand("sync", retry_count=retry_count)
344    elif "No space left on device" in output:
345      logger.SilentLog(output)
346      logger.Log("Restarting device runtime")
347      self.SendShellCommand("stop", retry_count=retry_count)
348      output = self.SendCommand("sync", retry_count=retry_count)
349      self.SendShellCommand("start", retry_count=retry_count)
350    elif error is not None:
351      # exception occurred that cannot be recovered from
352      raise error
353    logger.SilentLog(output)
354    self.WaitForDevicePm()
355    return output
356
357