Update from https://crrev.com/312600

TBR=jamesr@chromium.org

Review URL: https://codereview.chromium.org/863253002
diff --git a/build/android/pylib/base/test_run_factory.py b/build/android/pylib/base/test_run_factory.py
index 3f00caa..8c71ebb 100644
--- a/build/android/pylib/base/test_run_factory.py
+++ b/build/android/pylib/base/test_run_factory.py
@@ -9,7 +9,8 @@
 from pylib.local.device import local_device_instrumentation_test_run
 from pylib.remote.device import remote_device_environment
 from pylib.remote.device import remote_device_gtest_run
-from pylib.remote.device import remote_device_uirobot_run
+from pylib.remote.device import remote_device_instrumentation_test_run
+from pylib.remote.device import remote_device_uirobot_test_run
 from pylib.uirobot import uirobot_test_instance
 
 
@@ -24,10 +25,14 @@
 
   if isinstance(env, remote_device_environment.RemoteDeviceEnvironment):
     if isinstance(test_instance, gtest_test_instance.GtestTestInstance):
-      return remote_device_gtest_run.RemoteDeviceGtestRun(env, test_instance)
-    # TODO(rnephew): Add remote_device instrumentation test runs.
+      return remote_device_gtest_run.RemoteDeviceGtestTestRun(
+          env, test_instance)
+    if isinstance(test_instance,
+                  instrumentation_test_instance.InstrumentationTestInstance):
+      return (remote_device_instrumentation_test_run
+              .RemoteDeviceInstrumentationTestRun(env, test_instance))
     if isinstance(test_instance, uirobot_test_instance.UirobotTestInstance):
-      return remote_device_uirobot_run.RemoteDeviceUirobotRun(
+      return remote_device_uirobot_test_run.RemoteDeviceUirobotTestRun(
           env, test_instance)
 
 
diff --git a/build/android/pylib/constants.py b/build/android/pylib/constants.py
index 50dc075..fb574e6 100644
--- a/build/android/pylib/constants.py
+++ b/build/android/pylib/constants.py
@@ -88,7 +88,7 @@
     'android_webview_shell': PackageInfo(
         'org.chromium.android_webview.shell',
         'org.chromium.android_webview.shell.AwShellActivity',
-        None,
+        '/data/local/tmp/android-webview-command-line',
         None,
         'org.chromium.android_webview.test'),
     'gtest': PackageInfo(
diff --git a/build/android/pylib/device/device_utils.py b/build/android/pylib/device/device_utils.py
index fe9d2c6..a4ddef1 100644
--- a/build/android/pylib/device/device_utils.py
+++ b/build/android/pylib/device/device_utils.py
@@ -634,9 +634,12 @@
       CommandTimeoutError on timeout.
       DeviceUnreachableError on missing device.
     """
-    # Check that the package exists before clearing it. Necessary because
-    # calling pm clear on a package that doesn't exist may never return.
-    if self.GetApplicationPath(package):
+    # Check that the package exists before clearing it for android builds below
+    # JB MR2. Necessary because calling pm clear on a package that doesn't exist
+    # may never return.
+    if ((self.build_version_sdk >=
+         constants.ANDROID_SDK_VERSION_CODES.JELLY_BEAN_MR2)
+        or self.GetApplicationPath(package)):
       self.RunShellCommand(['pm', 'clear', package], check_return=True)
 
   @decorators.WithTimeoutAndRetriesFromInstance()
diff --git a/build/android/pylib/device/device_utils_test.py b/build/android/pylib/device/device_utils_test.py
index 58dd56a..72ab99c 100755
--- a/build/android/pylib/device/device_utils_test.py
+++ b/build/android/pylib/device/device_utils_test.py
@@ -930,19 +930,35 @@
 class DeviceUtilsClearApplicationStateTest(DeviceUtilsNewImplTest):
 
   def testClearApplicationState_packageDoesntExist(self):
-    with self.assertCall(
-        self.call.device.GetApplicationPath('this.package.does.not.exist'),
-        None):
+    with self.assertCalls(
+        (self.call.adb.Shell('getprop ro.build.version.sdk'), '17\n'),
+        (self.call.device.GetApplicationPath('this.package.does.not.exist'),
+         None)):
+      self.device.ClearApplicationState('this.package.does.not.exist')
+
+  def testClearApplicationState_packageDoesntExistOnAndroidJBMR2OrAbove(self):
+    with self.assertCalls(
+        (self.call.adb.Shell('getprop ro.build.version.sdk'), '18\n'),
+        (self.call.adb.Shell('pm clear this.package.does.not.exist'),
+         'Failed\r\n')):
       self.device.ClearApplicationState('this.package.does.not.exist')
 
   def testClearApplicationState_packageExists(self):
     with self.assertCalls(
+        (self.call.adb.Shell('getprop ro.build.version.sdk'), '17\n'),
         (self.call.device.GetApplicationPath('this.package.exists'),
          '/data/app/this.package.exists.apk'),
         (self.call.adb.Shell('pm clear this.package.exists'),
          'Success\r\n')):
       self.device.ClearApplicationState('this.package.exists')
 
+  def testClearApplicationState_packageExistsOnAndroidJBMR2OrAbove(self):
+    with self.assertCalls(
+        (self.call.adb.Shell('getprop ro.build.version.sdk'), '18\n'),
+        (self.call.adb.Shell('pm clear this.package.exists'),
+         'Success\r\n')):
+      self.device.ClearApplicationState('this.package.exists')
+
 
 class DeviceUtilsSendKeyEventTest(DeviceUtilsNewImplTest):
 
diff --git a/build/android/pylib/gtest/test_package_apk.py b/build/android/pylib/gtest/test_package_apk.py
index b4c67e6..cdb6daf 100644
--- a/build/android/pylib/gtest/test_package_apk.py
+++ b/build/android/pylib/gtest/test_package_apk.py
@@ -55,11 +55,11 @@
     device.RunShellCommand('rm -f ' + self._GetFifo())
 
   def _WatchFifo(self, device, timeout, logfile=None):
-    for i in range(10):
+    for i in range(100):
       if device.FileExists(self._GetFifo()):
-        logging.info('Fifo created.')
+        logging.info('Fifo created. Slept for %f secs' % (i * 0.5))
         break
-      time.sleep(i)
+      time.sleep(0.5)
     else:
       raise device_errors.DeviceUnreachableError(
           'Unable to find fifo on device %s ' % self._GetFifo())
@@ -67,14 +67,14 @@
     args += ['shell', 'cat', self._GetFifo()]
     return pexpect.spawn('adb', args, timeout=timeout, logfile=logfile)
 
-  def _StartActivity(self, device):
+  def _StartActivity(self, device, force_stop=True):
     device.StartActivity(
         intent.Intent(package=self._package_info.package,
                       activity=self._package_info.activity,
                       action='android.intent.action.MAIN'),
         # No wait since the runner waits for FIFO creation anyway.
         blocking=False,
-        force_stop=True)
+        force_stop=force_stop)
 
   #override
   def ClearApplicationState(self, device):
@@ -119,7 +119,10 @@
     try:
       self.tool.SetupEnvironment()
       self._ClearFifo(device)
-      self._StartActivity(device)
+      # Doesn't need to stop an Activity because ClearApplicationState() is
+      # always called before this call and so it is already stopped at this
+      # point.
+      self._StartActivity(device, force_stop=False)
     finally:
       self.tool.CleanUpEnvironment()
     logfile = android_commands.NewLineNormalizer(sys.stdout)
diff --git a/build/android/pylib/gtest/test_runner.py b/build/android/pylib/gtest/test_runner.py
index fa38c4f..4bb9737 100644
--- a/build/android/pylib/gtest/test_runner.py
+++ b/build/android/pylib/gtest/test_runner.py
@@ -14,6 +14,18 @@
 from pylib.local import local_test_server_spawner
 from pylib.perf import perf_control
 
+# Test case statuses.
+RE_RUN = re.compile('\\[ RUN      \\] ?(.*)\r\n')
+RE_FAIL = re.compile('\\[  FAILED  \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n')
+RE_OK = re.compile('\\[       OK \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n')
+
+# Test run statuses.
+RE_PASSED = re.compile('\\[  PASSED  \\] ?(.*)\r\n')
+RE_RUNNER_FAIL = re.compile('\\[ RUNNER_FAILED \\] ?(.*)\r\n')
+# Signal handlers are installed before starting tests
+# to output the CRASHED marker when a crash happens.
+RE_CRASH = re.compile('\\[ CRASHED      \\](.*)\r\n')
+
 
 def _TestSuiteRequiresMockTestServer(suite_name):
   """Returns True if the test suite requires mock test server."""
@@ -77,45 +89,33 @@
     """
     results = base_test_result.TestRunResults()
 
-    # Test case statuses.
-    re_run = re.compile('\\[ RUN      \\] ?(.*)\r\n')
-    re_fail = re.compile('\\[  FAILED  \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n')
-    re_ok = re.compile('\\[       OK \\] ?(.*?)( \\((\\d+) ms\\))?\r\r\n')
-
-    # Test run statuses.
-    re_passed = re.compile('\\[  PASSED  \\] ?(.*)\r\n')
-    re_runner_fail = re.compile('\\[ RUNNER_FAILED \\] ?(.*)\r\n')
-    # Signal handlers are installed before starting tests
-    # to output the CRASHED marker when a crash happens.
-    re_crash = re.compile('\\[ CRASHED      \\](.*)\r\n')
-
     log = ''
     try:
       while True:
         full_test_name = None
 
-        found = p.expect([re_run, re_passed, re_runner_fail],
+        found = p.expect([RE_RUN, RE_PASSED, RE_RUNNER_FAIL],
                          timeout=self._timeout)
-        if found == 1:  # re_passed
+        if found == 1:  # RE_PASSED
           break
-        elif found == 2:  # re_runner_fail
+        elif found == 2:  # RE_RUNNER_FAIL
           break
-        else:  # re_run
+        else:  # RE_RUN
           full_test_name = p.match.group(1).replace('\r', '')
-          found = p.expect([re_ok, re_fail, re_crash], timeout=self._timeout)
+          found = p.expect([RE_OK, RE_FAIL, RE_CRASH], timeout=self._timeout)
           log = p.before.replace('\r', '')
-          if found == 0:  # re_ok
+          if found == 0:  # RE_OK
             if full_test_name == p.match.group(1).replace('\r', ''):
               duration_ms = int(p.match.group(3)) if p.match.group(3) else 0
               results.AddResult(base_test_result.BaseTestResult(
                   full_test_name, base_test_result.ResultType.PASS,
                   duration=duration_ms, log=log))
-          elif found == 2:  # re_crash
+          elif found == 2:  # RE_CRASH
             results.AddResult(base_test_result.BaseTestResult(
                 full_test_name, base_test_result.ResultType.CRASH,
                 log=log))
             break
-          else:  # re_fail
+          else:  # RE_FAIL
             duration_ms = int(p.match.group(3)) if p.match.group(3) else 0
             results.AddResult(base_test_result.BaseTestResult(
                 full_test_name, base_test_result.ResultType.FAIL,
diff --git a/build/android/pylib/instrumentation/instrumentation_test_instance.py b/build/android/pylib/instrumentation/instrumentation_test_instance.py
index a87f81a..598967b 100644
--- a/build/android/pylib/instrumentation/instrumentation_test_instance.py
+++ b/build/android/pylib/instrumentation/instrumentation_test_instance.py
@@ -5,6 +5,7 @@
 import logging
 import os
 import pickle
+import re
 import sys
 
 from pylib import cmd_helper
@@ -24,6 +25,7 @@
 _DEFAULT_ANNOTATIONS = [
     'Smoke', 'SmallTest', 'MediumTest', 'LargeTest',
     'EnormousTest', 'IntegrationTest']
+_NATIVE_CRASH_RE = re.compile('native crash', re.IGNORECASE)
 _PICKLE_FORMAT_VERSION = 10
 
 
@@ -270,6 +272,10 @@
         self._flags.extend([flag for flag in stripped_lines if flag])
 
   @property
+  def suite(self):
+    return 'instrumentation'
+
+  @property
   def apk_under_test(self):
     return self._apk_under_test
 
@@ -459,6 +465,33 @@
     return inflated_tests
 
   @staticmethod
+  def GenerateMultiTestResult(errors, statuses):
+    INSTR_STATUS_CODE_START = 1
+    results = []
+    skip_counter = 1
+    for status_code, bundle in statuses:
+      if status_code != INSTR_STATUS_CODE_START:
+        # TODO(rnephew): Make skipped tests still output test name. This is only
+        # there to give skipped tests a unique name so they are counted
+        if 'test_skipped' in bundle:
+          test_name = str(skip_counter)
+          skip_counter += 1
+        else:
+          test_name = '%s#%s' % (
+              ''.join(bundle.get('class', [''])),
+              ''.join(bundle.get('test', [''])))
+
+        results.append(
+            GenerateTestResult(test_name, [(status_code, bundle)], 0, 0))
+    for error in errors:
+      if _NATIVE_CRASH_RE.search(error):
+        results.append(
+            base_test_result.BaseTestResult(
+            'Crash detected', base_test_result.ResultType.CRASH))
+
+    return results
+
+  @staticmethod
   def ParseAmInstrumentRawOutput(raw_output):
     return ParseAmInstrumentRawOutput(raw_output)
 
diff --git a/build/android/pylib/remote/device/remote_device_gtest_run.py b/build/android/pylib/remote/device/remote_device_gtest_run.py
index e5f6990..76d1d45 100644
--- a/build/android/pylib/remote/device/remote_device_gtest_run.py
+++ b/build/android/pylib/remote/device/remote_device_gtest_run.py
@@ -24,7 +24,7 @@
         'OnlyOutputFailures')
 
 
-class RemoteDeviceGtestRun(remote_device_test_run.RemoteDeviceTestRun):
+class RemoteDeviceGtestTestRun(remote_device_test_run.RemoteDeviceTestRun):
   """Run gtests and uirobot tests on a remote device."""
 
   DEFAULT_RUNNER_PACKAGE = (
diff --git a/build/android/pylib/remote/device/remote_device_instrumentation_test_run.py b/build/android/pylib/remote/device/remote_device_instrumentation_test_run.py
new file mode 100644
index 0000000..5138d46
--- /dev/null
+++ b/build/android/pylib/remote/device/remote_device_instrumentation_test_run.py
@@ -0,0 +1,55 @@
+# Copyright 2015 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Run specific test on specific environment."""
+
+import logging
+import os
+import tempfile
+
+from pylib.base import base_test_result
+from pylib.remote.device import remote_device_test_run
+from pylib.utils import apk_helper
+
+
+class RemoteDeviceInstrumentationTestRun(
+    remote_device_test_run.RemoteDeviceTestRun):
+  """Run instrumentation tests on a remote device."""
+
+  #override
+  def TestPackage(self):
+    return self._test_instance.test_package
+
+  #override
+  def _TriggerSetUp(self):
+    """Set up the triggering of a test run."""
+    logging.info('Triggering test run.')
+    self._AmInstrumentTestSetup(
+        self._test_instance._apk_under_test, self._test_instance.test_apk,
+        self._test_instance.test_runner, environment_variables={})
+
+  #override
+  def _ParseTestResults(self):
+    logging.info('Parsing results from stdout.')
+    r = base_test_result.TestRunResults()
+
+    if self._results['results']['exception']:
+      r.AddResult(base_test_result.BaseTestResult(
+          self._results['results']['exception'],
+          base_test_result.ResultType.FAIL))
+      return r
+
+    _, errors, parsed_output = self._test_instance.ParseAmInstrumentRawOutput(
+        self._results['results']['output'].splitlines())
+    logging.debug(errors)
+    result = self._test_instance.GenerateMultiTestResult(errors, parsed_output)
+
+    if isinstance(result, base_test_result.BaseTestResult):
+      r.AddResult(result)
+    elif isinstance(result, list):
+      r.AddResults(result)
+    else:
+      raise Exception('Unexpected result type: %s' % type(result).__name__)
+
+    return r
diff --git a/build/android/pylib/remote/device/remote_device_test_run.py b/build/android/pylib/remote/device/remote_device_test_run.py
index 86ee587..c4f75b0 100644
--- a/build/android/pylib/remote/device/remote_device_test_run.py
+++ b/build/android/pylib/remote/device/remote_device_test_run.py
@@ -219,7 +219,7 @@
         config['sdcard_files'] = ','.join(sdcard_files)
         config['host_test'] = host_test
         self._test_id = self._UploadTestToDevice(
-            'robotium', test_with_deps.name)
+            'robotium', test_with_deps.name, app_id=self._app_id)
     else:
       self._test_id = self._UploadTestToDevice('robotium', test_path)
 
@@ -238,7 +238,7 @@
           upload_results, 'Unable to upload %s.' % app_path)
       return upload_results.json()['response']['app_id']
 
-  def _UploadTestToDevice(self, test_type, test_path):
+  def _UploadTestToDevice(self, test_type, test_path, app_id=None):
     """Upload test to device
     Args:
       test_type: Type of test that is being uploaded. Ex. uirobot, gtest..
@@ -248,7 +248,7 @@
       with appurify_sanitized.SanitizeLogging(self._env.verbose_count,
                                               logging.WARNING):
         upload_results = appurify_sanitized.api.tests_upload(
-            self._env.token, test_src, 'raw', test_type)
+            self._env.token, test_src, 'raw', test_type, app_id=app_id)
       remote_device_helper.TestHttpResponse(upload_results,
           'Unable to upload %s.' % test_path)
       return upload_results.json()['response']['test_id']
diff --git a/build/android/pylib/remote/device/remote_device_uirobot_run.py b/build/android/pylib/remote/device/remote_device_uirobot_test_run.py
similarity index 91%
rename from build/android/pylib/remote/device/remote_device_uirobot_run.py
rename to build/android/pylib/remote/device/remote_device_uirobot_test_run.py
index 3aee343..7eedaa5 100644
--- a/build/android/pylib/remote/device/remote_device_uirobot_run.py
+++ b/build/android/pylib/remote/device/remote_device_uirobot_test_run.py
@@ -15,7 +15,7 @@
 from pylib.remote.device import remote_device_helper
 
 
-class RemoteDeviceUirobotRun(remote_device_test_run.RemoteDeviceTestRun):
+class RemoteDeviceUirobotTestRun(remote_device_test_run.RemoteDeviceTestRun):
   """Run uirobot tests on a remote device."""
 
   DEFAULT_RUNNER_TYPE = 'android_robot'
@@ -27,7 +27,7 @@
       env: Environment the tests will run in.
       test_instance: The test that will be run.
     """
-    super(RemoteDeviceUirobotRun, self).__init__(env, test_instance)
+    super(RemoteDeviceUirobotTestRun, self).__init__(env, test_instance)
 
   #override
   def TestPackage(self):
diff --git a/build/android/pylib/uirobot/uirobot_test_instance.py b/build/android/pylib/uirobot/uirobot_test_instance.py
index a531b41..edd3200 100644
--- a/build/android/pylib/uirobot/uirobot_test_instance.py
+++ b/build/android/pylib/uirobot/uirobot_test_instance.py
@@ -18,7 +18,7 @@
     """
     super(UirobotTestInstance, self).__init__()
     self._apk_under_test = os.path.join(
-        constants.GetOutDirectory(), args.apk_under_test)
+        constants.GetOutDirectory(), args.app_under_test)
     self._minutes = args.minutes
     self._package_name = apk_helper.GetPackageName(self._apk_under_test)
     self._suite = 'Android Uirobot'
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index e92f851..4164bd8 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -142,7 +142,7 @@
                      help='Type of test to run as.')
   group.add_argument('--runner-package', default='',
                      help='Package name of test.')
-  group.add_argument('--apk-under-test', default='apks/Chrome.apk',
+  group.add_argument('--app-under-test', default='',
                      help='APK to run tests on.')
 
   api_secret_group = group.add_mutually_exclusive_group()
@@ -316,6 +316,7 @@
 
   AddCommonOptions(parser)
   AddDeviceOptions(parser)
+  AddRemoteDeviceOptions(parser)
 
 
 def ProcessInstrumentationOptions(args):