OLD | NEW |
| (Empty) |
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. | |
4 | |
5 """Base class for host-driven test cases. | |
6 | |
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. | |
10 | |
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. | |
16 | |
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 | |
20 test result. | |
21 """ | |
22 | |
23 import logging | |
24 import os | |
25 import time | |
26 | |
27 from devil.android import forwarder | |
28 from pylib import constants | |
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 | |
34 | |
35 # aka the parent of com.google.android | |
36 BASE_ROOT = 'src' + os.sep | |
37 | |
38 | |
39 class HostDrivenTestCase(object): | |
40 """Base class for host-driven test cases.""" | |
41 | |
42 _HOST_DRIVEN_TAG = 'HostDriven' | |
43 | |
44 def __init__(self, test_name, instrumentation_options=None): | |
45 """Create a test case initialized to run |test_name|. | |
46 | |
47 Args: | |
48 test_name: The name of the method to run as the test. | |
49 instrumentation_options: An InstrumentationOptions object. | |
50 """ | |
51 class_name = self.__class__.__name__ | |
52 self.device = None | |
53 self.device_id = '' | |
54 self.has_forwarded_ports = False | |
55 self.instrumentation_options = instrumentation_options | |
56 self.ports_to_forward = [] | |
57 self.shard_index = 0 | |
58 | |
59 # Use tagged_name when creating results, so that we can identify host-driven | |
60 # tests in the overall results. | |
61 self.test_name = test_name | |
62 self.qualified_name = '%s.%s' % (class_name, self.test_name) | |
63 self.tagged_name = '%s_%s' % (self._HOST_DRIVEN_TAG, self.qualified_name) | |
64 | |
65 # TODO(bulach): make ports_to_forward not optional and move the Forwarder | |
66 # mapping here. | |
67 def SetUp(self, device, shard_index, ports_to_forward=None): | |
68 if not ports_to_forward: | |
69 ports_to_forward = [] | |
70 self.device = device | |
71 self.shard_index = shard_index | |
72 self.device_id = str(self.device) | |
73 if ports_to_forward: | |
74 self.ports_to_forward = ports_to_forward | |
75 | |
76 def TearDown(self): | |
77 pass | |
78 | |
79 # TODO(craigdh): Remove GetOutDir once references have been removed | |
80 # downstream. | |
81 @staticmethod | |
82 def GetOutDir(): | |
83 return constants.GetOutDirectory() | |
84 | |
85 def Run(self): | |
86 logging.info('Running host-driven test: %s', self.tagged_name) | |
87 # Get the test method on the derived class and execute it | |
88 return getattr(self, self.test_name)() | |
89 | |
90 @staticmethod | |
91 def __GetHostForwarderLog(): | |
92 return ('-- Begin Full HostForwarder log\n' | |
93 '%s\n' | |
94 '--End Full HostForwarder log\n' % forwarder.Forwarder.GetHostLog()) | |
95 | |
96 def __StartForwarder(self): | |
97 logging.warning('Forwarding %s %s', self.ports_to_forward, | |
98 self.has_forwarded_ports) | |
99 if self.ports_to_forward and not self.has_forwarded_ports: | |
100 self.has_forwarded_ports = True | |
101 tool = valgrind_tools.CreateTool(None, self.device) | |
102 forwarder.Forwarder.Map([(port, port) for port in self.ports_to_forward], | |
103 self.device, tool) | |
104 | |
105 def __RunJavaTest(self, test, test_pkg, additional_flags=None): | |
106 """Runs a single Java test in a Java TestRunner. | |
107 | |
108 Args: | |
109 test: Fully qualified test name (ex. foo.bar.TestClass#testMethod) | |
110 test_pkg: TestPackage object. | |
111 additional_flags: A list of additional flags to add to the command line. | |
112 | |
113 Returns: | |
114 TestRunResults object with a single test result. | |
115 """ | |
116 # TODO(bulach): move this to SetUp() stage. | |
117 self.__StartForwarder() | |
118 | |
119 java_test_runner = test_runner.TestRunner( | |
120 self.instrumentation_options, self.device, self.shard_index, | |
121 test_pkg, additional_flags=additional_flags) | |
122 try: | |
123 java_test_runner.SetUp() | |
124 return java_test_runner.RunTest(test)[0] | |
125 finally: | |
126 java_test_runner.TearDown() | |
127 | |
128 def _RunJavaTestFilters(self, test_filters, additional_flags=None): | |
129 """Calls a list of tests and stops at the first test failure. | |
130 | |
131 This method iterates until either it encounters a non-passing test or it | |
132 exhausts the list of tests. Then it returns the appropriate overall result. | |
133 | |
134 Test cases may make use of this method internally to assist in running | |
135 instrumentation tests. This function relies on instrumentation_options | |
136 being defined. | |
137 | |
138 Args: | |
139 test_filters: A list of Java test filters. | |
140 additional_flags: A list of addition flags to add to the command line. | |
141 | |
142 Returns: | |
143 A TestRunResults object containing an overall result for this set of Java | |
144 tests. If any Java tests do not pass, this is a fail overall. | |
145 """ | |
146 test_type = base_test_result.ResultType.PASS | |
147 log = '' | |
148 | |
149 opts = self.instrumentation_options | |
150 test_pkg = test_package.TestPackage( | |
151 opts.test_apk_path, | |
152 opts.test_apk_jar_path, | |
153 opts.test_support_apk_path, | |
154 test_apk_incremental_install_script= | |
155 opts.test_apk_incremental_install_script) | |
156 | |
157 start_ms = int(time.time()) * 1000 | |
158 done = False | |
159 for test_filter in test_filters: | |
160 tests = test_pkg.GetAllMatchingTests( | |
161 None, None, test_filter, [self.device]) | |
162 # Filters should always result in >= 1 test. | |
163 if len(tests) == 0: | |
164 raise Exception('Java test filter "%s" returned no tests.' | |
165 % test_filter) | |
166 for test in 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() | |
176 done = True | |
177 break | |
178 if done: | |
179 break | |
180 duration_ms = int(time.time()) * 1000 - start_ms | |
181 | |
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 | |
187 | |
188 def __str__(self): | |
189 return self.tagged_name | |
190 | |
191 def __repr__(self): | |
192 return self.tagged_name | |
OLD | NEW |