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

Side by Side Diff: build/android/pylib/gtest/dispatch.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
(Empty)
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
3 # found in the LICENSE file.
4
5 """Dispatches GTests."""
6
7 import copy
8 import fnmatch
9 import glob
10 import logging
11 import os
12 import shutil
13
14 from pylib import android_commands
15 from pylib import cmd_helper
16 from pylib import constants
17 from pylib import ports
18 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
24 import gtest_config
25 import test_runner
26
27
28 # TODO(frankf): Add more test targets here after making sure we don't
29 # blow up the dependency size (and the world).
30 _ISOLATE_FILE_PATHS = {
31 'base_unittests': 'base/base_unittests.isolate',
32 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
33 'cc_perftests': 'cc/cc_perftests.isolate',
34 'components_unittests': 'components/components_unittests.isolate',
35 'content_browsertests': 'content/content_browsertests.isolate',
36 'content_unittests': 'content/content_unittests.isolate',
37 'media_unittests': 'media/media_unittests.isolate',
38 'modules_unittests': 'third_party/webrtc/modules/modules_unittests.isolate',
39 'net_unittests': 'net/net_unittests.isolate',
40 'ui_unittests': 'ui/ui_unittests.isolate',
41 'unit_tests': 'chrome/unit_tests.isolate',
42 }
43
44 # Used for filtering large data deps at a finer grain than what's allowed in
45 # isolate files since pushing deps to devices is expensive.
46 # Wildcards are allowed.
47 _DEPS_EXCLUSION_LIST = [
48 'chrome/test/data/extensions/api_test',
49 'chrome/test/data/extensions/secure_shell',
50 'chrome/test/data/firefox*',
51 'chrome/test/data/gpu',
52 'chrome/test/data/image_decoding',
53 'chrome/test/data/import',
54 'chrome/test/data/page_cycler',
55 'chrome/test/data/perf',
56 'chrome/test/data/pyauto_private',
57 'chrome/test/data/safari_import',
58 'chrome/test/data/scroll',
59 'chrome/test/data/third_party',
60 'third_party/hunspell_dictionaries/*.dic',
61 # crbug.com/258690
62 'webkit/data/bmp_decoder',
63 'webkit/data/ico_decoder',
64 ]
65
66 _ISOLATE_SCRIPT = os.path.join(
67 constants.DIR_SOURCE_ROOT, 'tools', 'swarm_client', 'isolate.py')
68
69
70 def _GenerateDepsDirUsingIsolate(test_suite, build_type):
71 """Generate the dependency dir for the test suite using isolate.
72
73 Args:
74 test_suite: The test suite basename (e.g. base_unittests).
75 build_type: Release/Debug
76 """
77 product_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type)
78 assert os.path.isabs(product_dir)
79
80 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
81 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
82
83 isolate_rel_path = _ISOLATE_FILE_PATHS.get(test_suite)
84 if not isolate_rel_path:
85 logging.info('Did not find an isolate file for the test suite.')
86 return
87
88 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
89 isolated_abs_path = os.path.join(
90 product_dir, '%s.isolated' % test_suite)
91 assert os.path.exists(isolate_abs_path)
92 isolate_cmd = [
93 'python', _ISOLATE_SCRIPT,
94 'remap',
95 '--isolate', isolate_abs_path,
96 '--isolated', isolated_abs_path,
97 '-V', 'PRODUCT_DIR=%s' % product_dir,
98 '-V', 'OS=android',
99 '--outdir', constants.ISOLATE_DEPS_DIR,
100 ]
101 assert not cmd_helper.RunCmd(isolate_cmd)
102
103 # We're relying on the fact that timestamps are preserved
104 # by the remap command (hardlinked). Otherwise, all the data
105 # will be pushed to the device once we move to using time diff
106 # instead of md5sum. Perform a sanity check here.
107 for root, _, filenames in os.walk(constants.ISOLATE_DEPS_DIR):
108 if filenames:
109 linked_file = os.path.join(root, filenames[0])
110 orig_file = os.path.join(
111 constants.DIR_SOURCE_ROOT,
112 os.path.relpath(linked_file, constants.ISOLATE_DEPS_DIR))
113 if os.stat(linked_file).st_ino == os.stat(orig_file).st_ino:
114 break
115 else:
116 raise Exception('isolate remap command did not use hardlinks.')
117
118 # Delete excluded files as defined by _DEPS_EXCLUSION_LIST.
119 old_cwd = os.getcwd()
120 try:
121 os.chdir(constants.ISOLATE_DEPS_DIR)
122 excluded_paths = [x for y in _DEPS_EXCLUSION_LIST for x in glob.glob(y)]
123 if excluded_paths:
124 logging.info('Excluding the following from dependency list: %s',
125 excluded_paths)
126 for p in excluded_paths:
127 if os.path.isdir(p):
128 shutil.rmtree(p)
129 else:
130 os.remove(p)
131 finally:
132 os.chdir(old_cwd)
133
134 # On Android, all pak files need to be in the top-level 'paks' directory.
135 paks_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'paks')
136 os.mkdir(paks_dir)
137 for root, _, filenames in os.walk(os.path.join(constants.ISOLATE_DEPS_DIR,
138 'out')):
139 for filename in fnmatch.filter(filenames, '*.pak'):
140 shutil.move(os.path.join(root, filename), paks_dir)
141
142 # Move everything in PRODUCT_DIR to top level.
143 deps_product_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'out', build_type)
144 if os.path.isdir(deps_product_dir):
145 for p in os.listdir(deps_product_dir):
146 shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR)
147 os.rmdir(deps_product_dir)
148 os.rmdir(os.path.join(constants.ISOLATE_DEPS_DIR, 'out'))
149
150
151 def _FullyQualifiedTestSuites(exe, option_test_suite, build_type):
152 """Get a list of absolute paths to test suite targets.
153
154 Args:
155 exe: if True, use the executable-based test runner.
156 option_test_suite: the test_suite specified as an option.
157 build_type: 'Release' or 'Debug'.
158
159 Returns:
160 A list of tuples containing the suite and absolute path.
161 Ex. ('content_unittests',
162 '/tmp/chrome/src/out/Debug/content_unittests_apk/'
163 'content_unittests-debug.apk')
164
165 Raises:
166 Exception: If test suite not found.
167 """
168 def GetQualifiedSuite(suite):
169 if suite.is_suite_exe:
170 relpath = suite.name
171 else:
172 # out/(Debug|Release)/$SUITE_apk/$SUITE-debug.apk
173 relpath = os.path.join(suite.name + '_apk', suite.name + '-debug.apk')
174 return suite.name, os.path.join(test_suite_dir, relpath)
175
176 test_suite_dir = os.path.join(cmd_helper.OutDirectory.get(), build_type)
177 if option_test_suite:
178 all_test_suites = [gtest_config.Suite(exe, option_test_suite)]
179 else:
180 all_test_suites = gtest_config.STABLE_TEST_SUITES
181
182 # List of tuples (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
193
194 def GetTestsFromDevice(runner):
195 """Get a list of tests from a device, excluding disabled tests.
196
197 Args:
198 runner: a TestRunner.
199 Returns:
200 All non-disabled tests on the device.
201 """
202 # The executable/apk needs to be copied before we can call GetAllTests.
203 runner.test_package.Install()
204 all_tests = runner.test_package.GetAllTests()
205 # Only includes tests that do not have any match in the disabled list.
206 disabled_list = runner.GetDisabledTests()
207 return filter(lambda t: not any([fnmatch.fnmatch(t, disabled_pattern)
208 for disabled_pattern in disabled_list]),
209 all_tests)
210
211
212 def GetAllEnabledTests(runner_factory, devices):
213 """Get all enabled tests.
214
215 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.
217
218 Args:
219 runner_factory: callable that takes a devices and returns a TestRunner.
220 devices: list of devices.
221
222 Returns:
223 List of all enabled tests.
224
225 Raises:
226 Exception: If no devices available.
227 """
228 for device in devices:
229 try:
230 logging.info('Obtaining tests from %s', device)
231 runner = runner_factory(device, 0)
232 return GetTestsFromDevice(runner)
233 except Exception as e:
234 logging.warning('Failed obtaining tests from %s with exception: %s',
235 device, e)
236 raise Exception('No device available to get the list of tests.')
237
238
239 def _RunATestSuite(options, suite_name):
240 """Run a single test suite.
241
242 Helper for Dispatch() to allow stop/restart of the emulator across
243 test bundles. If using the emulator, we start it on entry and stop
244 it on exit.
245
246 Args:
247 options: options for running the tests.
248 suite_name: name of the test suite being run.
249
250 Returns:
251 A tuple of (base_test_result.TestRunResult object, exit code).
252
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():
276 raise Exception('Failed to reset test server port.')
277
278 _GenerateDepsDirUsingIsolate(suite_name, options.build_type)
279
280 # Constructs a new TestRunner with the current options.
281 def RunnerFactory(device, shard_index):
282 return test_runner.TestRunner(
283 device,
284 options.test_suite,
285 options.test_arguments,
286 options.timeout,
287 options.cleanup_test_files,
288 options.tool,
289 options.build_type,
290 options.webkit,
291 options.push_deps,
292 constants.GTEST_TEST_PACKAGE_NAME,
293 constants.GTEST_TEST_ACTIVITY_NAME,
294 constants.GTEST_COMMAND_LINE_FILE)
295
296 # Get tests and split them up based on the number of devices.
297 if options.test_filter:
298 all_tests = [t for t in options.test_filter.split(':') if t]
299 else:
300 all_tests = GetAllEnabledTests(RunnerFactory, attached_devices)
301 num_devices = len(attached_devices)
302 tests = [':'.join(all_tests[i::num_devices]) for i in xrange(num_devices)]
303 tests = [t for t in tests if t]
304
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):
318 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
319
320 for buildbot_emulator in buildbot_emulators:
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