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

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: Re-adds -f short form to gtest_filter switch 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
« no previous file with comments | « build/android/pylib/gtest/dispatch.py ('k') | build/android/pylib/gtest/test_package.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 import sys 12 import sys
14 13
15 from pylib import android_commands 14 from pylib import android_commands
16 from pylib import cmd_helper 15 from pylib import cmd_helper
17 from pylib import constants 16 from pylib import constants
18 from pylib import ports 17 from pylib import ports
19 from pylib.base import base_test_result 18 from pylib.base import base_test_result
20 from pylib.base import shard
21 from pylib.utils import emulator
22 from pylib.utils import report_results
23 from pylib.utils import xvfb
24 19
25 import gtest_config 20 import gtest_config
26 import test_runner 21 import test_runner
27 22
28 sys.path.insert(0, 23 sys.path.insert(0,
29 os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib')) 24 os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib'))
30 from common import unittest_util 25 from common import unittest_util
31 26
32 27
33 _ISOLATE_FILE_PATHS = { 28 _ISOLATE_FILE_PATHS = {
(...skipping 29 matching lines...) Expand all
63 'third_party/hunspell_dictionaries/*.dic', 58 'third_party/hunspell_dictionaries/*.dic',
64 # crbug.com/258690 59 # crbug.com/258690
65 'webkit/data/bmp_decoder', 60 'webkit/data/bmp_decoder',
66 'webkit/data/ico_decoder', 61 'webkit/data/ico_decoder',
67 ] 62 ]
68 63
69 _ISOLATE_SCRIPT = os.path.join( 64 _ISOLATE_SCRIPT = os.path.join(
70 constants.DIR_SOURCE_ROOT, 'tools', 'swarm_client', 'isolate.py') 65 constants.DIR_SOURCE_ROOT, 'tools', 'swarm_client', 'isolate.py')
71 66
72 67
73 def _GenerateDepsDirUsingIsolate(test_suite, build_type): 68 def _GenerateDepsDirUsingIsolate(suite_name, build_type):
74 """Generate the dependency dir for the test suite using isolate. 69 """Generate the dependency dir for the test suite using isolate.
75 70
76 Args: 71 Args:
77 test_suite: Name of the test suite (e.g. base_unittests). 72 suite_name: Name of the test suite (e.g. base_unittests).
78 build_type: Release/Debug 73 build_type: Release/Debug
79 """ 74 """
80 product_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type) 75 product_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type)
81 assert os.path.isabs(product_dir) 76 assert os.path.isabs(product_dir)
82 77
83 if os.path.isdir(constants.ISOLATE_DEPS_DIR): 78 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
84 shutil.rmtree(constants.ISOLATE_DEPS_DIR) 79 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
85 80
86 isolate_rel_path = _ISOLATE_FILE_PATHS.get(test_suite) 81 isolate_rel_path = _ISOLATE_FILE_PATHS.get(suite_name)
87 if not isolate_rel_path: 82 if not isolate_rel_path:
88 logging.info('Did not find an isolate file for the test suite.') 83 logging.info('Did not find an isolate file for the test suite.')
89 return 84 return
90 85
91 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path) 86 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
92 isolated_abs_path = os.path.join( 87 isolated_abs_path = os.path.join(
93 product_dir, '%s.isolated' % test_suite) 88 product_dir, '%s.isolated' % suite_name)
94 assert os.path.exists(isolate_abs_path) 89 assert os.path.exists(isolate_abs_path)
95 isolate_cmd = [ 90 isolate_cmd = [
96 'python', _ISOLATE_SCRIPT, 91 'python', _ISOLATE_SCRIPT,
97 'remap', 92 'remap',
98 '--isolate', isolate_abs_path, 93 '--isolate', isolate_abs_path,
99 '--isolated', isolated_abs_path, 94 '--isolated', isolated_abs_path,
100 '-V', 'PRODUCT_DIR=%s' % product_dir, 95 '-V', 'PRODUCT_DIR=%s' % product_dir,
101 '-V', 'OS=android', 96 '-V', 'OS=android',
102 '--outdir', constants.ISOLATE_DEPS_DIR, 97 '--outdir', constants.ISOLATE_DEPS_DIR,
103 ] 98 ]
(...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 139
145 # Move everything in PRODUCT_DIR to top level. 140 # Move everything in PRODUCT_DIR to top level.
146 deps_product_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'out', build_type) 141 deps_product_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'out', build_type)
147 if os.path.isdir(deps_product_dir): 142 if os.path.isdir(deps_product_dir):
148 for p in os.listdir(deps_product_dir): 143 for p in os.listdir(deps_product_dir):
149 shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR) 144 shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR)
150 os.rmdir(deps_product_dir) 145 os.rmdir(deps_product_dir)
151 os.rmdir(os.path.join(constants.ISOLATE_DEPS_DIR, 'out')) 146 os.rmdir(os.path.join(constants.ISOLATE_DEPS_DIR, 'out'))
152 147
153 148
154 def _FullyQualifiedTestSuites(exe, option_test_suite, build_type): 149 def _GetSuitePath(use_exe_test_runner, suite_name, build_type):
155 """Get a list of absolute paths to test suite targets. 150 """Get the absolute path to the test suite.
156 151
157 Args: 152 Args:
158 exe: if True, use the executable-based test runner. 153 use_exe_test_runner: If True, use the executable-based test runner.
159 option_test_suite: the test_suite specified as an option. 154 suite_name: The suite name specified on the command line.
160 build_type: 'Release' or 'Debug'. 155 build_type: 'Release' or 'Debug'.
161 156
162 Returns: 157 Returns:
163 A list of tuples containing the suite and absolute path. 158 The absolute path of the given suite.
164 Ex. ('content_unittests', 159 Ex. '/tmp/chrome/src/out/Debug/content_unittests_apk/'
165 '/tmp/chrome/src/out/Debug/content_unittests_apk/' 160 'content_unittests-debug.apk'
166 'content_unittests-debug.apk')
167 161
168 Raises: 162 Raises:
169 Exception: If test suite not found. 163 Exception: If test suite not found.
170 """ 164 """
171 def GetQualifiedSuite(suite): 165 if use_exe_test_runner:
172 if suite.is_suite_exe: 166 relpath = suite_name
173 relpath = suite.name 167 else:
174 else: 168 relpath = os.path.join(suite_name + '_apk', suite_name + '-debug.apk')
175 # out/(Debug|Release)/$SUITE_apk/$SUITE-debug.apk 169 suite_path = os.path.join(cmd_helper.OutDirectory.get(), build_type, relpath)
176 relpath = os.path.join(suite.name + '_apk', suite.name + '-debug.apk')
177 return suite.name, os.path.join(test_suite_dir, relpath)
178 170
179 test_suite_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type) 171 if not os.path.exists(suite_path):
180 if option_test_suite: 172 raise Exception('Test suite %s not found in %s.\n'
181 all_test_suites = [gtest_config.Suite(exe, option_test_suite)] 173 'Supported test suites:\n %s\n'
182 else: 174 'Ensure it has been built.\n' %
183 all_test_suites = gtest_config.STABLE_TEST_SUITES 175 (suite_name, suite_path,
176 [s.name for s in gtest_config.STABLE_TEST_SUITES]))
184 177
185 # List of tuples (suite_name, suite_path) 178 return suite_path
186 qualified_test_suites = map(GetQualifiedSuite, all_test_suites)
187
188 for t, q in qualified_test_suites:
189 if not os.path.exists(q):
190 raise Exception('Test suite %s not found in %s.\n'
191 'Supported test suites:\n %s\n'
192 'Ensure it has been built.\n' %
193 (t, q, [s.name for s in gtest_config.STABLE_TEST_SUITES]))
194 return qualified_test_suites
195 179
196 180
197 def _GetDisabledTestsFilterFromFile(test_suite): 181 def _GetDisabledTestsFilterFromFile(suite_name):
198 """Returns a gtest filter based on the *_disabled file. 182 """Returns a gtest filter based on the *_disabled file.
199 183
200 Args: 184 Args:
201 test_suite: Name of the test suite (e.g. base_unittests). 185 suite_name: Name of the test suite (e.g. base_unittests).
202 186
203 Returns: 187 Returns:
204 A gtest filter which excludes disabled tests. 188 A gtest filter which excludes disabled tests.
205 Example: '*-StackTrace.*:StringPrintfTest.StringPrintfMisc' 189 Example: '*-StackTrace.*:StringPrintfTest.StringPrintfMisc'
206 """ 190 """
207 filter_file_path = os.path.join( 191 filter_file_path = os.path.join(
208 os.path.abspath(os.path.dirname(__file__)), 192 os.path.abspath(os.path.dirname(__file__)),
209 'filter', '%s_disabled' % test_suite) 193 'filter', '%s_disabled' % suite_name)
210 194
211 if not filter_file_path or not os.path.exists(filter_file_path): 195 if not filter_file_path or not os.path.exists(filter_file_path):
212 logging.info('No filter file found at %s', filter_file_path) 196 logging.info('No filter file found at %s', filter_file_path)
213 return '*' 197 return '*'
214 198
215 filters = [x for x in [x.strip() for x in file(filter_file_path).readlines()] 199 filters = [x for x in [x.strip() for x in file(filter_file_path).readlines()]
216 if x and x[0] != '#'] 200 if x and x[0] != '#']
217 disabled_filter = '*-%s' % ':'.join(filters) 201 disabled_filter = '*-%s' % ':'.join(filters)
218 logging.info('Applying filter "%s" obtained from %s', 202 logging.info('Applying filter "%s" obtained from %s',
219 disabled_filter, filter_file_path) 203 disabled_filter, filter_file_path)
220 return disabled_filter 204 return disabled_filter
221 205
222 206
223 def _GetTestsFromDevice(runner_factory, devices): 207 def _GetTestsFromDevice(runner_factory, devices):
224 """Get a list of tests from a device. 208 """Get a list of tests from a device.
225 209
226 Args: 210 Args:
227 runner_factory: callable that takes a device and index and returns a 211 runner_factory: Callable that takes device and shard_index and returns
228 TestRunner object. 212 a TestRunner.
229 devices: List of devices. 213 devices: A list of device ids.
230 214
231 Returns: 215 Returns:
232 All the tests in the test suite. 216 All the tests in the test suite.
233 """ 217 """
234 for device in devices: 218 for device in devices:
235 try: 219 try:
236 logging.info('Obtaining tests from %s', device) 220 logging.info('Obtaining tests from %s', device)
237 runner = runner_factory(device, 0) 221 runner = runner_factory(device, 0)
238 runner.test_package.Install() 222 runner.test_package.Install()
239 return runner.test_package.GetAllTests() 223 return runner.test_package.GetAllTests()
(...skipping 25 matching lines...) Expand all
265 filter_prefixes.append('MANUAL_') 249 filter_prefixes.append('MANUAL_')
266 250
267 for t in all_tests: 251 for t in all_tests:
268 test_case, test = t.split('.', 1) 252 test_case, test = t.split('.', 1)
269 if not any([test_case.startswith(prefix) or test.startswith(prefix) for 253 if not any([test_case.startswith(prefix) or test.startswith(prefix) for
270 prefix in filter_prefixes]): 254 prefix in filter_prefixes]):
271 filtered_tests.append(t) 255 filtered_tests.append(t)
272 return filtered_tests 256 return filtered_tests
273 257
274 258
275 def GetTestsFiltered(test_suite, gtest_filter, runner_factory, devices): 259 def GetTestsFiltered(suite_name, gtest_filter, runner_factory, devices):
276 """Get all tests in the suite and filter them. 260 """Get all tests in the suite and filter them.
277 261
278 Obtains a list of tests from the test package on the device, and 262 Obtains a list of tests from the test package on the device, and
279 applies the following filters in order: 263 applies the following filters in order:
280 1. Remove tests with disabled prefixes. 264 1. Remove tests with disabled prefixes.
281 2. Remove tests specified in the *_disabled files in the 'filter' dir 265 2. Remove tests specified in the *_disabled files in the 'filter' dir
282 3. Applies |gtest_filter|. 266 3. Applies |gtest_filter|.
283 267
284 Args: 268 Args:
285 test_suite: Name of the test suite (e.g. base_unittests). 269 suite_name: Name of the test suite (e.g. base_unittests).
286 gtest_filter: A filter including negative and/or positive patterns. 270 gtest_filter: A filter including negative and/or positive patterns.
287 runner_factory: callable that takes a device and index and returns a 271 runner_factory: callable that takes a device and index and returns a
288 TestRunner object. 272 TestRunner object.
289 devices: List of devices. 273 devices: List of devices.
290 274
291 Returns: 275 Returns:
292 List of tests remaining. 276 List of tests remaining.
293 """ 277 """
294 tests = _GetTestsFromDevice(runner_factory, devices) 278 tests = _GetTestsFromDevice(runner_factory, devices)
295 tests = _FilterTestsUsingPrefixes( 279 tests = _FilterTestsUsingPrefixes(
296 tests, bool(gtest_filter), bool(gtest_filter)) 280 tests, bool(gtest_filter), bool(gtest_filter))
297 tests = unittest_util.FilterTestNames( 281 tests = unittest_util.FilterTestNames(
298 tests, _GetDisabledTestsFilterFromFile(test_suite)) 282 tests, _GetDisabledTestsFilterFromFile(suite_name))
299 283
300 if gtest_filter: 284 if gtest_filter:
301 tests = unittest_util.FilterTestNames(tests, gtest_filter) 285 tests = unittest_util.FilterTestNames(tests, gtest_filter)
302 286
303 return tests 287 return tests
304 288
305 289
306 def _RunATestSuite(options, suite_name): 290 def Setup(use_exe_test_runner, suite_name, test_arguments, timeout,
307 """Run a single test suite. 291 cleanup_test_files, tool, build_type, webkit, push_deps,
308 292 gtest_filter):
309 Helper for Dispatch() to allow stop/restart of the emulator across 293 """Create the test runner factory and tests.
310 test bundles. If using the emulator, we start it on entry and stop
311 it on exit.
312 294
313 Args: 295 Args:
314 options: options for running the tests. 296 use_exe_test_runner: If True, use the executable-based test runner.
315 suite_name: name of the test suite being run. 297 suite_name: The suite name specified on the command line.
298 test_arguments: Additional arguments to pass to the test binary.
299 timeout: Timeout for each test.
300 cleanup_test_files: Whether or not to cleanup test files on device.
301 tool: Name of the Valgrind tool.
302 build_type: 'Release' or 'Debug'.
303 webkit: Whether the suite is being run from a WebKit checkout.
304 push_deps: If True, push all dependencies to the device.
305 gtest_filter: Filter for tests.
316 306
317 Returns: 307 Returns:
318 A tuple of (base_test_result.TestRunResult object, exit code). 308 A tuple of (TestRunnerFactory, tests).
309 """
319 310
320 Raises:
321 Exception: For various reasons including device failure or failing to reset
322 the test server port.
323 """
324 attached_devices = []
325 buildbot_emulators = []
326
327 if options.use_emulator:
328 buildbot_emulators = emulator.LaunchEmulators(options.emulator_count,
329 options.abi,
330 wait_for_boot=True)
331 attached_devices = [e.device for e in buildbot_emulators]
332 elif options.test_device:
333 attached_devices = [options.test_device]
334 else:
335 attached_devices = android_commands.GetAttachedDevices()
336
337 if not attached_devices:
338 raise Exception('A device must be attached and online.')
339
340 # Reset the test port allocation. It's important to do it before starting
341 # to dispatch any tests.
342 if not ports.ResetTestServerPortAllocation(): 311 if not ports.ResetTestServerPortAllocation():
343 raise Exception('Failed to reset test server port.') 312 raise Exception('Failed to reset test server port.')
344 313
345 _GenerateDepsDirUsingIsolate(suite_name, options.build_type) 314 suite_path = _GetSuitePath(use_exe_test_runner, suite_name, build_type)
346 315
316 # TODO(gkanwar): This breaks the abstraction of having test_dispatcher.py deal
317 # entirely with the devices. Can we do this another way?
318 attached_devices = android_commands.GetAttachedDevices()
319
320 deps_dir = _GenerateDepsDirUsingIsolate(suite_name, build_type)
347 # Constructs a new TestRunner with the current options. 321 # Constructs a new TestRunner with the current options.
348 def RunnerFactory(device, shard_index): 322 def TestRunnerFactory(device, shard_index):
349 return test_runner.TestRunner( 323 return test_runner.TestRunner(
350 device, 324 device,
351 options.test_suite, 325 suite_path,
352 options.test_arguments, 326 test_arguments,
353 options.timeout, 327 timeout,
354 options.cleanup_test_files, 328 cleanup_test_files,
355 options.tool, 329 tool,
356 options.build_type, 330 build_type,
357 options.webkit, 331 webkit,
358 options.push_deps, 332 push_deps,
359 constants.GTEST_TEST_PACKAGE_NAME, 333 constants.GTEST_TEST_PACKAGE_NAME,
360 constants.GTEST_TEST_ACTIVITY_NAME, 334 constants.GTEST_TEST_ACTIVITY_NAME,
361 constants.GTEST_COMMAND_LINE_FILE) 335 constants.GTEST_COMMAND_LINE_FILE)
362 336
363 # Get tests and split them up based on the number of devices. 337 # Get tests and split them up based on the number of devices.
364 tests = GetTestsFiltered(suite_name, options.test_filter, 338 tests = GetTestsFiltered(suite_name, gtest_filter,
365 RunnerFactory, attached_devices) 339 TestRunnerFactory, attached_devices)
366 num_devices = len(attached_devices) 340 num_devices = len(attached_devices)
367 tests = [':'.join(tests[i::num_devices]) for i in xrange(num_devices)] 341 tests = [':'.join(tests[i::num_devices]) for i in xrange(num_devices)]
368 tests = [t for t in tests if t] 342 tests = [t for t in tests if t]
369 343
370 # Run tests. 344 return (TestRunnerFactory, tests)
371 test_results, exit_code = shard.ShardAndRunTests(
372 RunnerFactory, attached_devices, tests, options.build_type,
373 test_timeout=None, num_retries=options.num_retries)
374
375 report_results.LogFull(
376 results=test_results,
377 test_type='Unit test',
378 test_package=suite_name,
379 build_type=options.build_type,
380 flakiness_server=options.flakiness_dashboard_server)
381
382 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
383 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
384
385 for buildbot_emulator in buildbot_emulators:
386 buildbot_emulator.Shutdown()
387
388 return (test_results, exit_code)
389
390
391 def _ListTestSuites():
392 """Display a list of available test suites."""
393 print 'Available test suites are:'
394 for test_suite in gtest_config.STABLE_TEST_SUITES:
395 print test_suite
396
397
398 def Dispatch(options):
399 """Dispatches the tests, sharding if possible.
400
401 If options.use_emulator is True, all tests will be run in new emulator
402 instance.
403
404 Args:
405 options: options for running the tests.
406
407 Returns:
408 base_test_result.TestRunResults object with the results of running the tests
409 """
410 results = base_test_result.TestRunResults()
411
412 if options.test_suite == 'help':
413 _ListTestSuites()
414 return (results, 0)
415
416 if options.use_xvfb:
417 framebuffer = xvfb.Xvfb()
418 framebuffer.Start()
419
420 all_test_suites = _FullyQualifiedTestSuites(options.exe, options.test_suite,
421 options.build_type)
422 exit_code = 0
423 for suite_name, suite_path in all_test_suites:
424 # Give each test suite its own copy of options.
425 test_options = copy.deepcopy(options)
426 test_options.test_suite = suite_path
427 test_results, test_exit_code = _RunATestSuite(test_options, suite_name)
428 results.AddTestRunResults(test_results)
429 if test_exit_code and exit_code != constants.ERROR_EXIT_CODE:
430 exit_code = test_exit_code
431
432 if options.use_xvfb:
433 framebuffer.Stop()
434
435 return (results, exit_code)
OLDNEW
« no previous file with comments | « build/android/pylib/gtest/dispatch.py ('k') | build/android/pylib/gtest/test_package.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698