Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1343)

Side by Side Diff: build/android/pylib/gtest/setup.py

Issue 18770008: [Android] Redesigns the sharder to allow replicated vs distributed tests (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fixes python tests Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright (c) 2013 The Chromium Authors. All rights reserved. 1 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 """Dispatches GTests.""" 5 """Generates test runner factory and tests for GTests."""
6 6
7 import copy
8 import fnmatch 7 import fnmatch
9 import glob 8 import glob
10 import logging 9 import logging
11 import os 10 import os
12 import shutil 11 import shutil
13 12
14 from pylib import android_commands 13 from pylib import android_commands
15 from pylib import cmd_helper 14 from pylib import cmd_helper
16 from pylib import constants 15 from pylib import constants
17 from pylib import ports
18 from pylib.base import base_test_result 16 from pylib.base import base_test_result
19 from pylib.base import shard
20 from pylib.utils import emulator
21 from pylib.utils import report_results
22 from pylib.utils import xvfb
23 17
24 import gtest_config 18 import gtest_config
25 import test_runner 19 import test_runner
26 20
27 21
28 # TODO(frankf): Add more test targets here after making sure we don't 22 # TODO(frankf): Add more test targets here after making sure we don't
29 # blow up the dependency size (and the world). 23 # blow up the dependency size (and the world).
30 _ISOLATE_FILE_PATHS = { 24 _ISOLATE_FILE_PATHS = {
31 'base_unittests': 'base/base_unittests.isolate', 25 'base_unittests': 'base/base_unittests.isolate',
32 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate', 26 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
(...skipping 27 matching lines...) Expand all
60 'third_party/hunspell_dictionaries/*.dic', 54 'third_party/hunspell_dictionaries/*.dic',
61 # crbug.com/258690 55 # crbug.com/258690
62 'webkit/data/bmp_decoder', 56 'webkit/data/bmp_decoder',
63 'webkit/data/ico_decoder', 57 'webkit/data/ico_decoder',
64 ] 58 ]
65 59
66 _ISOLATE_SCRIPT = os.path.join( 60 _ISOLATE_SCRIPT = os.path.join(
67 constants.DIR_SOURCE_ROOT, 'tools', 'swarm_client', 'isolate.py') 61 constants.DIR_SOURCE_ROOT, 'tools', 'swarm_client', 'isolate.py')
68 62
69 63
70 def _GenerateDepsDirUsingIsolate(test_suite, build_type): 64 def _GenerateDepsDirUsingIsolate(suite_name, build_type):
71 """Generate the dependency dir for the test suite using isolate. 65 """Generate the dependency dir for the test suite using isolate.
72 66
73 Args: 67 Args:
74 test_suite: The test suite basename (e.g. base_unittests). 68 suite_name: The test suite basename (e.g. base_unittests).
75 build_type: Release/Debug 69 build_type: Release/Debug
76 """ 70 """
77 product_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type) 71 product_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type)
78 assert os.path.isabs(product_dir) 72 assert os.path.isabs(product_dir)
79 73
80 if os.path.isdir(constants.ISOLATE_DEPS_DIR): 74 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
81 shutil.rmtree(constants.ISOLATE_DEPS_DIR) 75 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
82 76
83 isolate_rel_path = _ISOLATE_FILE_PATHS.get(test_suite) 77 isolate_rel_path = _ISOLATE_FILE_PATHS.get(suite_name)
84 if not isolate_rel_path: 78 if not isolate_rel_path:
85 logging.info('Did not find an isolate file for the test suite.') 79 logging.info('Did not find an isolate file for the test suite.')
86 return 80 return
87 81
88 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path) 82 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
89 isolated_abs_path = os.path.join( 83 isolated_abs_path = os.path.join(
90 product_dir, '%s.isolated' % test_suite) 84 product_dir, '%s.isolated' % suite_name)
91 assert os.path.exists(isolate_abs_path) 85 assert os.path.exists(isolate_abs_path)
92 isolate_cmd = [ 86 isolate_cmd = [
93 'python', _ISOLATE_SCRIPT, 87 'python', _ISOLATE_SCRIPT,
94 'remap', 88 'remap',
95 '--isolate', isolate_abs_path, 89 '--isolate', isolate_abs_path,
96 '--isolated', isolated_abs_path, 90 '--isolated', isolated_abs_path,
97 '-V', 'PRODUCT_DIR=%s' % product_dir, 91 '-V', 'PRODUCT_DIR=%s' % product_dir,
98 '-V', 'OS=android', 92 '-V', 'OS=android',
99 '--outdir', constants.ISOLATE_DEPS_DIR, 93 '--outdir', constants.ISOLATE_DEPS_DIR,
100 ] 94 ]
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
141 135
142 # Move everything in PRODUCT_DIR to top level. 136 # Move everything in PRODUCT_DIR to top level.
143 deps_product_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'out', build_type) 137 deps_product_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'out', build_type)
144 if os.path.isdir(deps_product_dir): 138 if os.path.isdir(deps_product_dir):
145 for p in os.listdir(deps_product_dir): 139 for p in os.listdir(deps_product_dir):
146 shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR) 140 shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR)
147 os.rmdir(deps_product_dir) 141 os.rmdir(deps_product_dir)
148 os.rmdir(os.path.join(constants.ISOLATE_DEPS_DIR, 'out')) 142 os.rmdir(os.path.join(constants.ISOLATE_DEPS_DIR, 'out'))
149 143
150 144
151 def _FullyQualifiedTestSuites(exe, option_test_suite, build_type): 145 def _FullyQualifiedTestSuite(use_exe_test_runner, suite_name, build_type):
152 """Get a list of absolute paths to test suite targets. 146 """Get a list of absolute paths to test suite targets.
153 147
154 Args: 148 Args:
155 exe: if True, use the executable-based test runner. 149 use_exe_test_runner: If True, use the executable-based test runner.
156 option_test_suite: the test_suite specified as an option. 150 suite_name: The suite name specified on the command line.
157 build_type: 'Release' or 'Debug'. 151 build_type: 'Release' or 'Debug'.
158 152
159 Returns: 153 Returns:
160 A list of tuples containing the suite and absolute path. 154 A tuple containing the suite and absolute path.
161 Ex. ('content_unittests', 155 Ex. ('content_unittests',
162 '/tmp/chrome/src/out/Debug/content_unittests_apk/' 156 '/tmp/chrome/src/out/Debug/content_unittests_apk/'
163 'content_unittests-debug.apk') 157 'content_unittests-debug.apk')
164 158
165 Raises: 159 Raises:
166 Exception: If test suite not found. 160 Exception: If test suite not found.
167 """ 161 """
168 def GetQualifiedSuite(suite): 162 if use_exe_test_runner:
169 if suite.is_suite_exe: 163 relpath = suite_name
170 relpath = suite.name 164 else:
171 else: 165 relpath = os.path.join(suite_name + '_apk', suite_name + '-debug.apk')
172 # out/(Debug|Release)/$SUITE_apk/$SUITE-debug.apk 166 suite_path = os.path.join(cmd_helper.OutDirectory.get(), build_type, relpath)
173 relpath = os.path.join(suite.name + '_apk', suite.name + '-debug.apk')
174 return suite.name, os.path.join(test_suite_dir, relpath)
175 167
176 test_suite_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type) 168 if not os.path.exists(suite_path):
177 if option_test_suite: 169 raise Exception('Test suite %s not found in %s.\n'
178 all_test_suites = [gtest_config.Suite(exe, option_test_suite)] 170 'Supported test suites:\n %s\n'
179 else: 171 'Ensure it has been built.\n' %
180 all_test_suites = gtest_config.STABLE_TEST_SUITES 172 (suite_name, suite_path,
173 [s.name for s in gtest_config.STABLE_TEST_SUITES]))
181 174
182 # List of tuples (suite_name, suite_path) 175 return (suite_name, suite_path)
183 qualified_test_suites = map(GetQualifiedSuite, all_test_suites)
184
185 for t, q in qualified_test_suites:
186 if not os.path.exists(q):
187 raise Exception('Test suite %s not found in %s.\n'
188 'Supported test suites:\n %s\n'
189 'Ensure it has been built.\n' %
190 (t, q, [s.name for s in gtest_config.STABLE_TEST_SUITES]))
191 return qualified_test_suites
192 176
193 177
194 def GetTestsFromDevice(runner): 178 def GetTestsFromDevice(runner):
195 """Get a list of tests from a device, excluding disabled tests. 179 """Get a list of tests from a device, excluding disabled tests.
196 180
197 Args: 181 Args:
198 runner: a TestRunner. 182 runner: A TestRunner.
199 Returns: 183 Returns:
200 All non-disabled tests on the device. 184 All non-disabled tests on the device.
201 """ 185 """
202 # The executable/apk needs to be copied before we can call GetAllTests. 186 # The executable/apk needs to be copied before we can call GetAllTests.
203 runner.test_package.Install() 187 runner.test_package.Install()
204 all_tests = runner.test_package.GetAllTests() 188 all_tests = runner.test_package.GetAllTests()
205 # Only includes tests that do not have any match in the disabled list. 189 # Only includes tests that do not have any match in the disabled list.
206 disabled_list = runner.GetDisabledTests() 190 disabled_list = runner.GetDisabledTests()
207 return filter(lambda t: not any([fnmatch.fnmatch(t, disabled_pattern) 191 return filter(lambda t: not any([fnmatch.fnmatch(t, disabled_pattern)
208 for disabled_pattern in disabled_list]), 192 for disabled_pattern in disabled_list]),
209 all_tests) 193 all_tests)
210 194
211 195
212 def GetAllEnabledTests(runner_factory, devices): 196 def GetAllEnabledTests(runner_factory, devices):
213 """Get all enabled tests. 197 """Get all enabled tests.
214 198
215 Obtains a list of enabled tests from the test package on the device, 199 Obtains a list of enabled tests from the test package on the device,
216 then filters it again using the disabled list on the host. 200 then filters it again using the disabled list on the host.
217 201
218 Args: 202 Args:
219 runner_factory: callable that takes a devices and returns a TestRunner. 203 runner_factory: Callable that takes device and shard_index and returns
220 devices: list of devices. 204 a TestRunner.
205 devices: A list of device ids.
221 206
222 Returns: 207 Returns:
223 List of all enabled tests. 208 List of all enabled tests.
224 209
225 Raises: 210 Raises:
226 Exception: If no devices available. 211 Exception: If no devices available.
227 """ 212 """
228 for device in devices: 213 for device in devices:
229 try: 214 try:
230 logging.info('Obtaining tests from %s', device) 215 logging.info('Obtaining tests from %s', device)
231 runner = runner_factory(device, 0) 216 runner = runner_factory(device, 0)
232 return GetTestsFromDevice(runner) 217 return GetTestsFromDevice(runner)
233 except Exception as e: 218 except Exception as e:
234 logging.warning('Failed obtaining tests from %s with exception: %s', 219 logging.warning('Failed obtaining tests from %s with exception: %s',
235 device, e) 220 device, e)
236 raise Exception('No device available to get the list of tests.') 221 raise Exception('No device available to get the list of tests.')
237 222
238 223
239 def _RunATestSuite(options, suite_name): 224 def Setup(use_exe_test_runner, suite_name, test_arguments, timeout,
240 """Run a single test suite. 225 cleanup_test_files, tool, build_type, webkit, push_deps,
241 226 gtest_filter):
242 Helper for Dispatch() to allow stop/restart of the emulator across 227 """Create the test runner factory and tests.
243 test bundles. If using the emulator, we start it on entry and stop
244 it on exit.
245 228
246 Args: 229 Args:
247 options: options for running the tests. 230 use_exe_test_runner: If True, use the executable-based test runner.
248 suite_name: name of the test suite being run. 231 suite_name: The suite name specified on the command line.
232 test_arguments: Additional arguments to pass to the test binary.
233 timeout: Timeout for each test.
234 cleanup_test_files: Whether or not to cleanup test files on device.
235 tool: Name of the Valgrind tool.
236 build_type: 'Release' or 'Debug'.
237 webkit: Whether the suite is being run from a WebKit checkout.
238 push_deps: If True, push all dependencies to the device.
239 gtest_filter: Filter for tests.
249 240
250 Returns: 241 Returns:
251 A tuple of (base_test_result.TestRunResult object, exit code). 242 A tuple of (TestRunnerFactory, tests).
243 """
252 244
253 Raises:
254 Exception: For various reasons including device failure or failing to reset
255 the test server port.
256 """
257 attached_devices = []
258 buildbot_emulators = []
259
260 if options.use_emulator:
261 buildbot_emulators = emulator.LaunchEmulators(options.emulator_count,
262 options.abi,
263 wait_for_boot=True)
264 attached_devices = [e.device for e in buildbot_emulators]
265 elif options.test_device:
266 attached_devices = [options.test_device]
267 else:
268 attached_devices = android_commands.GetAttachedDevices()
269
270 if not attached_devices:
271 raise Exception('A device must be attached and online.')
272
273 # Reset the test port allocation. It's important to do it before starting
274 # to dispatch any tests.
275 if not ports.ResetTestServerPortAllocation(): 245 if not ports.ResetTestServerPortAllocation():
276 raise Exception('Failed to reset test server port.') 246 raise Exception('Failed to reset test server port.')
277 247
278 _GenerateDepsDirUsingIsolate(suite_name, options.build_type) 248 suite_path = _FullyQualifiedTestSuite(use_exe_test_runner,
249 suite_name, build_type)
250 # TODO(gkanwar): This breaks the abstraction of having test_dispatcher.py deal
251 # entirely with the devices. Can we do this another way?
252 attached_devices = android_commands.GetAttachedDevices()
279 253
254 deps_dir = _GenerateDepsDirUsingIsolate(suite_name, build_type)
280 # Constructs a new TestRunner with the current options. 255 # Constructs a new TestRunner with the current options.
281 def RunnerFactory(device, shard_index): 256 def TestRunnerFactory(device, shard_index):
282 return test_runner.TestRunner( 257 return test_runner.TestRunner(
283 device, 258 device,
284 options.test_suite, 259 suite_path,
285 options.test_arguments, 260 test_arguments,
286 options.timeout, 261 timeout,
287 options.cleanup_test_files, 262 cleanup_test_files,
288 options.tool, 263 tool,
289 options.build_type, 264 build_type,
290 options.webkit, 265 webkit,
291 options.push_deps, 266 push_deps,
292 constants.GTEST_TEST_PACKAGE_NAME, 267 constants.GTEST_TEST_PACKAGE_NAME,
293 constants.GTEST_TEST_ACTIVITY_NAME, 268 constants.GTEST_TEST_ACTIVITY_NAME,
294 constants.GTEST_COMMAND_LINE_FILE) 269 constants.GTEST_COMMAND_LINE_FILE,
270 deps_dir=deps_dir)
295 271
296 # Get tests and split them up based on the number of devices. 272 # Get tests and split them up based on the number of devices.
297 if options.test_filter: 273 # TODO(gkanwar): Sharding shouldn't happen here.
298 all_tests = [t for t in options.test_filter.split(':') if t] 274 if gtest_filter:
275 all_tests = [t for t in gtest_filter.split(':') if t]
299 else: 276 else:
300 all_tests = GetAllEnabledTests(RunnerFactory, attached_devices) 277 all_tests = GetAllEnabledTests(TestRunnerFactory, attached_devices)
301 num_devices = len(attached_devices) 278 num_devices = len(attached_devices)
302 tests = [':'.join(all_tests[i::num_devices]) for i in xrange(num_devices)] 279 tests = [':'.join(all_tests[i::num_devices]) for i in xrange(num_devices)]
303 tests = [t for t in tests if t] 280 tests = [t for t in tests if t]
304 281
305 # Run tests.
306 test_results, exit_code = shard.ShardAndRunTests(
307 RunnerFactory, attached_devices, tests, options.build_type,
308 test_timeout=None, num_retries=options.num_retries)
309
310 report_results.LogFull(
311 results=test_results,
312 test_type='Unit test',
313 test_package=suite_name,
314 build_type=options.build_type,
315 flakiness_server=options.flakiness_dashboard_server)
316
317 if os.path.isdir(constants.ISOLATE_DEPS_DIR): 282 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
318 shutil.rmtree(constants.ISOLATE_DEPS_DIR) 283 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
319 284
320 for buildbot_emulator in buildbot_emulators: 285 return (TestRunnerFactory, tests)
321 buildbot_emulator.Shutdown()
322
323 return (test_results, exit_code)
324
325
326 def _ListTestSuites():
327 """Display a list of available test suites."""
328 print 'Available test suites are:'
329 for test_suite in gtest_config.STABLE_TEST_SUITES:
330 print test_suite
331
332
333 def Dispatch(options):
334 """Dispatches the tests, sharding if possible.
335
336 If options.use_emulator is True, all tests will be run in new emulator
337 instance.
338
339 Args:
340 options: options for running the tests.
341
342 Returns:
343 base_test_result.TestRunResults object with the results of running the tests
344 """
345 results = base_test_result.TestRunResults()
346
347 if options.test_suite == 'help':
348 _ListTestSuites()
349 return (results, 0)
350
351 if options.use_xvfb:
352 framebuffer = xvfb.Xvfb()
353 framebuffer.Start()
354
355 all_test_suites = _FullyQualifiedTestSuites(options.exe, options.test_suite,
356 options.build_type)
357 exit_code = 0
358 for suite_name, suite_path in all_test_suites:
359 # Give each test suite its own copy of options.
360 test_options = copy.deepcopy(options)
361 test_options.test_suite = suite_path
362 test_results, test_exit_code = _RunATestSuite(test_options, suite_name)
363 results.AddTestRunResults(test_results)
364 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
365 exit_code = test_exit_code
366
367 if options.use_xvfb:
368 framebuffer.Stop()
369
370 return (results, exit_code)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698