1 # Copyright 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
5 """Base class for host-driven test cases.
7 This test case is intended to serve as the base class for any host-driven
8 test cases. It is similar to the Python unitttest module in that test cases
9 inherit from this class and add methods which will be run as tests.
11 When a HostDrivenTestCase object is instantiated, its purpose is to run only one
12 test method in the derived class. The test runner gives it the name of the test
13 method the instance will run. The test runner calls SetUp with the device ID
14 which the test method will run against. The test runner runs the test method
15 itself, collecting the result, and calls TearDown.
17 Tests can perform arbitrary Python commands and asserts in test methods. Tests
18 that run instrumentation tests can make use of the _RunJavaTestFilters helper
19 function to trigger Java tests and convert results into a single host-driven
27 from pylib import android_commands
28 from pylib import forwarder
29 from pylib import valgrind_tools
30 from pylib.base import base_test_result
31 from pylib.instrumentation import test_package
32 from pylib.instrumentation import test_result
33 from pylib.instrumentation import test_runner
35 # aka the parent of com.google.android
36 BASE_ROOT = 'src' + os.sep
39 class HostDrivenTestCase(object):
40 """Base class for host-driven test cases."""
42 _HOST_DRIVEN_TAG = 'HostDriven'
44 def __init__(self, test_name, instrumentation_options=None):
45 """Create a test case initialized to run |test_name|.
48 test_name: The name of the method to run as the test.
49 instrumentation_options: An InstrumentationOptions object.
51 class_name = self.__class__.__name__
53 self.cleanup_test_files = False
55 self.has_forwarded_ports = False
56 self.instrumentation_options = instrumentation_options
57 self.ports_to_forward = []
58 self.push_deps = False
61 # Use tagged_name when creating results, so that we can identify host-driven
62 # tests in the overall results.
63 self.test_name = test_name
64 self.qualified_name = '%s.%s' % (class_name, self.test_name)
65 self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name)
67 # TODO(bulach): make ports_to_forward not optional and move the Forwarder
69 def SetUp(self, device, shard_index, push_deps,
70 cleanup_test_files, ports_to_forward=None):
71 if not ports_to_forward:
73 self.device_id = device
74 self.shard_index = shard_index
75 self.adb = android_commands.AndroidCommands(self.device_id)
76 self.push_deps = push_deps
77 self.cleanup_test_files = cleanup_test_files
79 self.ports_to_forward = ports_to_forward
84 # TODO(craigdh): Remove GetOutDir once references have been removed
87 return constants.GetOutDirectory()
90 logging.info('Running host-driven test: %s', self.tagged_name)
91 # Get the test method on the derived class and execute it
92 return getattr(self, self.test_name)()
95 def __GetHostForwarderLog():
96 return ('-- Begin Full HostForwarder log\n'
98 '--End Full HostForwarder log\n' % forwarder.Forwarder.GetHostLog())
100 def __StartForwarder(self):
101 logging.warning('Forwarding %s %s', self.ports_to_forward,
102 self.has_forwarded_ports)
103 if self.ports_to_forward and not self.has_forwarded_ports:
104 self.has_forwarded_ports = True
105 tool = valgrind_tools.CreateTool(None, self.adb)
106 forwarder.Forwarder.Map([(port, port) for port in self.ports_to_forward],
109 def __RunJavaTest(self, test, test_pkg, additional_flags=None):
110 """Runs a single Java test in a Java TestRunner.
113 test: Fully qualified test name (ex. foo.bar.TestClass#testMethod)
114 test_pkg: TestPackage object.
115 additional_flags: A list of additional flags to add to the command line.
118 TestRunResults object with a single test result.
120 # TODO(bulach): move this to SetUp() stage.
121 self.__StartForwarder()
123 java_test_runner = test_runner.TestRunner(self.instrumentation_options,
125 self.shard_index, test_pkg,
126 additional_flags=additional_flags)
128 java_test_runner.SetUp()
129 return java_test_runner.RunTest(test)[0]
131 java_test_runner.TearDown()
133 def _RunJavaTestFilters(self, test_filters, additional_flags=None):
134 """Calls a list of tests and stops at the first test failure.
136 This method iterates until either it encounters a non-passing test or it
137 exhausts the list of tests. Then it returns the appropriate overall result.
139 Test cases may make use of this method internally to assist in running
140 instrumentation tests. This function relies on instrumentation_options
144 test_filters: A list of Java test filters.
145 additional_flags: A list of addition flags to add to the command line.
148 A TestRunResults object containing an overall result for this set of Java
149 tests. If any Java tests do not pass, this is a fail overall.
151 test_type = base_test_result.ResultType.PASS
154 test_pkg = test_package.TestPackage(
155 self.instrumentation_options.test_apk_path,
156 self.instrumentation_options.test_apk_jar_path)
158 start_ms = int(time.time()) * 1000
160 for test_filter in test_filters:
161 tests = test_pkg.GetAllMatchingTests(None, None, test_filter)
162 # Filters should always result in >= 1 test.
164 raise Exception('Java test filter "%s" returned no tests.'
167 # We're only running one test at a time, so this TestRunResults object
168 # will hold only one result.
169 java_result = self.__RunJavaTest(test, test_pkg, additional_flags)
170 assert len(java_result.GetAll()) == 1
171 if not java_result.DidRunPass():
172 result = java_result.GetNotPass().pop()
173 log = result.GetLog()
174 log += self.__GetHostForwarderLog()
175 test_type = result.GetType()
180 duration_ms = int(time.time()) * 1000 - start_ms
182 overall_result = base_test_result.TestRunResults()
183 overall_result.AddResult(
184 test_result.InstrumentationTestResult(
185 self.tagged_name, test_type, start_ms, duration_ms, log=log))
186 return overall_result
189 return self.tagged_name
192 return self.tagged_name