Add support for Junit tests in platform mode.

Adding support for Junit tests in platform mode. Added new test env
for running tests on local machine.

Review-Url: https://codereview.chromium.org/2498553004
Cr-Commit-Position: refs/heads/master@{#431711}
diff --git a/build/android/pylib/base/environment_factory.py b/build/android/pylib/base/environment_factory.py
index d72bbeb..29de483 100644
--- a/build/android/pylib/base/environment_factory.py
+++ b/build/android/pylib/base/environment_factory.py
@@ -4,12 +4,14 @@
 
 from pylib import constants
 from pylib.local.device import local_device_environment
+from pylib.local.machine import local_machine_environment
 
 def CreateEnvironment(args, error_func):
 
   if args.environment == 'local':
     if args.command not in constants.LOCAL_MACHINE_TESTS:
       return local_device_environment.LocalDeviceEnvironment(args, error_func)
-    # TODO(jbudorick) Add local machine environment.
+    else:
+      return local_machine_environment.LocalMachineEnvironment(args, error_func)
 
   error_func('Unable to create %s environment.' % args.environment)
diff --git a/build/android/pylib/base/test_instance_factory.py b/build/android/pylib/base/test_instance_factory.py
index 2d0d83f..129bd7f 100644
--- a/build/android/pylib/base/test_instance_factory.py
+++ b/build/android/pylib/base/test_instance_factory.py
@@ -4,6 +4,7 @@
 
 from pylib.gtest import gtest_test_instance
 from pylib.instrumentation import instrumentation_test_instance
+from pylib.junit import junit_test_instance
 from pylib.perf import perf_test_instance
 from pylib.utils import isolator
 
@@ -16,6 +17,8 @@
   elif args.command == 'instrumentation':
     return instrumentation_test_instance.InstrumentationTestInstance(
         args, isolator.Isolator(), error_func)
+  elif args.command == 'junit':
+    return junit_test_instance.JunitTestInstance(args, error_func)
   elif args.command == 'perf':
     return perf_test_instance.PerfTestInstance(args, error_func)
 
diff --git a/build/android/pylib/base/test_run.py b/build/android/pylib/base/test_run.py
index 7380e78..9b16f89 100644
--- a/build/android/pylib/base/test_run.py
+++ b/build/android/pylib/base/test_run.py
@@ -25,6 +25,11 @@
     raise NotImplementedError
 
   def RunTests(self):
+    """Runs Tests and returns test results.
+
+    Returns:
+      Should return list of |base_test_result.TestRunResults| objects.
+    """
     raise NotImplementedError
 
   def TearDown(self):
diff --git a/build/android/pylib/base/test_run_factory.py b/build/android/pylib/base/test_run_factory.py
index 04a8f3c..d706fd4 100644
--- a/build/android/pylib/base/test_run_factory.py
+++ b/build/android/pylib/base/test_run_factory.py
@@ -4,10 +4,13 @@
 
 from pylib.gtest import gtest_test_instance
 from pylib.instrumentation import instrumentation_test_instance
+from pylib.junit import junit_test_instance
 from pylib.local.device import local_device_environment
 from pylib.local.device import local_device_gtest_run
 from pylib.local.device import local_device_instrumentation_test_run
 from pylib.local.device import local_device_perf_test_run
+from pylib.local.machine import local_machine_environment
+from pylib.local.machine import local_machine_junit_test_run
 from pylib.perf import perf_test_instance
 
 
@@ -34,6 +37,11 @@
                   perf_test_instance.PerfTestInstance):
       return _CreatePerfTestRun(args, env, test_instance)
 
+  if isinstance(env, local_machine_environment.LocalMachineEnvironment):
+    if isinstance(test_instance, junit_test_instance.JunitTestInstance):
+      return (local_machine_junit_test_run
+              .LocalMachineJunitTestRun(env, test_instance))
+
   error_func('Unable to create test run for %s tests in %s environment'
              % (str(test_instance), str(env)))
 
diff --git a/build/android/pylib/junit/junit_test_instance.py b/build/android/pylib/junit/junit_test_instance.py
new file mode 100644
index 0000000..5fd6af9
--- /dev/null
+++ b/build/android/pylib/junit/junit_test_instance.py
@@ -0,0 +1,49 @@
+# Copyright 2016 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.
+
+from pylib.base import test_instance
+
+
+class JunitTestInstance(test_instance.TestInstance):
+
+  def __init__(self, args, _):
+    super(JunitTestInstance, self).__init__()
+
+    self._coverage_dir = args.coverage_dir
+    self._package_filter = args.package_filter
+    self._runner_filter = args.runner_filter
+    self._test_filter = args.test_filter
+    self._test_suite = args.test_suite
+
+  #override
+  def TestType(self):
+    return 'junit'
+
+  #override
+  def SetUp(self):
+    pass
+
+  #override
+  def TearDown(self):
+    pass
+
+  @property
+  def coverage_dir(self):
+    return self._coverage_dir
+
+  @property
+  def package_filter(self):
+    return self._package_filter
+
+  @property
+  def runner_filter(self):
+    return self._runner_filter
+
+  @property
+  def test_filter(self):
+    return self._test_filter
+
+  @property
+  def suite(self):
+    return self._test_suite
diff --git a/build/android/pylib/junit/test_runner.py b/build/android/pylib/junit/test_runner.py
index 5066c204..17e8afa 100644
--- a/build/android/pylib/junit/test_runner.py
+++ b/build/android/pylib/junit/test_runner.py
@@ -17,7 +17,7 @@
     self._coverage_dir = args.coverage_dir
     self._package_filter = args.package_filter
     self._runner_filter = args.runner_filter
-    self._sdk_version = args.sdk_version
+
     self._test_filter = args.test_filter
     self._test_suite = args.test_suite
 
@@ -40,8 +40,6 @@
         jar_args.extend(['-package-filter', self._package_filter])
       if self._runner_filter:
         jar_args.extend(['-runner-filter', self._runner_filter])
-      if self._sdk_version:
-        jar_args.extend(['-sdk-version', self._sdk_version])
       command.extend(['--jar-args', '"%s"' % ' '.join(jar_args)])
 
       # Add JVM arguments.
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 6c00b70..59d43a9 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
@@ -62,6 +62,7 @@
   def TestPackage(self):
     return self._test_instance.suite
 
+  #override
   def SetUp(self):
     def substitute_device_root(d, device_root):
       if not d:
@@ -153,6 +154,7 @@
         individual_device_set_up,
         self._test_instance.GetDataDependencies())
 
+  #override
   def TearDown(self):
     @local_device_environment.handle_shard_failures_with(
         self._env.BlacklistDevice)
diff --git a/build/android/pylib/local/device/local_device_perf_test_run.py b/build/android/pylib/local/device/local_device_perf_test_run.py
index 823e903..3398a0b 100644
--- a/build/android/pylib/local/device/local_device_perf_test_run.py
+++ b/build/android/pylib/local/device/local_device_perf_test_run.py
@@ -335,11 +335,13 @@
     self._test_instance = test_instance
     self._timeout = None if test_instance.no_timeout else self._DEFAULT_TIMEOUT
 
+  #override
   def SetUp(self):
     if os.path.exists(constants.PERF_OUTPUT_DIR):
       shutil.rmtree(constants.PERF_OUTPUT_DIR)
     os.makedirs(constants.PERF_OUTPUT_DIR)
 
+  #override
   def TearDown(self):
     pass
 
diff --git a/build/android/pylib/local/machine/__init__.py b/build/android/pylib/local/machine/__init__.py
new file mode 100644
index 0000000..ca3e206f
--- /dev/null
+++ b/build/android/pylib/local/machine/__init__.py
@@ -0,0 +1,3 @@
+# Copyright 2016 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.
diff --git a/build/android/pylib/local/machine/local_machine_environment.py b/build/android/pylib/local/machine/local_machine_environment.py
new file mode 100644
index 0000000..b9f6acad
--- /dev/null
+++ b/build/android/pylib/local/machine/local_machine_environment.py
@@ -0,0 +1,19 @@
+# Copyright 2016 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.
+
+from pylib.base import environment
+
+
+class LocalMachineEnvironment(environment.Environment):
+
+  def __init__(self, _args, _error_func):
+    super(LocalMachineEnvironment, self).__init__()
+
+  #override
+  def SetUp(self):
+    pass
+
+  #override
+  def TearDown(self):
+    pass
diff --git a/build/android/pylib/local/machine/local_machine_junit_test_run.py b/build/android/pylib/local/machine/local_machine_junit_test_run.py
new file mode 100644
index 0000000..ef8bef4
--- /dev/null
+++ b/build/android/pylib/local/machine/local_machine_junit_test_run.py
@@ -0,0 +1,78 @@
+# Copyright 2016 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.
+
+import json
+import os
+import tempfile
+
+from devil.utils import cmd_helper
+from pylib import constants
+from pylib.base import base_test_result
+from pylib.base import test_run
+from pylib.results import json_results
+
+
+class LocalMachineJunitTestRun(test_run.TestRun):
+  def __init__(self, env, test_instance):
+    super(LocalMachineJunitTestRun, self).__init__(env, test_instance)
+
+  #override
+  def TestPackage(self):
+    return self._test_instance.suite
+
+  #override
+  def SetUp(self):
+    pass
+
+  #override
+  def RunTests(self):
+    with tempfile.NamedTemporaryFile() as json_file:
+      java_script = os.path.join(
+          constants.GetOutDirectory(), 'bin', 'helper',
+          self._test_instance.suite)
+      command = [java_script]
+
+      # Add Jar arguments.
+      jar_args = ['-test-jars', self._test_instance.suite + '.jar',
+                  '-json-results-file', json_file.name]
+      if self._test_instance.test_filter:
+        jar_args.extend(['-gtest-filter', self._test_instance.test_filter])
+      if self._test_instance.package_filter:
+        jar_args.extend(['-package-filter',
+                         self._test_instance.package_filter])
+      if self._test_instance.runner_filter:
+        jar_args.extend(['-runner-filter', self._test_instance.runner_filter])
+      command.extend(['--jar-args', '"%s"' % ' '.join(jar_args)])
+
+      # Add JVM arguments.
+      jvm_args = []
+      # TODO(mikecase): Add a --robolectric-dep-dir arg to test runner.
+      # Have this arg set by GN in the generated test runner scripts.
+      jvm_args += [
+          '-Drobolectric.dependency.dir=%s' %
+          os.path.join(constants.GetOutDirectory(),
+              'lib.java', 'third_party', 'robolectric')]
+      if self._test_instance.coverage_dir:
+        if not os.path.exists(self._test_instance.coverage_dir):
+          os.makedirs(self._test_instance.coverage_dir)
+        elif not os.path.isdir(self._test_instance.coverage_dir):
+          raise Exception('--coverage-dir takes a directory, not file path.')
+        jvm_args.append('-Demma.coverage.out.file=%s' % os.path.join(
+            self._test_instance.coverage_dir,
+            '%s.ec' % self._test_instance.suite))
+      if jvm_args:
+        command.extend(['--jvm-args', '"%s"' % ' '.join(jvm_args)])
+
+      cmd_helper.RunCmd(command)
+      results_list = json_results.ParseResultsFromJson(
+          json.loads(json_file.read()))
+
+      test_run_results = base_test_result.TestRunResults()
+      test_run_results.AddResults(results_list)
+
+      return [test_run_results]
+
+  #override
+  def TearDown(self):
+    pass
diff --git a/build/android/pylib/perf/perf_test_instance.py b/build/android/pylib/perf/perf_test_instance.py
index 426ffaf..fbb7962d 100644
--- a/build/android/pylib/perf/perf_test_instance.py
+++ b/build/android/pylib/perf/perf_test_instance.py
@@ -81,9 +81,11 @@
     self._test_filter = args.test_filter
     self._write_buildbot_json = args.write_buildbot_json
 
+  #override
   def SetUp(self):
     pass
 
+  #override
   def TearDown(self):
     pass
 
diff --git a/build/android/test_runner.py b/build/android/test_runner.py
index 6509d1a2b66..42429d3 100755
--- a/build/android/test_runner.py
+++ b/build/android/test_runner.py
@@ -400,13 +400,19 @@
   group.add_argument(
       '--package-filter', dest='package_filter',
       help='Filters tests by package.')
+  # TODO(mikecase): Add --repeat and --break-on-failure to common options.
+  # These options are required for platform-mode support.
+  group.add_argument(
+      '--repeat', dest='repeat', type=int, default=0,
+      help='Number of times to repeat the specified set of tests.')
+  group.add_argument(
+      '--break-on-failure', '--break_on_failure',
+      dest='break_on_failure', action='store_true',
+      help='Whether to break on failure.')
   group.add_argument(
       '--runner-filter', dest='runner_filter',
       help='Filters tests by runner class. Must be fully qualified.')
   group.add_argument(
-      '--sdk-version', dest='sdk_version', type=int,
-      help='The Android SDK version.')
-  group.add_argument(
       '--coverage-dir', dest='coverage_dir', type=os.path.realpath,
       help='Directory to store coverage info.')
   AddCommonOptions(parser)
@@ -721,6 +727,7 @@
   # TODO(jbudorick): Add support for more test types.
   'gtest',
   'instrumentation',
+  'junit',
   'perf',
 ]
 
diff --git a/build/android/test_runner.pydeps b/build/android/test_runner.pydeps
index 554181d..8bc5786 100644
--- a/build/android/test_runner.pydeps
+++ b/build/android/test_runner.pydeps
@@ -93,6 +93,7 @@
 pylib/instrumentation/instrumentation_test_instance.py
 pylib/instrumentation/test_result.py
 pylib/junit/__init__.py
+pylib/junit/junit_test_instance.py
 pylib/junit/setup.py
 pylib/junit/test_dispatcher.py
 pylib/junit/test_runner.py
@@ -108,6 +109,9 @@
 pylib/local/device/local_device_perf_test_run.py
 pylib/local/device/local_device_test_run.py
 pylib/local/local_test_server_spawner.py
+pylib/local/machine/__init__.py
+pylib/local/machine/local_machine_environment.py
+pylib/local/machine/local_machine_junit_test_run.py
 pylib/monkey/__init__.py
 pylib/monkey/setup.py
 pylib/monkey/test_options.py