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