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

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: Adds support for cleanup_test_files 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 suite_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type)
169 if suite.is_suite_exe: 163 if use_exe_test_runner:
170 relpath = suite.name 164 relpath = suite_name
171 else: 165 else:
172 # out/(Debug|Release)/$SUITE_apk/$SUITE-debug.apk 166 relpath = os.path.join(suite_name + '_apk', suite_name + '-debug.apk')
173 relpath = os.path.join(suite.name + '_apk', suite.name + '-debug.apk') 167 suite_path = os.path.join(suite_dir, relpath)
174 return suite.name, os.path.join(test_suite_dir, relpath)
175 168
176 test_suite_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type) 169 if not os.path.exists(suite_path):
177 if option_test_suite: 170 raise Exception('Test suite %s not found in %s.\n'
178 all_test_suites = [gtest_config.Suite(exe, option_test_suite)] 171 'Supported test suites:\n %s\n'
179 else: 172 'Ensure it has been built.\n' %
180 all_test_suites = gtest_config.STABLE_TEST_SUITES 173 (suite_name, suite_path,
174 [s.name for s in gtest_config.STABLE_TEST_SUITES]))
181 175
182 # List of tuples (suite_name, suite_path) 176 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 177
193 178
194 def GetTestsFromDevice(runner): 179 def GetTestsFromDevice(runner):
195 """Get a list of tests from a device, excluding disabled tests. 180 """Get a list of tests from a device, excluding disabled tests.
196 181
197 Args: 182 Args:
198 runner: a TestRunner. 183 runner: A TestRunner.
199 Returns: 184 Returns:
200 All non-disabled tests on the device. 185 All non-disabled tests on the device.
201 """ 186 """
202 # The executable/apk needs to be copied before we can call GetAllTests. 187 # The executable/apk needs to be copied before we can call GetAllTests.
203 runner.test_package.Install() 188 runner.test_package.Install()
204 all_tests = runner.test_package.GetAllTests() 189 all_tests = runner.test_package.GetAllTests()
205 # Only includes tests that do not have any match in the disabled list. 190 # Only includes tests that do not have any match in the disabled list.
206 disabled_list = runner.GetDisabledTests() 191 disabled_list = runner.GetDisabledTests()
207 return filter(lambda t: not any([fnmatch.fnmatch(t, disabled_pattern) 192 return filter(lambda t: not any([fnmatch.fnmatch(t, disabled_pattern)
208 for disabled_pattern in disabled_list]), 193 for disabled_pattern in disabled_list]),
209 all_tests) 194 all_tests)
210 195
211 196
212 def GetAllEnabledTests(runner_factory, devices): 197 def GetAllEnabledTests(runner_factory, devices):
213 """Get all enabled tests. 198 """Get all enabled tests.
214 199
215 Obtains a list of enabled tests from the test package on the device, 200 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. 201 then filters it again using the disabled list on the host.
217 202
218 Args: 203 Args:
219 runner_factory: callable that takes a devices and returns a TestRunner. 204 runner_factory: Callable that takes device and shard_index and returns
220 devices: list of devices. 205 a TestRunner.
206 devices: A list of device ids.
221 207
222 Returns: 208 Returns:
223 List of all enabled tests. 209 List of all enabled tests.
224 210
225 Raises: 211 Raises:
226 Exception: If no devices available. 212 Exception: If no devices available.
227 """ 213 """
228 for device in devices: 214 for device in devices:
229 try: 215 try:
230 logging.info('Obtaining tests from %s', device) 216 logging.info('Obtaining tests from %s', device)
231 runner = runner_factory(device, 0) 217 runner = runner_factory(device, 0)
232 return GetTestsFromDevice(runner) 218 return GetTestsFromDevice(runner)
233 except Exception as e: 219 except Exception as e:
234 logging.warning('Failed obtaining tests from %s with exception: %s', 220 logging.warning('Failed obtaining tests from %s with exception: %s',
235 device, e) 221 device, e)
236 raise Exception('No device available to get the list of tests.') 222 raise Exception('No device available to get the list of tests.')
237 223
238 224
239 def _RunATestSuite(options, suite_name): 225 def Setup(use_exe_test_runner, suite_name, test_arguments, timeout,
240 """Run a single test suite. 226 cleanup_test_files, tool, build_type, webkit, push_deps,
241 227 gtest_filter):
242 Helper for Dispatch() to allow stop/restart of the emulator across 228 """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 229
246 Args: 230 Args:
247 options: options for running the tests. 231 use_exe_test_runner: If True, use the executable-based test runner.
248 suite_name: name of the test suite being run. 232 suite_name: The suite name specified on the command line.
233 test_arguments: Additional arguments to pass to the test binary.
234 timeout: Timeout for each test.
235 cleanup_test_files: Whether or not to cleanup test files on device.
236 tool: Name of the Valgrind tool.
237 build_type: 'Release' or 'Debug'.
238 webkit: Whether the suite is being run from a WebKit checkout.
239 push_deps: If True, push all dependencies to the device.
240 gtest_filter: Filter for tests.
249 241
250 Returns: 242 Returns:
251 A tuple of (base_test_result.TestRunResult object, exit code). 243 A tuple of (TestRunnerFactory, tests).
244 """
252 245
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(): 246 if not ports.ResetTestServerPortAllocation():
276 raise Exception('Failed to reset test server port.') 247 raise Exception('Failed to reset test server port.')
277 248
278 _GenerateDepsDirUsingIsolate(suite_name, options.build_type) 249 suite_path = _FullyQualifiedTestSuite(use_exe_test_runner,
250 suite_name, build_type)
251 # TODO(gkanwar): This breaks the abstraction of having test_dispatcher.py deal
252 # entirely with the devices. Can we do this another way?
253 attached_devices = android_commands.GetAttachedDevices()
279 254
255 deps_dir = _GenerateDepsDirUsingIsolate(suite_name, build_type)
280 # Constructs a new TestRunner with the current options. 256 # Constructs a new TestRunner with the current options.
281 def RunnerFactory(device, shard_index): 257 def TestRunnerFactory(device, shard_index):
282 return test_runner.TestRunner( 258 return test_runner.TestRunner(
283 device, 259 device,
284 options.test_suite, 260 suite_path,
285 options.test_arguments, 261 test_arguments,
286 options.timeout, 262 timeout,
287 options.cleanup_test_files, 263 cleanup_test_files,
288 options.tool, 264 tool,
289 options.build_type, 265 build_type,
290 options.webkit, 266 webkit,
291 options.push_deps, 267 push_deps,
292 constants.GTEST_TEST_PACKAGE_NAME, 268 constants.GTEST_TEST_PACKAGE_NAME,
293 constants.GTEST_TEST_ACTIVITY_NAME, 269 constants.GTEST_TEST_ACTIVITY_NAME,
294 constants.GTEST_COMMAND_LINE_FILE) 270 constants.GTEST_COMMAND_LINE_FILE,
271 deps_dir=deps_dir)
295 272
296 # Get tests and split them up based on the number of devices. 273 # Get tests and split them up based on the number of devices.
297 if options.test_filter: 274 # TODO(gkanwar): Sharding shouldn't happen here.
298 all_tests = [t for t in options.test_filter.split(':') if t] 275 if gtest_filter:
276 all_tests = [t for t in gtest_filter.split(':') if t]
299 else: 277 else:
300 all_tests = GetAllEnabledTests(RunnerFactory, attached_devices) 278 all_tests = GetAllEnabledTests(TestRunnerFactory, attached_devices)
301 num_devices = len(attached_devices) 279 num_devices = len(attached_devices)
302 tests = [':'.join(all_tests[i::num_devices]) for i in xrange(num_devices)] 280 tests = [':'.join(all_tests[i::num_devices]) for i in xrange(num_devices)]
303 tests = [t for t in tests if t] 281 tests = [t for t in tests if t]
304 282
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): 283 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
318 shutil.rmtree(constants.ISOLATE_DEPS_DIR) 284 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
319 285
320 for buildbot_emulator in buildbot_emulators: 286 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