Index: build/android/pylib/host_driven/test_case.py |
diff --git a/build/android/pylib/host_driven/test_case.py b/build/android/pylib/host_driven/test_case.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..6ff4c5fec5353b5388c8c053e54327b724b08faf |
--- /dev/null |
+++ b/build/android/pylib/host_driven/test_case.py |
@@ -0,0 +1,189 @@ |
+# Copyright 2013 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. |
+ |
+"""Base class for host-driven test cases. |
+ |
+This test case is intended to serve as the base class for any host-driven |
+test cases. It is similar to the Python unitttest module in that test cases |
+inherit from this class and add methods which will be run as tests. |
+ |
+When a HostDrivenTestCase object is instantiated, its purpose is to run only one |
+test method in the derived class. The test runner gives it the name of the test |
+method the instance will run. The test runner calls SetUp with the device ID |
+which the test method will run against. The test runner runs the test method |
+itself, collecting the result, and calls TearDown. |
+ |
+Tests can perform arbitrary Python commands and asserts in test methods. Tests |
+that run instrumentation tests can make use of the _RunJavaTestFilters helper |
+function to trigger Java tests and convert results into a single host-driven |
+test result. |
+""" |
+ |
+import logging |
+import os |
+import time |
+ |
+from pylib import constants |
+from pylib import forwarder |
+from pylib import valgrind_tools |
+from pylib.base import base_test_result |
+from pylib.device import device_utils |
+from pylib.instrumentation import test_package |
+from pylib.instrumentation import test_result |
+from pylib.instrumentation import test_runner |
+ |
+# aka the parent of com.google.android |
+BASE_ROOT = 'src' + os.sep |
+ |
+ |
+class HostDrivenTestCase(object): |
+ """Base class for host-driven test cases.""" |
+ |
+ _HOST_DRIVEN_TAG = 'HostDriven' |
+ |
+ def __init__(self, test_name, instrumentation_options=None): |
+ """Create a test case initialized to run |test_name|. |
+ |
+ Args: |
+ test_name: The name of the method to run as the test. |
+ instrumentation_options: An InstrumentationOptions object. |
+ """ |
+ class_name = self.__class__.__name__ |
+ self.device = None |
+ self.device_id = '' |
+ self.has_forwarded_ports = False |
+ self.instrumentation_options = instrumentation_options |
+ self.ports_to_forward = [] |
+ self.shard_index = 0 |
+ |
+ # Use tagged_name when creating results, so that we can identify host-driven |
+ # tests in the overall results. |
+ self.test_name = test_name |
+ self.qualified_name = '%s.%s' % (class_name, self.test_name) |
+ self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name) |
+ |
+ # TODO(bulach): make ports_to_forward not optional and move the Forwarder |
+ # mapping here. |
+ def SetUp(self, device, shard_index, ports_to_forward=None): |
+ if not ports_to_forward: |
+ ports_to_forward = [] |
+ self.device = device |
+ self.shard_index = shard_index |
+ self.device_id = str(self.device) |
+ if ports_to_forward: |
+ self.ports_to_forward = ports_to_forward |
+ |
+ def TearDown(self): |
+ pass |
+ |
+ # TODO(craigdh): Remove GetOutDir once references have been removed |
+ # downstream. |
+ @staticmethod |
+ def GetOutDir(): |
+ return constants.GetOutDirectory() |
+ |
+ def Run(self): |
+ logging.info('Running host-driven test: %s', self.tagged_name) |
+ # Get the test method on the derived class and execute it |
+ return getattr(self, self.test_name)() |
+ |
+ @staticmethod |
+ def __GetHostForwarderLog(): |
+ return ('-- Begin Full HostForwarder log\n' |
+ '%s\n' |
+ '--End Full HostForwarder log\n' % forwarder.Forwarder.GetHostLog()) |
+ |
+ def __StartForwarder(self): |
+ logging.warning('Forwarding %s %s', self.ports_to_forward, |
+ self.has_forwarded_ports) |
+ if self.ports_to_forward and not self.has_forwarded_ports: |
+ self.has_forwarded_ports = True |
+ tool = valgrind_tools.CreateTool(None, self.device) |
+ forwarder.Forwarder.Map([(port, port) for port in self.ports_to_forward], |
+ self.device, tool) |
+ |
+ def __RunJavaTest(self, test, test_pkg, additional_flags=None): |
+ """Runs a single Java test in a Java TestRunner. |
+ |
+ Args: |
+ test: Fully qualified test name (ex. foo.bar.TestClass#testMethod) |
+ test_pkg: TestPackage object. |
+ additional_flags: A list of additional flags to add to the command line. |
+ |
+ Returns: |
+ TestRunResults object with a single test result. |
+ """ |
+ # TODO(bulach): move this to SetUp() stage. |
+ self.__StartForwarder() |
+ |
+ java_test_runner = test_runner.TestRunner( |
+ self.instrumentation_options, self.device, self.shard_index, |
+ test_pkg, additional_flags=additional_flags) |
+ try: |
+ java_test_runner.SetUp() |
+ return java_test_runner.RunTest(test)[0] |
+ finally: |
+ java_test_runner.TearDown() |
+ |
+ def _RunJavaTestFilters(self, test_filters, additional_flags=None): |
+ """Calls a list of tests and stops at the first test failure. |
+ |
+ This method iterates until either it encounters a non-passing test or it |
+ exhausts the list of tests. Then it returns the appropriate overall result. |
+ |
+ Test cases may make use of this method internally to assist in running |
+ instrumentation tests. This function relies on instrumentation_options |
+ being defined. |
+ |
+ Args: |
+ test_filters: A list of Java test filters. |
+ additional_flags: A list of addition flags to add to the command line. |
+ |
+ Returns: |
+ A TestRunResults object containing an overall result for this set of Java |
+ tests. If any Java tests do not pass, this is a fail overall. |
+ """ |
+ test_type = base_test_result.ResultType.PASS |
+ log = '' |
+ |
+ test_pkg = test_package.TestPackage( |
+ self.instrumentation_options.test_apk_path, |
+ self.instrumentation_options.test_apk_jar_path, |
+ self.instrumentation_options.test_support_apk_path) |
+ |
+ start_ms = int(time.time()) * 1000 |
+ done = False |
+ for test_filter in test_filters: |
+ tests = test_pkg.GetAllMatchingTests(None, None, test_filter) |
+ # Filters should always result in >= 1 test. |
+ if len(tests) == 0: |
+ raise Exception('Java test filter "%s" returned no tests.' |
+ % test_filter) |
+ for test in tests: |
+ # We're only running one test at a time, so this TestRunResults object |
+ # will hold only one result. |
+ java_result = self.__RunJavaTest(test, test_pkg, additional_flags) |
+ assert len(java_result.GetAll()) == 1 |
+ if not java_result.DidRunPass(): |
+ result = java_result.GetNotPass().pop() |
+ log = result.GetLog() |
+ log += self.__GetHostForwarderLog() |
+ test_type = result.GetType() |
+ done = True |
+ break |
+ if done: |
+ break |
+ duration_ms = int(time.time()) * 1000 - start_ms |
+ |
+ overall_result = base_test_result.TestRunResults() |
+ overall_result.AddResult( |
+ test_result.InstrumentationTestResult( |
+ self.tagged_name, test_type, start_ms, duration_ms, log=log)) |
+ return overall_result |
+ |
+ def __str__(self): |
+ return self.tagged_name |
+ |
+ def __repr__(self): |
+ return self.tagged_name |