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 Install(self, apk_path):
    135     """Installs apk on device.
    136 
    137     Args:
    138       apk_path: file path to apk file on host
    139 
    140     Returns:
    141       output of install command
    142     """
    143     return self.SendCommand("install -r %s" % apk_path)
    144 
    145   def DoesFileExist(self, src):
    146     """Checks if the given path exists on device target.
    147 
    148     Args:
    149       src: file path to be checked.
    150 
    151     Returns:
    152       True if file exists
    153     """
    154 
    155     output = self.SendShellCommand("ls %s" % src)
    156     error = "No such file or directory"
    157 
    158     if error in output:
    159       return False
    160     return True
    161 
    162   def EnableAdbRoot(self):
    163     """Enable adb root on device."""
    164     output = self.SendCommand("root")
    165     if "adbd is already running as root" in output:
    166       return True
    167     elif "restarting adbd as root" in output:
    168       # device will disappear from adb, wait for it to come back
    169       time.sleep(2)
    170       self.SendCommand("wait-for-device")
    171       return True
    172     else:
    173       logger.Log("Unrecognized output from adb root: %s" % output)
    174       return False
    175 
    176   def StartInstrumentationForPackage(
    177       self, package_name, runner_name, timeout_time=60*10,
    178       no_window_animation=False, instrumentation_args={}):
    179     """Run instrumentation test for given package and runner.
    180 
    181     Equivalent to StartInstrumentation, except instrumentation path is
    182     separated into its package and runner components.
    183     """
    184     instrumentation_path = "%s/%s" % (package_name, runner_name)
    185     return self.StartInstrumentation(instrumentation_path, timeout_time=timeout_time,
    186                                      no_window_animation=no_window_animation,
    187                                      instrumentation_args=instrumentation_args)
    188 
    189   def StartInstrumentation(
    190       self, instrumentation_path, timeout_time=60*10, no_window_animation=False,
    191       profile=False, instrumentation_args={}):
    192 
    193     """Runs an instrumentation class on the target.
    194 
    195     Returns a dictionary containing the key value pairs from the
    196     instrumentations result bundle and a list of TestResults. Also handles the
    197     interpreting of error output from the device and raises the necessary
    198     exceptions.
    199 
    200     Args:
    201       instrumentation_path: string. It should be the fully classified package
    202       name, and instrumentation test runner, separated by "/"
    203         e.g. com.android.globaltimelaunch/.GlobalTimeLaunch
    204       timeout_time: Timeout value for the am command.
    205       no_window_animation: boolean, Whether you want window animations enabled
    206         or disabled
    207       profile: If True, profiling will be turned on for the instrumentation.
    208       instrumentation_args: Dictionary of key value bundle arguments to pass to
    209       instrumentation.
    210 
    211     Returns:
    212       (test_results, inst_finished_bundle)
    213 
    214       test_results: a list of TestResults
    215       inst_finished_bundle (dict): Key/value pairs contained in the bundle that
    216         is passed into ActivityManager.finishInstrumentation(). Included in this
    217         bundle is the return code of the Instrumentation process, any error
    218         codes reported by the activity manager, and any results explicitly added
    219         by the instrumentation code.
    220 
    221      Raises:
    222        WaitForResponseTimedOutError: if timeout occurred while waiting for
    223          response to adb instrument command
    224        DeviceUnresponsiveError: if device system process is not responding
    225        InstrumentationError: if instrumentation failed to run
    226     """
    227 
    228     command_string = self._BuildInstrumentationCommandPath(
    229         instrumentation_path, no_window_animation=no_window_animation,
    230         profile=profile, raw_mode=True,
    231         instrumentation_args=instrumentation_args)
    232     logger.Log(command_string)
    233     (test_results, inst_finished_bundle) = (
    234         am_instrument_parser.ParseAmInstrumentOutput(
    235             self.SendShellCommand(command_string, timeout_time=timeout_time,
    236                                   retry_count=2)))
    237 
    238     if "code" not in inst_finished_bundle:
    239       raise errors.InstrumentationError("no test results... device setup "
    240                                         "correctly?")
    241 
    242     if inst_finished_bundle["code"] == "0":
    243       short_msg_result = "no error message"
    244       if "shortMsg" in inst_finished_bundle:
    245         short_msg_result = inst_finished_bundle["shortMsg"]
    246         logger.Log("Error! Test run failed: %s" % short_msg_result)
    247       raise errors.InstrumentationError(short_msg_result)
    248 
    249     if "INSTRUMENTATION_ABORTED" in inst_finished_bundle:
    250       logger.Log("INSTRUMENTATION ABORTED!")
    251       raise errors.DeviceUnresponsiveError
    252 
    253     return (test_results, inst_finished_bundle)
    254 
    255   def StartInstrumentationNoResults(
    256       self, package_name, runner_name, no_window_animation=False,
    257       raw_mode=False, instrumentation_args={}):
    258     """Runs instrumentation and dumps output to stdout.
    259 
    260     Equivalent to StartInstrumentation, but will dump instrumentation
    261     'normal' output to stdout, instead of parsing return results. Command will
    262     never timeout.
    263     """
    264     adb_command_string = self.PreviewInstrumentationCommand(
    265         package_name, runner_name, no_window_animation=no_window_animation,
    266         raw_mode=raw_mode, instrumentation_args=instrumentation_args)
    267     logger.Log(adb_command_string)
    268     run_command.RunCommand(adb_command_string, return_output=False)
    269 
    270   def PreviewInstrumentationCommand(
    271       self, package_name, runner_name, no_window_animation=False,
    272       raw_mode=False, instrumentation_args={}):
    273     """Returns a string of adb command that will be executed."""
    274     inst_command_string = self._BuildInstrumentationCommand(
    275         package_name, runner_name, no_window_animation=no_window_animation,
    276         raw_mode=raw_mode, instrumentation_args=instrumentation_args)
    277     return self.PreviewShellCommand(inst_command_string)
    278 
    279   def PreviewShellCommand(self, cmd):
    280     return "adb %s shell %s" % (self._target_arg, cmd)
    281 
    282   def _BuildInstrumentationCommand(
    283       self, package, runner_name, no_window_animation=False, profile=False,
    284       raw_mode=True, instrumentation_args={}):
    285     instrumentation_path = "%s/%s" % (package, runner_name)
    286 
    287     return self._BuildInstrumentationCommandPath(
    288         instrumentation_path, no_window_animation=no_window_animation,
    289         profile=profile, raw_mode=raw_mode,
    290         instrumentation_args=instrumentation_args)
    291 
    292   def _BuildInstrumentationCommandPath(
    293       self, instrumentation_path, no_window_animation=False, profile=False,
    294       raw_mode=True, instrumentation_args={}):
    295     command_string = "am instrument"
    296     if no_window_animation:
    297       command_string += " --no_window_animation"
    298     if profile:
    299       self._CreateTraceDir()
    300       command_string += (
    301           " -p %s/%s.dmtrace" %
    302           (self.DEVICE_TRACE_DIR, instrumentation_path.split(".")[-1]))
    303 
    304     for key, value in instrumentation_args.items():
    305       command_string += " -e %s '%s'" % (key, value)
    306     if raw_mode:
    307       command_string += " -r"
    308     command_string += " -w '%s'" % instrumentation_path
    309     return command_string
    310 
    311   def _CreateTraceDir(self):
    312     ls_response = self.SendShellCommand("ls /data/trace")
    313     if ls_response.strip("#").strip(string.whitespace) != "":
    314       self.SendShellCommand("create /data/trace", "mkdir /data/trace")
    315       self.SendShellCommand("make /data/trace world writeable",
    316                             "chmod 777 /data/trace")
    317 
    318   def WaitForDevicePm(self, wait_time=120):
    319     """Waits for targeted device's package manager to be up.
    320 
    321     Args:
    322       wait_time: time in seconds to wait
    323 
    324     Raises:
    325       WaitForResponseTimedOutError if wait_time elapses and pm still does not
    326       respond.
    327     """
    328     logger.Log("Waiting for device package manager...")
    329     self.SendCommand("wait-for-device")
    330     # Now the device is there, but may not be running.
    331     # Query the package manager with a basic command
    332     try:
    333       self._WaitForShellCommandContents("pm path android", "package:",
    334                                         wait_time)
    335     except errors.WaitForResponseTimedOutError:
    336       raise errors.WaitForResponseTimedOutError(
    337           "Package manager did not respond after %s seconds" % wait_time)
    338 
    339   def IsInstrumentationInstalled(self, package_name, runner_name):
    340     """Checks if instrumentation is present on device."""
    341     instrumentation_path = "%s/%s" % (package_name, runner_name)
    342     command = "pm list instrumentation | grep %s" % instrumentation_path
    343     try:
    344       output = self.SendShellCommand(command)
    345       return output.startswith("instrumentation:")
    346     except errors.AbortError:
    347       # command can return error code on failure
    348       return False
    349 
    350   def WaitForProcess(self, name, wait_time=120):
    351     """Wait until a process is running on the device.
    352 
    353     Args:
    354       name: the process name as it appears in `ps`
    355       wait_time: time in seconds to wait
    356 
    357     Raises:
    358       WaitForResponseTimedOutError if wait_time elapses and the process is
    359           still not running
    360     """
    361     logger.Log("Waiting for process %s" % name)
    362     self.SendCommand("wait-for-device")
    363     self._WaitForShellCommandContents("ps", name, wait_time)
    364 
    365   def WaitForProcessEnd(self, name, wait_time=120):
    366     """Wait until a process is no longer running on the device.
    367 
    368     Args:
    369       name: the process name as it appears in `ps`
    370       wait_time: time in seconds to wait
    371 
    372     Raises:
    373       WaitForResponseTimedOutError if wait_time elapses and the process is
    374           still running
    375     """
    376     logger.Log("Waiting for process %s to end" % name)
    377     self._WaitForShellCommandContents("ps", name, wait_time, invert=True)
    378 
    379   def _WaitForShellCommandContents(self, command, expected, wait_time,
    380                                    raise_abort=True, invert=False):
    381     """Wait until the response to a command contains a given output.
    382 
    383     Assumes that a only successful execution of "adb shell <command>" contains
    384     the substring expected. Assumes that a device is present.
    385 
    386     Args:
    387       command: adb shell command to execute
    388       expected: the string that should appear to consider the
    389           command successful.
    390       wait_time: time in seconds to wait
    391       raise_abort: if False, retry when executing the command raises an
    392           AbortError, rather than failing.
    393       invert: if True, wait until the command output no longer contains the
    394           expected contents.
    395 
    396     Raises:
    397       WaitForResponseTimedOutError: If wait_time elapses and the command has not
    398           returned an output containing expected yet.
    399     """
    400     # Query the device with the command
    401     success = False
    402     attempts = 0
    403     wait_period = 5
    404     while not success and (attempts*wait_period) < wait_time:
    405       # assume the command will always contain expected in the success case
    406       try:
    407         output = self.SendShellCommand(command, retry_count=1)
    408         if ((not invert and expected in output)
    409             or (invert and expected not in output)):
    410           success = True
    411       except errors.AbortError, e:
    412         if raise_abort:
    413           raise
    414         # ignore otherwise
    415 
    416       if not success:
    417         time.sleep(wait_period)
    418         attempts += 1
    419 
    420     if not success:
    421       raise errors.WaitForResponseTimedOutError()
    422 
    423   def WaitForBootComplete(self, wait_time=120):
    424     """Waits for targeted device's bootcomplete flag to be set.
    425 
    426     Args:
    427       wait_time: time in seconds to wait
    428 
    429     Raises:
    430       WaitForResponseTimedOutError if wait_time elapses and pm still does not
    431       respond.
    432     """
    433     logger.Log("Waiting for boot complete...")
    434     self.SendCommand("wait-for-device")
    435     # Now the device is there, but may not be running.
    436     # Query the package manager with a basic command
    437     boot_complete = False
    438     attempts = 0
    439     wait_period = 5
    440     while not boot_complete and (attempts*wait_period) < wait_time:
    441       output = self.SendShellCommand("getprop dev.bootcomplete", retry_count=1)
    442       output = output.strip()
    443       if output == "1":
    444         boot_complete = True
    445       else:
    446         time.sleep(wait_period)
    447         attempts += 1
    448     if not boot_complete:
    449       raise errors.WaitForResponseTimedOutError(
    450           "dev.bootcomplete flag was not set after %s seconds" % wait_time)
    451 
    452   def Sync(self, retry_count=3, runtime_restart=False):
    453     """Perform a adb sync.
    454 
    455     Blocks until device package manager is responding.
    456 
    457     Args:
    458       retry_count: number of times to retry sync before failing
    459       runtime_restart: stop runtime during sync and restart afterwards, useful
    460         for syncing system libraries (core, framework etc)
    461 
    462     Raises:
    463       WaitForResponseTimedOutError if package manager does not respond
    464       AbortError if unrecoverable error occurred
    465     """
    466     output = ""
    467     error = None
    468     if runtime_restart:
    469       self.SendShellCommand("setprop ro.test_harness 1", retry_count=retry_count)
    470       # manual rest bootcomplete flag
    471       self.SendShellCommand("setprop dev.bootcomplete 0",
    472                             retry_count=retry_count)
    473       self.SendShellCommand("stop", retry_count=retry_count)
    474 
    475     try:
    476       output = self.SendCommand("sync", retry_count=retry_count)
    477     except errors.AbortError, e:
    478       error = e
    479       output = e.msg
    480     if "Read-only file system" in output:
    481       logger.SilentLog(output)
    482       logger.Log("Remounting read-only filesystem")
    483       self.SendCommand("remount")
    484       output = self.SendCommand("sync", retry_count=retry_count)
    485     elif "No space left on device" in output:
    486       logger.SilentLog(output)
    487       logger.Log("Restarting device runtime")
    488       self.SendShellCommand("stop", retry_count=retry_count)
    489       output = self.SendCommand("sync", retry_count=retry_count)
    490       self.SendShellCommand("start", retry_count=retry_count)
    491     elif error is not None:
    492       # exception occurred that cannot be recovered from
    493       raise error
    494     logger.SilentLog(output)
    495     if runtime_restart:
    496       # start runtime and wait till boot complete flag is set
    497       self.SendShellCommand("start", retry_count=retry_count)
    498       self.WaitForBootComplete()
    499       # press the MENU key, this will disable key guard if runtime is started
    500       # with ro.monkey set to 1
    501       self.SendShellCommand("input keyevent 82", retry_count=retry_count)
    502     else:
    503       self.WaitForDevicePm()
    504     return output
    505 
    506   def GetSerialNumber(self):
    507     """Returns the serial number of the targeted device."""
    508     return self.SendCommand("get-serialno").strip()
    509