Android: Add --wait-for-java-debugger to test_runner.py

I used this to debug test listing in the instrumentation test runner.
This is nicer that using set-debug-app separately, because it disables
all timeouts (or else the process gets killed as you're debugging).

Change-Id: I7cd58c747534b3b539afcc84fcbae48475c4d9c3
Reviewed-on: https://chromium-review.googlesource.com/693218
Commit-Queue: Andrew Grieve <agrieve@chromium.org>
Reviewed-by: Yoland Yan <yolandyan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#506049}
diff --git a/build/android/apk_operations.py b/build/android/apk_operations.py
index c7fef4a2..1343924 100755
--- a/build/android/apk_operations.py
+++ b/build/android/apk_operations.py
@@ -81,7 +81,7 @@
 
 
 def _LaunchUrl(devices, input_args, device_args_file, url, apk,
-               wait_for_debugger):
+               wait_for_java_debugger):
   if input_args and device_args_file is None:
     raise Exception('This apk does not support any flags.')
   if url:
@@ -92,8 +92,10 @@
   def launch(device):
     # Set debug app in order to enable reading command line flags on user
     # builds.
-    cmd = ['am', 'set-debug-app', '--persistent', apk.GetPackageName()]
-    if wait_for_debugger:
+    cmd = ['am', 'set-debug-app', apk.GetPackageName()]
+    if wait_for_java_debugger:
+      # To wait for debugging on a non-primary process:
+      #     am set-debug-app org.chromium.chrome:privileged_process0
       cmd[-1:-1] = ['-w']
     # Ignore error since it will fail if apk is not debuggable.
     device.RunShellCommand(cmd, check_return=False)
@@ -844,13 +846,15 @@
   all_devices_by_default = True
 
   def _RegisterExtraArgs(self, group):
-    group.add_argument('-w', '--wait-for-debugger', action='store_true',
-                       help='Pause execution until debugger attaches.')
+    group.add_argument('-w', '--wait-for-java-debugger', action='store_true',
+                       help='Pause execution until debugger attaches. Applies '
+                            'only to the main process. To have renderers wait, '
+                            'use --args="--renderer-wait-for-java-debugger"')
     group.add_argument('url', nargs='?', help='A URL to launch with.')
 
   def Run(self):
     _LaunchUrl(self.devices, self.args.args, self.args.command_line_flags_file,
-               self.args.url, self.apk_helper, self.args.wait_for_debugger)
+               self.args.url, self.apk_helper, self.args.wait_for_java_debugger)
 
 
 class _StopCommand(_Command):
diff --git a/build/android/pylib/gtest/gtest_test_instance.py b/build/android/pylib/gtest/gtest_test_instance.py
index 26fedbf..4de7c81 100644
--- a/build/android/pylib/gtest/gtest_test_instance.py
+++ b/build/android/pylib/gtest/gtest_test_instance.py
@@ -293,6 +293,7 @@
     self._suite = args.suite_name[0]
     self._symbolizer = stack_symbolizer.Symbolizer(None, False)
     self._gs_test_artifacts_bucket = args.gs_test_artifacts_bucket
+    self._wait_for_java_debugger = args.wait_for_java_debugger
 
     # GYP:
     if args.executable_dist_dir:
@@ -327,6 +328,8 @@
         self._extras[_EXTRA_SHARD_SIZE_LIMIT] = 1
         self._extras[EXTRA_SHARD_NANO_TIMEOUT] = int(1e9 * self._shard_timeout)
         self._shard_timeout = 10 * self._shard_timeout
+      if args.wait_for_java_debugger:
+        self._extras[EXTRA_SHARD_NANO_TIMEOUT] = int(1e15)  # Forever
 
     if not self._apk_helper and not self._exe_dist_dir:
       error_func('Could not find apk or executable for %s' % self._suite)
@@ -469,6 +472,10 @@
   def total_external_shards(self):
     return self._total_external_shards
 
+  @property
+  def wait_for_java_debugger(self):
+    return self._wait_for_java_debugger
+
   #override
   def TestType(self):
     return 'gtest'
diff --git a/build/android/pylib/local/device/local_device_environment.py b/build/android/pylib/local/device/local_device_environment.py
index 1bae4a0..6f1d0389 100644
--- a/build/android/pylib/local/device/local_device_environment.py
+++ b/build/android/pylib/local/device/local_device_environment.py
@@ -145,6 +145,10 @@
     self._trace_all = None
     if hasattr(args, 'trace_all'):
       self._trace_all = args.trace_all
+    self._wait_for_java_debugger = args.wait_for_java_debugger
+
+    if self._wait_for_java_debugger:
+      self._max_tries = 1
 
     devil_chromium.Initialize(
         output_directory=constants.GetOutDirectory(),
@@ -250,6 +254,10 @@
   def trace_output(self):
     return self._trace_output
 
+  @property
+  def wait_for_java_debugger(self):
+    return self._wait_for_java_debugger
+
   #override
   def TearDown(self):
     if self.trace_output:
diff --git a/build/android/pylib/local/device/local_device_gtest_run.py b/build/android/pylib/local/device/local_device_gtest_run.py
index 22794a43..6d9ae3a 100644
--- a/build/android/pylib/local/device/local_device_gtest_run.py
+++ b/build/android/pylib/local/device/local_device_gtest_run.py
@@ -118,6 +118,7 @@
     self._suite = test_instance.suite
     self._component = '%s/%s' % (self._package, self._runner)
     self._extras = test_instance.extras
+    self._wait_for_java_debugger = test_instance.wait_for_java_debugger
 
   def GetTestDataRoot(self, device):
     # pylint: disable=no-self-use
@@ -169,6 +170,10 @@
         device.adb, dir=device.GetExternalStoragePath(), suffix='.gtest_out')
     extras[_EXTRA_STDOUT_FILE] = stdout_file.name
 
+    if self._wait_for_java_debugger:
+      cmd = ['am', 'set-debug-app', '-w', self._package]
+      device.RunShellCommand(cmd, check_return=True)
+
     with command_line_file, test_list_file, stdout_file:
       try:
         device.StartInstrumentation(
@@ -370,9 +375,12 @@
     @local_device_environment.handle_shard_failures_with(
         on_failure=self._env.BlacklistDevice)
     def list_tests(dev):
+      timeout = 30
+      if self._test_instance.wait_for_java_debugger:
+        timeout = None
       raw_test_list = crash_handler.RetryOnSystemCrash(
           lambda d: self._delegate.Run(
-              None, d, flags='--gtest_list_tests', timeout=30),
+              None, d, flags='--gtest_list_tests', timeout=timeout),
           device=dev)
       tests = gtest_test_instance.ParseGTestListTests(raw_test_list)
       if not tests:
@@ -420,6 +428,8 @@
     # Run the test.
     timeout = (self._test_instance.shard_timeout
                * self.GetTool(device).GetTimeoutScale())
+    if self._test_instance.wait_for_java_debugger:
+      timeout = None
     if self._test_instance.store_tombstones:
       tombstones.ClearAllTombstones(device)
     with device_temp_file.DeviceTempFile(
diff --git a/build/android/pylib/local/device/local_device_instrumentation_test_run.py b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
index 2c781791f..94ff92b 100644
--- a/build/android/pylib/local/device/local_device_instrumentation_test_run.py
+++ b/build/android/pylib/local/device/local_device_instrumentation_test_run.py
@@ -202,15 +202,16 @@
       def set_debug_app(dev):
         # Set debug app in order to enable reading command line flags on user
         # builds
-        if self._test_instance.flags:
-          if not self._test_instance.package_info:
-            logging.error("Couldn't set debug app: no package info")
-          elif not self._test_instance.package_info.package:
-            logging.error("Couldn't set debug app: no package defined")
-          else:
-            dev.RunShellCommand(['am', 'set-debug-app', '--persistent',
-                               self._test_instance.package_info.package],
-                              check_return=True)
+        if not self._test_instance.package_info:
+          logging.error("Couldn't set debug app: no package info")
+        elif not self._test_instance.package_info.package:
+          logging.error("Couldn't set debug app: no package defined")
+        else:
+          cmd = ['am', 'set-debug-app', '--persistent']
+          if self._env.wait_for_java_debugger:
+            cmd.append('-w')
+          cmd.append(self._test_instance.package_info.package)
+          dev.RunShellCommand(cmd, check_return=True)
 
       @trace_event.traced
       def edit_shared_prefs(dev):
@@ -421,6 +422,8 @@
         valgrind_tools.SetChromeTimeoutScale(
             device, test_timeout_scale * self._test_instance.timeout_scale)
 
+    if self._env.wait_for_java_debugger:
+      timeout = None
     logging.info('preparing to run %s: %s', test_display_name, test)
 
     render_tests_device_output_dir = None
@@ -611,8 +614,11 @@
           extras['log'] = 'true'
           extras[_EXTRA_TEST_LIST] = dev_test_list_json.name
           target = '%s/%s' % (test_package, junit4_runner_class)
+          kwargs = {}
+          if self._env.wait_for_java_debugger:
+            kwargs['timeout'] = None
           test_list_run_output = dev.StartInstrumentation(
-              target, extras=extras)
+              target, extras=extras, retries=0, **kwargs)
           if any(test_list_run_output):
             logging.error('Unexpected output while listing tests:')
             for line in test_list_run_output:
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index ae2275c..edfa8c5c 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -334,6 +334,10 @@
       '--test-apk-incremental-install-json',
       type=os.path.realpath,
       help='Path to install json for the test apk.')
+  parser.add_argument(
+      '-w', '--wait-for-java-debugger', action='store_true',
+      help='Wait for java debugger to attach before running any application '
+           'code. Also disables test timeouts and sets retries=0.')
 
   filter_group = parser.add_mutually_exclusive_group()
   filter_group.add_argument(
@@ -481,6 +485,10 @@
       '--ui-screenshot-directory',
       dest='ui_screenshot_dir', type=os.path.realpath,
       help='Destination for screenshots captured by the tests')
+  parser.add_argument(
+      '-w', '--wait-for-java-debugger', action='store_true',
+      help='Wait for java debugger to attach before running any application '
+           'code. Also disables test timeouts and sets retries=0.')
 
   # These arguments are suppressed from the help text because they should
   # only ever be specified by an intermediate script.
diff --git a/docs/android_debugging_instructions.md b/docs/android_debugging_instructions.md
index 705b538..62fd19fe 100644
--- a/docs/android_debugging_instructions.md
+++ b/docs/android_debugging_instructions.md
@@ -106,8 +106,8 @@
 
 ## Waiting for Java Debugger on Early Startup
 
-*   To debug early startup, pass `--wait-for-java-debugger` as a command line
-    flag.
+*   To debug early startup, pass `--wait-for-java-debugger` to the wrapper
+    scripts (works for both apk wrappers as well as test wrappers).
 
 ## Debugging C/C++