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