android_commands.py revision 868fa2fe829687343ffae624259930155e16dbd8
1# Copyright (c) 2012 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Provides an interface to communicate with the device via the adb command.
6
7Assumes adb binary is currently on system path.
8"""
9
10import collections
11import datetime
12import logging
13import os
14import re
15import shlex
16import subprocess
17import sys
18import tempfile
19import time
20
21import cmd_helper
22import constants
23import io_stats_parser
24try:
25  import pexpect
26except:
27  pexpect = None
28
29sys.path.append(os.path.join(
30    constants.DIR_SOURCE_ROOT, 'third_party', 'android_testrunner'))
31import adb_interface
32import am_instrument_parser
33import errors
34
35
36# Pattern to search for the next whole line of pexpect output and capture it
37# into a match group. We can't use ^ and $ for line start end with pexpect,
38# see http://www.noah.org/python/pexpect/#doc for explanation why.
39PEXPECT_LINE_RE = re.compile('\n([^\r]*)\r')
40
41# Set the adb shell prompt to be a unique marker that will [hopefully] not
42# appear at the start of any line of a command's output.
43SHELL_PROMPT = '~+~PQ\x17RS~+~'
44
45# Java properties file
46LOCAL_PROPERTIES_PATH = '/data/local.prop'
47
48# Property in /data/local.prop that controls Java assertions.
49JAVA_ASSERT_PROPERTY = 'dalvik.vm.enableassertions'
50
51MEMORY_INFO_RE = re.compile('^(?P<key>\w+):\s+(?P<usage_kb>\d+) kB$')
52NVIDIA_MEMORY_INFO_RE = re.compile('^\s*(?P<user>\S+)\s*(?P<name>\S+)\s*'
53                                   '(?P<pid>\d+)\s*(?P<usage_bytes>\d+)$')
54
55# Keycode "enum" suitable for passing to AndroidCommands.SendKey().
56KEYCODE_HOME = 3
57KEYCODE_BACK = 4
58KEYCODE_DPAD_UP = 19
59KEYCODE_DPAD_DOWN = 20
60KEYCODE_DPAD_RIGHT = 22
61KEYCODE_ENTER = 66
62KEYCODE_MENU = 82
63
64MD5SUM_DEVICE_FOLDER = constants.TEST_EXECUTABLE_DIR + '/md5sum/'
65MD5SUM_DEVICE_PATH = MD5SUM_DEVICE_FOLDER + 'md5sum_bin'
66MD5SUM_LD_LIBRARY_PATH = 'LD_LIBRARY_PATH=%s' % MD5SUM_DEVICE_FOLDER
67
68def GetEmulators():
69  """Returns a list of emulators.  Does not filter by status (e.g. offline).
70
71  Both devices starting with 'emulator' will be returned in below output:
72
73    * daemon not running. starting it now on port 5037 *
74    * daemon started successfully *
75    List of devices attached
76    027c10494100b4d7        device
77    emulator-5554   offline
78    emulator-5558   device
79  """
80  re_device = re.compile('^emulator-[0-9]+', re.MULTILINE)
81  devices = re_device.findall(cmd_helper.GetCmdOutput([constants.ADB_PATH,
82                                                       'devices']))
83  return devices
84
85
86def GetAVDs():
87  """Returns a list of AVDs."""
88  re_avd = re.compile('^[ ]+Name: ([a-zA-Z0-9_:.-]+)', re.MULTILINE)
89  avds = re_avd.findall(cmd_helper.GetCmdOutput(['android', 'list', 'avd']))
90  return avds
91
92
93def GetAttachedDevices():
94  """Returns a list of attached, online android devices.
95
96  If a preferred device has been set with ANDROID_SERIAL, it will be first in
97  the returned list.
98
99  Example output:
100
101    * daemon not running. starting it now on port 5037 *
102    * daemon started successfully *
103    List of devices attached
104    027c10494100b4d7        device
105    emulator-5554   offline
106  """
107  re_device = re.compile('^([a-zA-Z0-9_:.-]+)\tdevice$', re.MULTILINE)
108  devices = re_device.findall(cmd_helper.GetCmdOutput([constants.ADB_PATH,
109                                                       'devices']))
110  preferred_device = os.environ.get('ANDROID_SERIAL')
111  if preferred_device in devices:
112    devices.remove(preferred_device)
113    devices.insert(0, preferred_device)
114  return devices
115
116
117def IsDeviceAttached(device):
118  return device in GetAttachedDevices()
119
120
121def _GetFilesFromRecursiveLsOutput(path, ls_output, re_file, utc_offset=None):
122  """Gets a list of files from `ls` command output.
123
124  Python's os.walk isn't used because it doesn't work over adb shell.
125
126  Args:
127    path: The path to list.
128    ls_output: A list of lines returned by an `ls -lR` command.
129    re_file: A compiled regular expression which parses a line into named groups
130        consisting of at minimum "filename", "date", "time", "size" and
131        optionally "timezone".
132    utc_offset: A 5-character string of the form +HHMM or -HHMM, where HH is a
133        2-digit string giving the number of UTC offset hours, and MM is a
134        2-digit string giving the number of UTC offset minutes. If the input
135        utc_offset is None, will try to look for the value of "timezone" if it
136        is specified in re_file.
137
138  Returns:
139    A dict of {"name": (size, lastmod), ...} where:
140      name: The file name relative to |path|'s directory.
141      size: The file size in bytes (0 for directories).
142      lastmod: The file last modification date in UTC.
143  """
144  re_directory = re.compile('^%s/(?P<dir>[^:]+):$' % re.escape(path))
145  path_dir = os.path.dirname(path)
146
147  current_dir = ''
148  files = {}
149  for line in ls_output:
150    directory_match = re_directory.match(line)
151    if directory_match:
152      current_dir = directory_match.group('dir')
153      continue
154    file_match = re_file.match(line)
155    if file_match:
156      filename = os.path.join(current_dir, file_match.group('filename'))
157      if filename.startswith(path_dir):
158        filename = filename[len(path_dir) + 1:]
159      lastmod = datetime.datetime.strptime(
160          file_match.group('date') + ' ' + file_match.group('time')[:5],
161          '%Y-%m-%d %H:%M')
162      if not utc_offset and 'timezone' in re_file.groupindex:
163        utc_offset = file_match.group('timezone')
164      if isinstance(utc_offset, str) and len(utc_offset) == 5:
165        utc_delta = datetime.timedelta(hours=int(utc_offset[1:3]),
166                                       minutes=int(utc_offset[3:5]))
167        if utc_offset[0:1] == '-':
168          utc_delta = -utc_delta
169        lastmod -= utc_delta
170      files[filename] = (int(file_match.group('size')), lastmod)
171  return files
172
173
174def _ComputeFileListHash(md5sum_output):
175  """Returns a list of MD5 strings from the provided md5sum output."""
176  return [line.split('  ')[0] for line in md5sum_output]
177
178
179def _HasAdbPushSucceeded(command_output):
180  """Returns whether adb push has succeeded from the provided output."""
181  if not command_output:
182    return False
183  # Success looks like this: "3035 KB/s (12512056 bytes in 4.025s)"
184  # Errors look like this: "failed to copy  ... "
185  if not re.search('^[0-9]', command_output.splitlines()[-1]):
186    logging.critical('PUSH FAILED: ' + command_output)
187    return False
188  return True
189
190
191def GetLogTimestamp(log_line, year):
192  """Returns the timestamp of the given |log_line| in the given year."""
193  try:
194    return datetime.datetime.strptime('%s-%s' % (year, log_line[:18]),
195                                      '%Y-%m-%d %H:%M:%S.%f')
196  except (ValueError, IndexError):
197    logging.critical('Error reading timestamp from ' + log_line)
198    return None
199
200
201class AndroidCommands(object):
202  """Helper class for communicating with Android device via adb.
203
204  Args:
205    device: If given, adb commands are only send to the device of this ID.
206        Otherwise commands are sent to all attached devices.
207  """
208
209  def __init__(self, device=None):
210    adb_dir = os.path.dirname(constants.ADB_PATH)
211    if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
212      # Required by third_party/android_testrunner to call directly 'adb'.
213      os.environ['PATH'] += os.pathsep + adb_dir
214    self._adb = adb_interface.AdbInterface()
215    if device:
216      self._adb.SetTargetSerial(device)
217    self._device = device
218    self._logcat = None
219    self.logcat_process = None
220    self._logcat_tmpoutfile = None
221    self._pushed_files = []
222    self._device_utc_offset = self.RunShellCommand('date +%z')[0]
223    self._md5sum_build_dir = ''
224    self._external_storage = ''
225    self._util_wrapper = ''
226
227  def _LogShell(self, cmd):
228    """Logs the adb shell command."""
229    if self._device:
230      device_repr = self._device[-4:]
231    else:
232      device_repr = '????'
233    logging.info('[%s]> %s', device_repr, cmd)
234
235  def Adb(self):
236    """Returns our AdbInterface to avoid us wrapping all its methods."""
237    return self._adb
238
239  def IsOnline(self):
240    """Checks whether the device is online.
241
242    Returns:
243      True if device is in 'device' mode, False otherwise.
244    """
245    out = self._adb.SendCommand('get-state')
246    return out.strip() == 'device'
247
248  def IsRootEnabled(self):
249    """Checks if root is enabled on the device."""
250    root_test_output = self.RunShellCommand('ls /root') or ['']
251    return not 'Permission denied' in root_test_output[0]
252
253  def EnableAdbRoot(self):
254    """Enables adb root on the device.
255
256    Returns:
257      True: if output from executing adb root was as expected.
258      False: otherwise.
259    """
260    if self.GetBuildType() == 'user':
261      logging.warning("Can't enable root in production builds with type user")
262      return False
263    else:
264      return_value = self._adb.EnableAdbRoot()
265      # EnableAdbRoot inserts a call for wait-for-device only when adb logcat
266      # output matches what is expected. Just to be safe add a call to
267      # wait-for-device.
268      self._adb.SendCommand('wait-for-device')
269      return return_value
270
271  def GetDeviceYear(self):
272    """Returns the year information of the date on device."""
273    return self.RunShellCommand('date +%Y')[0]
274
275  def GetExternalStorage(self):
276    if not self._external_storage:
277      self._external_storage = self.RunShellCommand('echo $EXTERNAL_STORAGE')[0]
278      assert self._external_storage, 'Unable to find $EXTERNAL_STORAGE'
279    return self._external_storage
280
281  def WaitForDevicePm(self):
282    """Blocks until the device's package manager is available.
283
284    To workaround http://b/5201039, we restart the shell and retry if the
285    package manager isn't back after 120 seconds.
286
287    Raises:
288      errors.WaitForResponseTimedOutError after max retries reached.
289    """
290    last_err = None
291    retries = 3
292    while retries:
293      try:
294        self._adb.WaitForDevicePm()
295        return  # Success
296      except errors.WaitForResponseTimedOutError as e:
297        last_err = e
298        logging.warning('Restarting and retrying after timeout: %s', e)
299        retries -= 1
300        self.RestartShell()
301    raise last_err  # Only reached after max retries, re-raise the last error.
302
303  def RestartShell(self):
304    """Restarts the shell on the device. Does not block for it to return."""
305    self.RunShellCommand('stop')
306    self.RunShellCommand('start')
307
308  def Reboot(self, full_reboot=True):
309    """Reboots the device and waits for the package manager to return.
310
311    Args:
312      full_reboot: Whether to fully reboot the device or just restart the shell.
313    """
314    # TODO(torne): hive can't reboot the device either way without breaking the
315    # connection; work out if we can handle this better
316    if os.environ.get('USING_HIVE'):
317      logging.warning('Ignoring reboot request as we are on hive')
318      return
319    if full_reboot or not self.IsRootEnabled():
320      self._adb.SendCommand('reboot')
321      timeout = 300
322    else:
323      self.RestartShell()
324      timeout = 120
325    # To run tests we need at least the package manager and the sd card (or
326    # other external storage) to be ready.
327    self.WaitForDevicePm()
328    self.WaitForSdCardReady(timeout)
329
330  def Uninstall(self, package):
331    """Uninstalls the specified package from the device.
332
333    Args:
334      package: Name of the package to remove.
335
336    Returns:
337      A status string returned by adb uninstall
338    """
339    uninstall_command = 'uninstall %s' % package
340
341    self._LogShell(uninstall_command)
342    return self._adb.SendCommand(uninstall_command, timeout_time=60)
343
344  def Install(self, package_file_path, reinstall=False):
345    """Installs the specified package to the device.
346
347    Args:
348      package_file_path: Path to .apk file to install.
349      reinstall: Reinstall an existing apk, keeping the data.
350
351    Returns:
352      A status string returned by adb install
353    """
354    assert os.path.isfile(package_file_path), ('<%s> is not file' %
355                                               package_file_path)
356
357    install_cmd = ['install']
358
359    if reinstall:
360      install_cmd.append('-r')
361
362    install_cmd.append(package_file_path)
363    install_cmd = ' '.join(install_cmd)
364
365    self._LogShell(install_cmd)
366    return self._adb.SendCommand(install_cmd,
367                                 timeout_time=2 * 60,
368                                 retry_count=0)
369
370  def ManagedInstall(self, apk_path, keep_data=False, package_name=None,
371                     reboots_on_failure=2):
372    """Installs specified package and reboots device on timeouts.
373
374    Args:
375      apk_path: Path to .apk file to install.
376      keep_data: Reinstalls instead of uninstalling first, preserving the
377        application data.
378      package_name: Package name (only needed if keep_data=False).
379      reboots_on_failure: number of time to reboot if package manager is frozen.
380
381    Returns:
382      A status string returned by adb install
383    """
384    reboots_left = reboots_on_failure
385    while True:
386      try:
387        if not keep_data:
388          assert package_name
389          self.Uninstall(package_name)
390        install_status = self.Install(apk_path, reinstall=keep_data)
391        if 'Success' in install_status:
392          return install_status
393      except errors.WaitForResponseTimedOutError:
394        print '@@@STEP_WARNINGS@@@'
395        logging.info('Timeout on installing %s on device %s', apk_path,
396                     self._device)
397
398      if reboots_left <= 0:
399        raise Exception('Install failure')
400
401      # Force a hard reboot on last attempt
402      self.Reboot(full_reboot=(reboots_left == 1))
403      reboots_left -= 1
404
405  def MakeSystemFolderWritable(self):
406    """Remounts the /system folder rw."""
407    out = self._adb.SendCommand('remount')
408    if out.strip() != 'remount succeeded':
409      raise errors.MsgException('Remount failed: %s' % out)
410
411  def RestartAdbServer(self):
412    """Restart the adb server."""
413    self.KillAdbServer()
414    self.StartAdbServer()
415
416  def KillAdbServer(self):
417    """Kill adb server."""
418    adb_cmd = [constants.ADB_PATH, 'kill-server']
419    return cmd_helper.RunCmd(adb_cmd)
420
421  def StartAdbServer(self):
422    """Start adb server."""
423    adb_cmd = [constants.ADB_PATH, 'start-server']
424    return cmd_helper.RunCmd(adb_cmd)
425
426  def WaitForSystemBootCompleted(self, wait_time):
427    """Waits for targeted system's boot_completed flag to be set.
428
429    Args:
430      wait_time: time in seconds to wait
431
432    Raises:
433      WaitForResponseTimedOutError if wait_time elapses and flag still not
434      set.
435    """
436    logging.info('Waiting for system boot completed...')
437    self._adb.SendCommand('wait-for-device')
438    # Now the device is there, but system not boot completed.
439    # Query the sys.boot_completed flag with a basic command
440    boot_completed = False
441    attempts = 0
442    wait_period = 5
443    while not boot_completed and (attempts * wait_period) < wait_time:
444      output = self._adb.SendShellCommand('getprop sys.boot_completed',
445                                          retry_count=1)
446      output = output.strip()
447      if output == '1':
448        boot_completed = True
449      else:
450        # If 'error: xxx' returned when querying the flag, it means
451        # adb server lost the connection to the emulator, so restart the adb
452        # server.
453        if 'error:' in output:
454          self.RestartAdbServer()
455        time.sleep(wait_period)
456        attempts += 1
457    if not boot_completed:
458      raise errors.WaitForResponseTimedOutError(
459          'sys.boot_completed flag was not set after %s seconds' % wait_time)
460
461  def WaitForSdCardReady(self, timeout_time):
462    """Wait for the SD card ready before pushing data into it."""
463    logging.info('Waiting for SD card ready...')
464    sdcard_ready = False
465    attempts = 0
466    wait_period = 5
467    external_storage = self.GetExternalStorage()
468    while not sdcard_ready and attempts * wait_period < timeout_time:
469      output = self.RunShellCommand('ls ' + external_storage)
470      if output:
471        sdcard_ready = True
472      else:
473        time.sleep(wait_period)
474        attempts += 1
475    if not sdcard_ready:
476      raise errors.WaitForResponseTimedOutError(
477          'SD card not ready after %s seconds' % timeout_time)
478
479  # It is tempting to turn this function into a generator, however this is not
480  # possible without using a private (local) adb_shell instance (to ensure no
481  # other command interleaves usage of it), which would defeat the main aim of
482  # being able to reuse the adb shell instance across commands.
483  def RunShellCommand(self, command, timeout_time=20, log_result=False):
484    """Send a command to the adb shell and return the result.
485
486    Args:
487      command: String containing the shell command to send. Must not include
488               the single quotes as we use them to escape the whole command.
489      timeout_time: Number of seconds to wait for command to respond before
490        retrying, used by AdbInterface.SendShellCommand.
491      log_result: Boolean to indicate whether we should log the result of the
492                  shell command.
493
494    Returns:
495      list containing the lines of output received from running the command
496    """
497    self._LogShell(command)
498    if "'" in command: logging.warning(command + " contains ' quotes")
499    result = self._adb.SendShellCommand(
500        "'%s'" % command, timeout_time).splitlines()
501    if ['error: device not found'] == result:
502      raise errors.DeviceUnresponsiveError('device not found')
503    if log_result:
504      self._LogShell('\n'.join(result))
505    return result
506
507  def GetShellCommandStatusAndOutput(self, command, timeout_time=20,
508                                     log_result=False):
509    """See RunShellCommand() above.
510
511    Returns:
512      The tuple (exit code, list of output lines).
513    """
514    lines = self.RunShellCommand(
515        command + '; echo %$?', timeout_time, log_result)
516    last_line = lines[-1]
517    status_pos = last_line.rfind('%')
518    assert status_pos >= 0
519    status = int(last_line[status_pos + 1:])
520    if status_pos == 0:
521      lines = lines[:-1]
522    else:
523      lines = lines[:-1] + [last_line[:status_pos]]
524    return (status, lines)
525
526  def KillAll(self, process):
527    """Android version of killall, connected via adb.
528
529    Args:
530      process: name of the process to kill off
531
532    Returns:
533      the number of processes killed
534    """
535    pids = self.ExtractPid(process)
536    if pids:
537      self.RunShellCommand('kill -9 ' + ' '.join(pids))
538    return len(pids)
539
540  def KillAllBlocking(self, process, timeout_sec):
541    """Blocking version of killall, connected via adb.
542
543    This waits until no process matching the corresponding name appears in ps'
544    output anymore.
545
546    Args:
547      process: name of the process to kill off
548      timeout_sec: the timeout in seconds
549
550    Returns:
551      the number of processes killed
552    """
553    processes_killed = self.KillAll(process)
554    if processes_killed:
555      elapsed = 0
556      wait_period = 0.1
557      # Note that this doesn't take into account the time spent in ExtractPid().
558      while self.ExtractPid(process) and elapsed < timeout_sec:
559        time.sleep(wait_period)
560        elapsed += wait_period
561      if elapsed >= timeout_sec:
562        return 0
563    return processes_killed
564
565  def _GetActivityCommand(self, package, activity, wait_for_completion, action,
566                          category, data, extras, trace_file_name, force_stop):
567    """Creates command to start |package|'s activity on the device.
568
569    Args - as for StartActivity
570
571    Returns:
572      the command to run on the target to start the activity
573    """
574    cmd = 'am start -a %s' % action
575    if force_stop:
576      cmd += ' -S'
577    if wait_for_completion:
578      cmd += ' -W'
579    if category:
580      cmd += ' -c %s' % category
581    if package and activity:
582      cmd += ' -n %s/%s' % (package, activity)
583    if data:
584      cmd += ' -d "%s"' % data
585    if extras:
586      for key in extras:
587        value = extras[key]
588        if isinstance(value, str):
589          cmd += ' --es'
590        elif isinstance(value, bool):
591          cmd += ' --ez'
592        elif isinstance(value, int):
593          cmd += ' --ei'
594        else:
595          raise NotImplementedError(
596              'Need to teach StartActivity how to pass %s extras' % type(value))
597        cmd += ' %s %s' % (key, value)
598    if trace_file_name:
599      cmd += ' --start-profiler ' + trace_file_name
600    return cmd
601
602  def StartActivity(self, package, activity, wait_for_completion=False,
603                    action='android.intent.action.VIEW',
604                    category=None, data=None,
605                    extras=None, trace_file_name=None,
606                    force_stop=False):
607    """Starts |package|'s activity on the device.
608
609    Args:
610      package: Name of package to start (e.g. 'com.google.android.apps.chrome').
611      activity: Name of activity (e.g. '.Main' or
612        'com.google.android.apps.chrome.Main').
613      wait_for_completion: wait for the activity to finish launching (-W flag).
614      action: string (e.g. "android.intent.action.MAIN"). Default is VIEW.
615      category: string (e.g. "android.intent.category.HOME")
616      data: Data string to pass to activity (e.g. 'http://www.example.com/').
617      extras: Dict of extras to pass to activity. Values are significant.
618      trace_file_name: If used, turns on and saves the trace to this file name.
619      force_stop: force stop the target app before starting the activity (-S
620        flag).
621    """
622    cmd = self._GetActivityCommand(package, activity, wait_for_completion,
623                                   action, category, data, extras,
624                                   trace_file_name, force_stop)
625    self.RunShellCommand(cmd)
626
627  def StartActivityTimed(self, package, activity, wait_for_completion=False,
628                         action='android.intent.action.VIEW',
629                         category=None, data=None,
630                         extras=None, trace_file_name=None,
631                         force_stop=False):
632    """Starts |package|'s activity on the device, returning the start time
633
634    Args - as for StartActivity
635
636    Returns:
637      a timestamp string for the time at which the activity started
638    """
639    cmd = self._GetActivityCommand(package, activity, wait_for_completion,
640                                   action, category, data, extras,
641                                   trace_file_name, force_stop)
642    self.StartMonitoringLogcat()
643    self.RunShellCommand('log starting activity; ' + cmd)
644    activity_started_re = re.compile('.*starting activity.*')
645    m = self.WaitForLogMatch(activity_started_re, None)
646    assert m
647    start_line = m.group(0)
648    return GetLogTimestamp(start_line, self.GetDeviceYear())
649
650  def GoHome(self):
651    """Tell the device to return to the home screen. Blocks until completion."""
652    self.RunShellCommand('am start -W '
653        '-a android.intent.action.MAIN -c android.intent.category.HOME')
654
655  def CloseApplication(self, package):
656    """Attempt to close down the application, using increasing violence.
657
658    Args:
659      package: Name of the process to kill off, e.g.
660      com.google.android.apps.chrome
661    """
662    self.RunShellCommand('am force-stop ' + package)
663
664  def GetApplicationPath(self, package):
665    """Get the installed apk path on the device for the given package.
666
667    Args:
668      package: Name of the package.
669
670    Returns:
671      Path to the apk on the device if it exists, None otherwise.
672    """
673    pm_path_output  = self.RunShellCommand('pm path ' + package)
674    # The path output contains anything if and only if the package
675    # exists.
676    if pm_path_output:
677      # pm_path_output is of the form: "package:/path/to/foo.apk"
678      return pm_path_output[0].split(':')[1]
679    else:
680      return None
681
682  def ClearApplicationState(self, package):
683    """Closes and clears all state for the given |package|."""
684    # Check that the package exists before clearing it. Necessary because
685    # calling pm clear on a package that doesn't exist may never return.
686    pm_path_output  = self.RunShellCommand('pm path ' + package)
687    # The path output only contains anything if and only if the package exists.
688    if pm_path_output:
689      self.RunShellCommand('pm clear ' + package)
690
691  def SendKeyEvent(self, keycode):
692    """Sends keycode to the device.
693
694    Args:
695      keycode: Numeric keycode to send (see "enum" at top of file).
696    """
697    self.RunShellCommand('input keyevent %d' % keycode)
698
699  def CheckMd5Sum(self, local_path, device_path, ignore_paths=False):
700    """Compares the md5sum of a local path against a device path.
701
702    Args:
703      local_path: Path (file or directory) on the host.
704      device_path: Path on the device.
705      ignore_paths: If False, both the md5sum and the relative paths/names of
706          files must match. If True, only the md5sum must match.
707
708    Returns:
709      True if the md5sums match.
710    """
711    assert os.path.exists(local_path), 'Local path not found %s' % local_path
712
713    if not self._md5sum_build_dir:
714      default_build_type = os.environ.get('BUILD_TYPE', 'Debug')
715      build_dir = '%s/%s/' % (
716          cmd_helper.OutDirectory().get(), default_build_type)
717      md5sum_dist_path = '%s/md5sum_dist' % build_dir
718      if not os.path.exists(md5sum_dist_path):
719        build_dir = '%s/Release/' % cmd_helper.OutDirectory().get()
720        md5sum_dist_path = '%s/md5sum_dist' % build_dir
721        assert os.path.exists(md5sum_dist_path), 'Please build md5sum.'
722      command = 'push %s %s' % (md5sum_dist_path, MD5SUM_DEVICE_FOLDER)
723      assert _HasAdbPushSucceeded(self._adb.SendCommand(command))
724      self._md5sum_build_dir = build_dir
725
726    self._pushed_files.append(device_path)
727    hashes_on_device = _ComputeFileListHash(
728        self.RunShellCommand(MD5SUM_LD_LIBRARY_PATH + ' ' + self._util_wrapper +
729            ' ' + MD5SUM_DEVICE_PATH + ' ' + device_path))
730    assert os.path.exists(local_path), 'Local path not found %s' % local_path
731    md5sum_output = cmd_helper.GetCmdOutput(
732        ['%s/md5sum_bin_host' % self._md5sum_build_dir, local_path])
733    hashes_on_host = _ComputeFileListHash(md5sum_output.splitlines())
734
735    if ignore_paths:
736      hashes_on_device = [h.split()[0] for h in hashes_on_device]
737      hashes_on_host = [h.split()[0] for h in hashes_on_host]
738
739    return hashes_on_device == hashes_on_host
740
741  def PushIfNeeded(self, local_path, device_path):
742    """Pushes |local_path| to |device_path|.
743
744    Works for files and directories. This method skips copying any paths in
745    |test_data_paths| that already exist on the device with the same hash.
746
747    All pushed files can be removed by calling RemovePushedFiles().
748    """
749    if self.CheckMd5Sum(local_path, device_path):
750      return
751
752    # They don't match, so remove everything first and then create it.
753    if os.path.isdir(local_path):
754      self.RunShellCommand('rm -r %s' % device_path, timeout_time=2 * 60)
755      self.RunShellCommand('mkdir -p %s' % device_path)
756
757    # NOTE: We can't use adb_interface.Push() because it hardcodes a timeout of
758    # 60 seconds which isn't sufficient for a lot of users of this method.
759    push_command = 'push %s %s' % (local_path, device_path)
760    self._LogShell(push_command)
761    output = self._adb.SendCommand(push_command, timeout_time=30 * 60)
762    assert _HasAdbPushSucceeded(output)
763
764
765  def GetFileContents(self, filename, log_result=False):
766    """Gets contents from the file specified by |filename|."""
767    return self.RunShellCommand('cat "%s" 2>/dev/null' % filename,
768                                log_result=log_result)
769
770  def SetFileContents(self, filename, contents):
771    """Writes |contents| to the file specified by |filename|."""
772    with tempfile.NamedTemporaryFile() as f:
773      f.write(contents)
774      f.flush()
775      self._adb.Push(f.name, filename)
776
777  _TEMP_FILE_BASE_FMT = 'temp_file_%d'
778  _TEMP_SCRIPT_FILE_BASE_FMT = 'temp_script_file_%d.sh'
779
780  def _GetDeviceTempFileName(self, base_name):
781    i = 0
782    while self.FileExistsOnDevice(
783        self.GetExternalStorage() + '/' + base_name % i):
784      i += 1
785    return self.GetExternalStorage() + '/' + base_name % i
786
787  def CanAccessProtectedFileContents(self):
788    """Returns True if Get/SetProtectedFileContents would work via "su".
789
790    Devices running user builds don't have adb root, but may provide "su" which
791    can be used for accessing protected files.
792    """
793    r = self.RunShellCommand('su -c cat /dev/null')
794    return r == [] or r[0].strip() == ''
795
796  def GetProtectedFileContents(self, filename, log_result=False):
797    """Gets contents from the protected file specified by |filename|.
798
799    This is less efficient than GetFileContents, but will work for protected
800    files and device files.
801    """
802    # Run the script as root
803    return self.RunShellCommand('su -c cat "%s" 2> /dev/null' % filename)
804
805  def SetProtectedFileContents(self, filename, contents):
806    """Writes |contents| to the protected file specified by |filename|.
807
808    This is less efficient than SetFileContents, but will work for protected
809    files and device files.
810    """
811    temp_file = self._GetDeviceTempFileName(AndroidCommands._TEMP_FILE_BASE_FMT)
812    temp_script = self._GetDeviceTempFileName(
813        AndroidCommands._TEMP_SCRIPT_FILE_BASE_FMT)
814
815    # Put the contents in a temporary file
816    self.SetFileContents(temp_file, contents)
817    # Create a script to copy the file contents to its final destination
818    self.SetFileContents(temp_script, 'cat %s > %s' % (temp_file, filename))
819    # Run the script as root
820    self.RunShellCommand('su -c sh %s' % temp_script)
821    # And remove the temporary files
822    self.RunShellCommand('rm ' + temp_file)
823    self.RunShellCommand('rm ' + temp_script)
824
825  def RemovePushedFiles(self):
826    """Removes all files pushed with PushIfNeeded() from the device."""
827    for p in self._pushed_files:
828      self.RunShellCommand('rm -r %s' % p, timeout_time=2 * 60)
829
830  def ListPathContents(self, path):
831    """Lists files in all subdirectories of |path|.
832
833    Args:
834      path: The path to list.
835
836    Returns:
837      A dict of {"name": (size, lastmod), ...}.
838    """
839    # Example output:
840    # /foo/bar:
841    # -rw-r----- 1 user group   102 2011-05-12 12:29:54.131623387 +0100 baz.txt
842    re_file = re.compile('^-(?P<perms>[^\s]+)\s+'
843                         '(?P<user>[^\s]+)\s+'
844                         '(?P<group>[^\s]+)\s+'
845                         '(?P<size>[^\s]+)\s+'
846                         '(?P<date>[^\s]+)\s+'
847                         '(?P<time>[^\s]+)\s+'
848                         '(?P<filename>[^\s]+)$')
849    return _GetFilesFromRecursiveLsOutput(
850        path, self.RunShellCommand('ls -lR %s' % path), re_file,
851        self._device_utc_offset)
852
853  def SetJavaAssertsEnabled(self, enable):
854    """Sets or removes the device java assertions property.
855
856    Args:
857      enable: If True the property will be set.
858
859    Returns:
860      True if the file was modified (reboot is required for it to take effect).
861    """
862    # First ensure the desired property is persisted.
863    temp_props_file = tempfile.NamedTemporaryFile()
864    properties = ''
865    if self._adb.Pull(LOCAL_PROPERTIES_PATH, temp_props_file.name):
866      properties = file(temp_props_file.name).read()
867    re_search = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
868                           r'\s*=\s*all\s*$', re.MULTILINE)
869    if enable != bool(re.search(re_search, properties)):
870      re_replace = re.compile(r'^\s*' + re.escape(JAVA_ASSERT_PROPERTY) +
871                              r'\s*=\s*\w+\s*$', re.MULTILINE)
872      properties = re.sub(re_replace, '', properties)
873      if enable:
874        properties += '\n%s=all\n' % JAVA_ASSERT_PROPERTY
875
876      file(temp_props_file.name, 'w').write(properties)
877      self._adb.Push(temp_props_file.name, LOCAL_PROPERTIES_PATH)
878
879    # Next, check the current runtime value is what we need, and
880    # if not, set it and report that a reboot is required.
881    was_set = 'all' in self.RunShellCommand('getprop ' + JAVA_ASSERT_PROPERTY)
882    if was_set == enable:
883      return False
884
885    self.RunShellCommand('setprop %s "%s"' % (JAVA_ASSERT_PROPERTY,
886                                              enable and 'all' or ''))
887    return True
888
889  def GetBuildId(self):
890    """Returns the build ID of the system (e.g. JRM79C)."""
891    build_id = self.RunShellCommand('getprop ro.build.id')[0]
892    assert build_id
893    return build_id
894
895  def GetBuildType(self):
896    """Returns the build type of the system (e.g. eng)."""
897    build_type = self.RunShellCommand('getprop ro.build.type')[0]
898    assert build_type
899    return build_type
900
901  def GetProductModel(self):
902    """Returns the namve of the product model (e.g. "Galaxy Nexus") """
903    model = self.RunShellCommand('getprop ro.product.model')[0]
904    assert model
905    return model
906
907  def StartMonitoringLogcat(self, clear=True, logfile=None, filters=None):
908    """Starts monitoring the output of logcat, for use with WaitForLogMatch.
909
910    Args:
911      clear: If True the existing logcat output will be cleared, to avoiding
912             matching historical output lurking in the log.
913      filters: A list of logcat filters to be used.
914    """
915    if clear:
916      self.RunShellCommand('logcat -c')
917    args = []
918    if self._adb._target_arg:
919      args += shlex.split(self._adb._target_arg)
920    args += ['logcat', '-v', 'threadtime']
921    if filters:
922      args.extend(filters)
923    else:
924      args.append('*:v')
925
926    if logfile:
927      logfile = NewLineNormalizer(logfile)
928
929    # Spawn logcat and syncronize with it.
930    for _ in range(4):
931      self._logcat = pexpect.spawn(constants.ADB_PATH, args, timeout=10,
932                                   logfile=logfile)
933      self.RunShellCommand('log startup_sync')
934      if self._logcat.expect(['startup_sync', pexpect.EOF,
935                              pexpect.TIMEOUT]) == 0:
936        break
937      self._logcat.close(force=True)
938    else:
939      logging.critical('Error reading from logcat: ' + str(self._logcat.match))
940      sys.exit(1)
941
942  def GetMonitoredLogCat(self):
943    """Returns an "adb logcat" command as created by pexpected.spawn."""
944    if not self._logcat:
945      self.StartMonitoringLogcat(clear=False)
946    return self._logcat
947
948  def WaitForLogMatch(self, success_re, error_re, clear=False, timeout=10):
949    """Blocks until a matching line is logged or a timeout occurs.
950
951    Args:
952      success_re: A compiled re to search each line for.
953      error_re: A compiled re which, if found, terminates the search for
954          |success_re|. If None is given, no error condition will be detected.
955      clear: If True the existing logcat output will be cleared, defaults to
956          false.
957      timeout: Timeout in seconds to wait for a log match.
958
959    Raises:
960      pexpect.TIMEOUT after |timeout| seconds without a match for |success_re|
961      or |error_re|.
962
963    Returns:
964      The re match object if |success_re| is matched first or None if |error_re|
965      is matched first.
966    """
967    logging.info('<<< Waiting for logcat:' + str(success_re.pattern))
968    t0 = time.time()
969    while True:
970      if not self._logcat:
971        self.StartMonitoringLogcat(clear)
972      try:
973        while True:
974          # Note this will block for upto the timeout _per log line_, so we need
975          # to calculate the overall timeout remaining since t0.
976          time_remaining = t0 + timeout - time.time()
977          if time_remaining < 0: raise pexpect.TIMEOUT(self._logcat)
978          self._logcat.expect(PEXPECT_LINE_RE, timeout=time_remaining)
979          line = self._logcat.match.group(1)
980          if error_re:
981            error_match = error_re.search(line)
982            if error_match:
983              return None
984          success_match = success_re.search(line)
985          if success_match:
986            return success_match
987          logging.info('<<< Skipped Logcat Line:' + str(line))
988      except pexpect.TIMEOUT:
989        raise pexpect.TIMEOUT(
990            'Timeout (%ds) exceeded waiting for pattern "%s" (tip: use -vv '
991            'to debug)' %
992            (timeout, success_re.pattern))
993      except pexpect.EOF:
994        # It seems that sometimes logcat can end unexpectedly. This seems
995        # to happen during Chrome startup after a reboot followed by a cache
996        # clean. I don't understand why this happens, but this code deals with
997        # getting EOF in logcat.
998        logging.critical('Found EOF in adb logcat. Restarting...')
999        # Rerun spawn with original arguments. Note that self._logcat.args[0] is
1000        # the path of adb, so we don't want it in the arguments.
1001        self._logcat = pexpect.spawn(constants.ADB_PATH,
1002                                     self._logcat.args[1:],
1003                                     timeout=self._logcat.timeout,
1004                                     logfile=self._logcat.logfile)
1005
1006  def StartRecordingLogcat(self, clear=True, filters=['*:v']):
1007    """Starts recording logcat output to eventually be saved as a string.
1008
1009    This call should come before some series of tests are run, with either
1010    StopRecordingLogcat or SearchLogcatRecord following the tests.
1011
1012    Args:
1013      clear: True if existing log output should be cleared.
1014      filters: A list of logcat filters to be used.
1015    """
1016    if clear:
1017      self._adb.SendCommand('logcat -c')
1018    logcat_command = 'adb %s logcat -v threadtime %s' % (self._adb._target_arg,
1019                                                         ' '.join(filters))
1020    self._logcat_tmpoutfile = tempfile.TemporaryFile(bufsize=0)
1021    self.logcat_process = subprocess.Popen(logcat_command, shell=True,
1022                                           stdout=self._logcat_tmpoutfile)
1023
1024  def StopRecordingLogcat(self):
1025    """Stops an existing logcat recording subprocess and returns output.
1026
1027    Returns:
1028      The logcat output as a string or an empty string if logcat was not
1029      being recorded at the time.
1030    """
1031    if not self.logcat_process:
1032      return ''
1033    # Cannot evaluate directly as 0 is a possible value.
1034    # Better to read the self.logcat_process.stdout before killing it,
1035    # Otherwise the communicate may return incomplete output due to pipe break.
1036    if self.logcat_process.poll() is None:
1037      self.logcat_process.kill()
1038    self.logcat_process.wait()
1039    self.logcat_process = None
1040    self._logcat_tmpoutfile.seek(0)
1041    output = self._logcat_tmpoutfile.read()
1042    self._logcat_tmpoutfile.close()
1043    return output
1044
1045  def SearchLogcatRecord(self, record, message, thread_id=None, proc_id=None,
1046                         log_level=None, component=None):
1047    """Searches the specified logcat output and returns results.
1048
1049    This method searches through the logcat output specified by record for a
1050    certain message, narrowing results by matching them against any other
1051    specified criteria.  It returns all matching lines as described below.
1052
1053    Args:
1054      record: A string generated by Start/StopRecordingLogcat to search.
1055      message: An output string to search for.
1056      thread_id: The thread id that is the origin of the message.
1057      proc_id: The process that is the origin of the message.
1058      log_level: The log level of the message.
1059      component: The name of the component that would create the message.
1060
1061    Returns:
1062      A list of dictionaries represeting matching entries, each containing keys
1063      thread_id, proc_id, log_level, component, and message.
1064    """
1065    if thread_id:
1066      thread_id = str(thread_id)
1067    if proc_id:
1068      proc_id = str(proc_id)
1069    results = []
1070    reg = re.compile('(\d+)\s+(\d+)\s+([A-Z])\s+([A-Za-z]+)\s*:(.*)$',
1071                     re.MULTILINE)
1072    log_list = reg.findall(record)
1073    for (tid, pid, log_lev, comp, msg) in log_list:
1074      if ((not thread_id or thread_id == tid) and
1075          (not proc_id or proc_id == pid) and
1076          (not log_level or log_level == log_lev) and
1077          (not component or component == comp) and msg.find(message) > -1):
1078        match = dict({'thread_id': tid, 'proc_id': pid,
1079                      'log_level': log_lev, 'component': comp,
1080                      'message': msg})
1081        results.append(match)
1082    return results
1083
1084  def ExtractPid(self, process_name):
1085    """Extracts Process Ids for a given process name from Android Shell.
1086
1087    Args:
1088      process_name: name of the process on the device.
1089
1090    Returns:
1091      List of all the process ids (as strings) that match the given name.
1092      If the name of a process exactly matches the given name, the pid of
1093      that process will be inserted to the front of the pid list.
1094    """
1095    pids = []
1096    for line in self.RunShellCommand('ps', log_result=False):
1097      data = line.split()
1098      try:
1099        if process_name in data[-1]:  # name is in the last column
1100          if process_name == data[-1]:
1101            pids.insert(0, data[1])  # PID is in the second column
1102          else:
1103            pids.append(data[1])
1104      except IndexError:
1105        pass
1106    return pids
1107
1108  def GetIoStats(self):
1109    """Gets cumulative disk IO stats since boot (for all processes).
1110
1111    Returns:
1112      Dict of {num_reads, num_writes, read_ms, write_ms} or None if there
1113      was an error.
1114    """
1115    for line in self.GetFileContents('/proc/diskstats', log_result=False):
1116      stats = io_stats_parser.ParseIoStatsLine(line)
1117      if stats.device == 'mmcblk0':
1118        return {
1119            'num_reads': stats.num_reads_issued,
1120            'num_writes': stats.num_writes_completed,
1121            'read_ms': stats.ms_spent_reading,
1122            'write_ms': stats.ms_spent_writing,
1123        }
1124    logging.warning('Could not find disk IO stats.')
1125    return None
1126
1127  def GetMemoryUsageForPid(self, pid):
1128    """Returns the memory usage for given pid.
1129
1130    Args:
1131      pid: The pid number of the specific process running on device.
1132
1133    Returns:
1134      A tuple containg:
1135      [0]: Dict of {metric:usage_kb}, for the process which has specified pid.
1136      The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1137      Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
1138      KernelPageSize, MMUPageSize, Nvidia (tablet only).
1139      [1]: Detailed /proc/[PID]/smaps information.
1140    """
1141    usage_dict = collections.defaultdict(int)
1142    smaps = collections.defaultdict(dict)
1143    current_smap = ''
1144    for line in self.GetProtectedFileContents('/proc/%s/smaps' % pid,
1145                                              log_result=False):
1146      items = line.split()
1147      # See man 5 proc for more details. The format is:
1148      # address perms offset dev inode pathname
1149      if len(items) > 5:
1150        current_smap = ' '.join(items[5:])
1151      elif len(items) > 3:
1152        current_smap = ' '.join(items[3:])
1153      match = re.match(MEMORY_INFO_RE, line)
1154      if match:
1155        key = match.group('key')
1156        usage_kb = int(match.group('usage_kb'))
1157        usage_dict[key] += usage_kb
1158        if key not in smaps[current_smap]:
1159          smaps[current_smap][key] = 0
1160        smaps[current_smap][key] += usage_kb
1161    if not usage_dict or not any(usage_dict.values()):
1162      # Presumably the process died between ps and calling this method.
1163      logging.warning('Could not find memory usage for pid ' + str(pid))
1164
1165    for line in self.GetProtectedFileContents('/d/nvmap/generic-0/clients',
1166                                              log_result=False):
1167      match = re.match(NVIDIA_MEMORY_INFO_RE, line)
1168      if match and match.group('pid') == pid:
1169        usage_bytes = int(match.group('usage_bytes'))
1170        usage_dict['Nvidia'] = int(round(usage_bytes / 1000.0))  # kB
1171        break
1172
1173    return (usage_dict, smaps)
1174
1175  def GetMemoryUsageForPackage(self, package):
1176    """Returns the memory usage for all processes whose name contains |pacakge|.
1177
1178    Args:
1179      package: A string holding process name to lookup pid list for.
1180
1181    Returns:
1182      A tuple containg:
1183      [0]: Dict of {metric:usage_kb}, summed over all pids associated with
1184           |name|.
1185      The metric keys which may be included are: Size, Rss, Pss, Shared_Clean,
1186      Shared_Dirty, Private_Clean, Private_Dirty, Referenced, Swap,
1187      KernelPageSize, MMUPageSize, Nvidia (tablet only).
1188      [1]: a list with detailed /proc/[PID]/smaps information.
1189    """
1190    usage_dict = collections.defaultdict(int)
1191    pid_list = self.ExtractPid(package)
1192    smaps = collections.defaultdict(dict)
1193
1194    for pid in pid_list:
1195      usage_dict_per_pid, smaps_per_pid = self.GetMemoryUsageForPid(pid)
1196      smaps[pid] = smaps_per_pid
1197      for (key, value) in usage_dict_per_pid.items():
1198        usage_dict[key] += value
1199
1200    return usage_dict, smaps
1201
1202  def ProcessesUsingDevicePort(self, device_port):
1203    """Lists processes using the specified device port on loopback interface.
1204
1205    Args:
1206      device_port: Port on device we want to check.
1207
1208    Returns:
1209      A list of (pid, process_name) tuples using the specified port.
1210    """
1211    tcp_results = self.RunShellCommand('cat /proc/net/tcp', log_result=False)
1212    tcp_address = '0100007F:%04X' % device_port
1213    pids = []
1214    for single_connect in tcp_results:
1215      connect_results = single_connect.split()
1216      # Column 1 is the TCP port, and Column 9 is the inode of the socket
1217      if connect_results[1] == tcp_address:
1218        socket_inode = connect_results[9]
1219        socket_name = 'socket:[%s]' % socket_inode
1220        lsof_results = self.RunShellCommand('lsof', log_result=False)
1221        for single_process in lsof_results:
1222          process_results = single_process.split()
1223          # Ignore the line if it has less than nine columns in it, which may
1224          # be the case when a process stops while lsof is executing.
1225          if len(process_results) <= 8:
1226            continue
1227          # Column 0 is the executable name
1228          # Column 1 is the pid
1229          # Column 8 is the Inode in use
1230          if process_results[8] == socket_name:
1231            pids.append((int(process_results[1]), process_results[0]))
1232        break
1233    logging.info('PidsUsingDevicePort: %s', pids)
1234    return pids
1235
1236  def FileExistsOnDevice(self, file_name):
1237    """Checks whether the given file exists on the device.
1238
1239    Args:
1240      file_name: Full path of file to check.
1241
1242    Returns:
1243      True if the file exists, False otherwise.
1244    """
1245    assert '"' not in file_name, 'file_name cannot contain double quotes'
1246    try:
1247      status = self._adb.SendShellCommand(
1248          '\'test -e "%s"; echo $?\'' % (file_name))
1249      if 'test: not found' not in status:
1250        return int(status) == 0
1251
1252      status = self._adb.SendShellCommand(
1253          '\'ls "%s" >/dev/null 2>&1; echo $?\'' % (file_name))
1254      return int(status) == 0
1255    except ValueError:
1256      if IsDeviceAttached(self._device):
1257        raise errors.DeviceUnresponsiveError('Device may be offline.')
1258
1259      return False
1260
1261  def TakeScreenshot(self, host_file):
1262    """Saves a screenshot image to |host_file| on the host.
1263
1264    Args:
1265      host_file: Absolute path to the image file to store on the host.
1266    """
1267    host_dir = os.path.dirname(host_file)
1268    if not os.path.exists(host_dir):
1269      os.makedirs(host_dir)
1270    device_file = '%s/screenshot.png' % self.GetExternalStorage()
1271    self.RunShellCommand('/system/bin/screencap -p %s' % device_file)
1272    assert self._adb.Pull(device_file, host_file)
1273    assert os.path.exists(host_file)
1274
1275  def SetUtilWrapper(self, util_wrapper):
1276    """Sets a wrapper prefix to be used when running a locally-built
1277    binary on the device (ex.: md5sum_bin).
1278    """
1279    self._util_wrapper = util_wrapper
1280
1281  def RunInstrumentationTest(self, test, test_package, instr_args, timeout):
1282    """Runs a single instrumentation test.
1283
1284    Args:
1285      test: Test class/method.
1286      test_package: Package name of test apk.
1287      instr_args: Extra key/value to pass to am instrument.
1288      timeout: Timeout time in seconds.
1289
1290    Returns:
1291      An instance of am_instrument_parser.TestResult object.
1292    """
1293    instrumentation_path = ('%s/android.test.InstrumentationTestRunner' %
1294                            test_package)
1295    args_with_filter = dict(instr_args)
1296    args_with_filter['class'] = test
1297    logging.info(args_with_filter)
1298    (raw_results, _) = self._adb.StartInstrumentation(
1299        instrumentation_path=instrumentation_path,
1300        instrumentation_args=args_with_filter,
1301        timeout_time=timeout)
1302    assert len(raw_results) == 1
1303    return raw_results[0]
1304
1305  def RunUIAutomatorTest(self, test, test_package, timeout):
1306    """Runs a single uiautomator test.
1307
1308    Args:
1309      test: Test class/method.
1310      test_package: Name of the test jar.
1311      timeout: Timeout time in seconds.
1312
1313    Returns:
1314      An instance of am_instrument_parser.TestResult object.
1315    """
1316    cmd = 'uiautomator runtest %s -e class %s' % (test_package, test)
1317    self._LogShell(cmd)
1318    output = self._adb.SendShellCommand(cmd, timeout_time=timeout)
1319    # uiautomator doesn't fully conform to the instrumenation test runner
1320    # convention and doesn't terminate with INSTRUMENTATION_CODE.
1321    # Just assume the first result is valid.
1322    (test_results, _) = am_instrument_parser.ParseAmInstrumentOutput(output)
1323    if not test_results:
1324      raise errors.InstrumentationError(
1325          'no test results... device setup correctly?')
1326    return test_results[0]
1327
1328
1329class NewLineNormalizer(object):
1330  """A file-like object to normalize EOLs to '\n'.
1331
1332  Pexpect runs adb within a pseudo-tty device (see
1333  http://www.noah.org/wiki/pexpect), so any '\n' printed by adb is written
1334  as '\r\n' to the logfile. Since adb already uses '\r\n' to terminate
1335  lines, the log ends up having '\r\r\n' at the end of each line. This
1336  filter replaces the above with a single '\n' in the data stream.
1337  """
1338  def __init__(self, output):
1339    self._output = output
1340
1341  def write(self, data):
1342    data = data.replace('\r\r\n', '\n')
1343    self._output.write(data)
1344
1345  def flush(self):
1346    self._output.flush()
1347