Home | History | Annotate | Download | only in testrunner
      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 
     20 Assumes adb binary is currently on system path.
     21 """
     22 # Python imports
     23 import os
     24 import string
     25 import time
     26 
     27 # local imports
     28 import am_instrument_parser
     29 import errors
     30 import logger
     31 import run_command
     32 
     33 
     34 class 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(instrumentation_path, timeout_time=timeout_time,
    161                                      no_window_animation=no_window_animation,
    162                                      instrumentation_args=instrumentation_args)
    163 
    164   def StartInstrumentation(
    165       self, instrumentation_path, timeout_time=60*10, no_window_animation=False,
    166       profile=False, instrumentation_args={}):
    167 
    168     """Runs an instrumentation class on the target.
    169 
    170     Returns a dictionary containing the key value pairs from the
    171     instrumentations result bundle and a list of TestResults. Also handles the
    172     interpreting of error output from the device and raises the necessary
    173     exceptions.
    174 
    175     Args:
    176       instrumentation_path: string. It should be the fully classified package
    177       name, and instrumentation test runner, separated by "/"
    178         e.g. com.android.globaltimelaunch/.GlobalTimeLaunch
    179       timeout_time: Timeout value for the am command.
    180       no_window_animation: boolean, Whether you want window animations enabled
    181         or disabled
    182       profile: If True, profiling will be turned on for the instrumentation.
    183       instrumentation_args: Dictionary of key value bundle arguments to pass to
    184       instrumentation.
    185 
    186     Returns:
    187       (test_results, inst_finished_bundle)
    188 
    189       test_results: a list of TestResults
    190       inst_finished_bundle (dict): Key/value pairs contained in the bundle that
    191         is passed into ActivityManager.finishInstrumentation(). Included in this
    192         bundle is the return code of the Instrumentation process, any error
    193         codes reported by the activity manager, and any results explicitly added
    194         by the instrumentation code.
    195 
    196      Raises:
    197        WaitForResponseTimedOutError: if timeout occurred while waiting for
    198          response to adb instrument command
    199        DeviceUnresponsiveError: if device system process is not responding
    200        InstrumentationError: if instrumentation failed to run
    201     """
    202 
    203     command_string = self._BuildInstrumentationCommandPath(
    204         instrumentation_path, no_window_animation=no_window_animation,
    205         profile=profile, raw_mode=True,
    206         instrumentation_args=instrumentation_args)
    207     logger.Log(command_string)
    208     (test_results, inst_finished_bundle) = (
    209         am_instrument_parser.ParseAmInstrumentOutput(
    210             self.SendShellCommand(command_string, timeout_time=timeout_time,
    211                                   retry_count=2)))
    212 
    213     if "code" not in inst_finished_bundle:
    214       raise errors.InstrumentationError("no test results... device setup "
    215                                         "correctly?")
    216 
    217     if inst_finished_bundle["code"] == "0":
    218       short_msg_result = "no error message"
    219       if "shortMsg" in inst_finished_bundle:
    220         short_msg_result = inst_finished_bundle["shortMsg"]
    221         logger.Log("Error! Test run failed: %s" % short_msg_result)
    222       raise errors.InstrumentationError(short_msg_result)
    223 
    224     if "INSTRUMENTATION_ABORTED" in inst_finished_bundle:
    225       logger.Log("INSTRUMENTATION ABORTED!")
    226       raise errors.DeviceUnresponsiveError
    227 
    228     return (test_results, inst_finished_bundle)
    229 
    230   def StartInstrumentationNoResults(
    231       self, package_name, runner_name, no_window_animation=False,
    232       raw_mode=False, instrumentation_args={}):
    233     """Runs instrumentation and dumps output to stdout.
    234 
    235     Equivalent to StartInstrumentation, but will dump instrumentation
    236     'normal' output to stdout, instead of parsing return results. Command will
    237     never timeout.
    238     """
    239     adb_command_string = self.PreviewInstrumentationCommand(
    240         package_name, runner_name, no_window_animation=no_window_animation,
    241         raw_mode=raw_mode, instrumentation_args=instrumentation_args)
    242     logger.Log(adb_command_string)
    243     run_command.RunCommand(adb_command_string, return_output=False)
    244 
    245   def PreviewInstrumentationCommand(
    246       self, package_name, runner_name, no_window_animation=False,
    247       raw_mode=False, instrumentation_args={}):
    248     """Returns a string of adb command that will be executed."""
    249     inst_command_string = self._BuildInstrumentationCommand(
    250         package_name, runner_name, no_window_animation=no_window_animation,
    251         raw_mode=raw_mode, instrumentation_args=instrumentation_args)
    252     command_string = "adb %s shell %s" % (self._target_arg, inst_command_string)
    253     return command_string
    254 
    255   def _BuildInstrumentationCommand(
    256       self, package, runner_name, no_window_animation=False, profile=False,
    257       raw_mode=True, instrumentation_args={}):
    258     instrumentation_path = "%s/%s" % (package, runner_name)
    259 
    260     return self._BuildInstrumentationCommandPath(
    261         instrumentation_path, no_window_animation=no_window_animation,
    262         profile=profile, raw_mode=raw_mode,
    263         instrumentation_args=instrumentation_args)
    264 
    265   def _BuildInstrumentationCommandPath(
    266       self, instrumentation_path, no_window_animation=False, profile=False,
    267       raw_mode=True, instrumentation_args={}):
    268     command_string = "am instrument"
    269     if no_window_animation:
    270       command_string += " --no_window_animation"
    271     if profile:
    272       self._CreateTraceDir()
    273       command_string += (
    274           " -p %s/%s.dmtrace" %
    275           (self.DEVICE_TRACE_DIR, instrumentation_path.split(".")[-1]))
    276 
    277     for key, value in instrumentation_args.items():
    278       command_string += " -e %s '%s'" % (key, value)
    279     if raw_mode:
    280       command_string += " -r"
    281     command_string += " -w %s" % instrumentation_path
    282     return command_string
    283 
    284   def _CreateTraceDir(self):
    285     ls_response = self.SendShellCommand("ls /data/trace")
    286     if ls_response.strip("#").strip(string.whitespace) != "":
    287       self.SendShellCommand("create /data/trace", "mkdir /data/trace")
    288       self.SendShellCommand("make /data/trace world writeable",
    289                             "chmod 777 /data/trace")
    290 
    291   def WaitForDevicePm(self, wait_time=120):
    292     """Waits for targeted device's package manager to be up.
    293 
    294     Args:
    295       wait_time: time in seconds to wait
    296 
    297     Raises:
    298       WaitForResponseTimedOutError if wait_time elapses and pm still does not
    299       respond.
    300     """
    301     logger.Log("Waiting for device package manager...")
    302     self.SendCommand("wait-for-device")
    303     # Now the device is there, but may not be running.
    304     # Query the package manager with a basic command
    305     pm_found = False
    306     attempts = 0
    307     wait_period = 5
    308     while not pm_found and (attempts*wait_period) < wait_time:
    309       # assume the 'adb shell pm path android' command will always
    310       # return 'package: something' in the success case
    311       output = self.SendShellCommand("pm path android", retry_count=1)
    312       if "package:" in output:
    313         pm_found = True
    314       else:
    315         time.sleep(wait_period)
    316         attempts += 1
    317     if not pm_found:
    318       raise errors.WaitForResponseTimedOutError(
    319           "Package manager did not respond after %s seconds" % wait_time)
    320 
    321   def WaitForInstrumentation(self, package_name, runner_name, wait_time=120):
    322     """Waits for given instrumentation to be present on device
    323 
    324     Args:
    325       wait_time: time in seconds to wait
    326 
    327     Raises:
    328       WaitForResponseTimedOutError if wait_time elapses and instrumentation
    329       still not present.
    330     """
    331     instrumentation_path = "%s/%s" % (package_name, runner_name)
    332     logger.Log("Waiting for instrumentation to be present")
    333     # Query the package manager
    334     inst_found = False
    335     attempts = 0
    336     wait_period = 5
    337     while not inst_found and (attempts*wait_period) < wait_time:
    338       # assume the 'adb shell pm list instrumentation'
    339       # return 'instrumentation: something' in the success case
    340       try:
    341         output = self.SendShellCommand("pm list instrumentation | grep %s"
    342                                        % instrumentation_path, retry_count=1)
    343         if "instrumentation:" in output:
    344           inst_found = True
    345       except errors.AbortError, e:
    346         # ignore
    347         pass
    348       if not inst_found:
    349         time.sleep(wait_period)
    350         attempts += 1
    351     if not inst_found:
    352       logger.Log(
    353           "Could not find instrumentation %s on device. Does the "
    354           "instrumentation in test's AndroidManifest.xml match definition"
    355           "in test_defs.xml?" % instrumentation_path)
    356       raise errors.WaitForResponseTimedOutError()
    357 
    358   def Sync(self, retry_count=3):
    359     """Perform a adb sync.
    360 
    361     Blocks until device package manager is responding.
    362 
    363     Args:
    364       retry_count: number of times to retry sync before failing
    365 
    366     Raises:
    367       WaitForResponseTimedOutError if package manager does not respond
    368       AbortError if unrecoverable error occurred
    369     """
    370     output = ""
    371     error = None
    372     try:
    373       output = self.SendCommand("sync", retry_count=retry_count)
    374     except errors.AbortError, e:
    375       error = e
    376       output = e.msg
    377     if "Read-only file system" in output:
    378       logger.SilentLog(output)
    379       logger.Log("Remounting read-only filesystem")
    380       self.SendCommand("remount")
    381       output = self.SendCommand("sync", retry_count=retry_count)
    382     elif "No space left on device" in output:
    383       logger.SilentLog(output)
    384       logger.Log("Restarting device runtime")
    385       self.SendShellCommand("stop", retry_count=retry_count)
    386       output = self.SendCommand("sync", retry_count=retry_count)
    387       self.SendShellCommand("start", retry_count=retry_count)
    388     elif error is not None:
    389       # exception occurred that cannot be recovered from
    390       raise error
    391     logger.SilentLog(output)
    392     self.WaitForDevicePm()
    393     return output
    394 
    395   def GetSerialNumber(self):
    396     """Returns the serial number of the targeted device."""
    397     return self.SendCommand("get-serialno").strip()
    398 
    399