Add option to specify ADB binary in test runner.

BUG=430957

Review URL: https://codereview.chromium.org/711113002

Cr-Commit-Position: refs/heads/master@{#304913}
diff --git a/build/android/pylib/android_commands.py b/build/android/pylib/android_commands.py
index 971fc4ee..8d7866dc 100644
--- a/build/android/pylib/android_commands.py
+++ b/build/android/pylib/android_commands.py
@@ -314,11 +314,7 @@
       device: If given, adb commands are only send to the device of this ID.
           Otherwise commands are sent to all attached devices.
     """
-    adb_dir = os.path.dirname(constants.GetAdbPath())
-    if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
-      # Required by third_party/android_testrunner to call directly 'adb'.
-      os.environ['PATH'] += os.pathsep + adb_dir
-    self._adb = adb_interface.AdbInterface()
+    self._adb = adb_interface.AdbInterface(constants.GetAdbPath())
     if device:
       self._adb.SetTargetSerial(device)
     self._device = device
diff --git a/build/android/pylib/constants.py b/build/android/pylib/constants.py
index 29da6012..da28eeb 100644
--- a/build/android/pylib/constants.py
+++ b/build/android/pylib/constants.py
@@ -246,8 +246,21 @@
   return Wrapper
 
 
-@_Memoize
+def SetAdbPath(adb_path):
+  os.environ['ADB_PATH'] = adb_path
+
+
 def GetAdbPath():
+  # Check if a custom adb path as been set. If not, try to find adb
+  # on the system.
+  if os.environ.get('ADB_PATH'):
+    return os.environ.get('ADB_PATH')
+  else:
+    return _FindAdbPath()
+
+
+@_Memoize
+def _FindAdbPath():
   if os.environ.get('ANDROID_SDK_ROOT'):
     return 'adb'
   # If envsetup.sh hasn't been sourced and there's no adb in the path,
@@ -260,7 +273,6 @@
     logging.debug('No adb found in $PATH, fallback to checked in binary.')
     return os.path.join(ANDROID_SDK_ROOT, 'platform-tools', 'adb')
 
-
 # Exit codes
 ERROR_EXIT_CODE = 1
 WARNING_EXIT_CODE = 88
diff --git a/build/android/pylib/device/adb_wrapper.py b/build/android/pylib/device/adb_wrapper.py
index 23f77c3e..c7ea310 100644
--- a/build/android/pylib/device/adb_wrapper.py
+++ b/build/android/pylib/device/adb_wrapper.py
@@ -12,6 +12,7 @@
 import os
 
 from pylib import cmd_helper
+from pylib import constants
 from pylib.device import decorators
 from pylib.device import device_errors
 from pylib.utils import timeout_retry
@@ -49,7 +50,7 @@
   @classmethod
   @decorators.WithTimeoutAndRetries
   def _RunAdbCmd(cls, arg_list, timeout=None, retries=None, check_error=True):
-    cmd = ['adb'] + arg_list
+    cmd = [constants.GetAdbPath()] + arg_list
     exit_code, output = cmd_helper.GetCmdStatusAndOutputWithTimeout(
       cmd, timeout_retry.CurrentTimeoutThread().GetRemainingTime())
     if exit_code != 0:
diff --git a/build/android/pylib/device/device_utils_test.py b/build/android/pylib/device/device_utils_test.py
index 1fb973bd..2ba3f727 100755
--- a/build/android/pylib/device/device_utils_test.py
+++ b/build/android/pylib/device/device_utils_test.py
@@ -215,9 +215,14 @@
     return type(self).AndroidCommandsCalls(self, cmd_ret, comp)
 
   def setUp(self):
+    self._get_adb_path_patch = mock.patch('pylib.constants.GetAdbPath',
+                                          mock.Mock(return_value='adb'))
+    self._get_adb_path_patch.start()
     self.device = device_utils.DeviceUtils(
         '0123456789abcdef', default_timeout=1, default_retries=0)
 
+  def tearDown(self):
+    self._get_adb_path_patch.stop()
 
 class DeviceUtilsNewImplTest(mock_calls.TestCase):
 
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index bce79d5..b483d2b 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -91,6 +91,9 @@
   group.add_option('-e', '--environment', default='local',
                    help=('Test environment to run in. Must be one of: %s' %
                          ', '.join(constants.VALID_ENVIRONMENTS)))
+  group.add_option('--adb-path',
+                   help=('Specify the absolute path of the adb binary that '
+                         'should be used.'))
   option_parser.add_option_group(group)
 
 
@@ -102,6 +105,12 @@
     constants.SetBuildDirectory(options.build_directory)
   if options.output_directory:
     constants.SetOutputDirectort(options.output_directory)
+  if options.adb_path:
+    constants.SetAdbPath(options.adb_path)
+  # Some things such as Forwarder require ADB to be in the environment path.
+  adb_dir = os.path.dirname(constants.GetAdbPath())
+  if adb_dir and adb_dir not in os.environ['PATH'].split(os.pathsep):
+    os.environ['PATH'] = adb_dir + os.pathsep + os.environ['PATH']
   if options.environment not in constants.VALID_ENVIRONMENTS:
     error_func('--environment must be one of: %s' %
                ', '.join(constants.VALID_ENVIRONMENTS))
diff --git a/third_party/android_testrunner/README.chromium b/third_party/android_testrunner/README.chromium
index 7d382a1..6ca8498f 100644
--- a/third_party/android_testrunner/README.chromium
+++ b/third_party/android_testrunner/README.chromium
@@ -18,6 +18,8 @@
    bugs in run_command.py.
 4. Fixed a bug where wait_time wasn't properly respected in
     _WaitForShellCommandContents.
+5. Added option to specify the ADB binary that should be used instead of always
+  using the ADB in the environment path.
 
 Here is the detail steps
 1. Checkout Android source code
diff --git a/third_party/android_testrunner/adb_interface.py b/third_party/android_testrunner/adb_interface.py
index 93e19632..306f186 100644
--- a/third_party/android_testrunner/adb_interface.py
+++ b/third_party/android_testrunner/adb_interface.py
@@ -34,11 +34,19 @@
 class AdbInterface:
   """Helper class for communicating with Android device via adb."""
 
-  # argument to pass to adb, to direct command to specific device
-  _target_arg = ""
-
   DEVICE_TRACE_DIR = "/data/test_results/"
 
+  def __init__(self, adb_path='adb'):
+    """Constructor.
+
+    Args:
+      adb_path: Absolute path to the adb binary that should be used. Defaults
+                to the adb in the environment path.
+    """
+    self._adb_path = adb_path
+    # argument to pass to adb, to direct command to specific device
+    self._target_arg = ""
+
   def SetEmulatorTarget(self):
     """Direct all future commands to the only running emulator."""
     self._target_arg = "-e"
@@ -66,7 +74,7 @@
     Raises:
       WaitForResponseTimedOutError if device does not respond to command within time
     """
-    adb_cmd = "adb %s %s" % (self._target_arg, command_string)
+    adb_cmd = "%s %s %s" % (self._adb_path, self._target_arg, command_string)
     logger.SilentLog("about to run %s" % adb_cmd)
     return run_command.RunCommand(adb_cmd, timeout_time=timeout_time,
                                   retry_count=retry_count)