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.
5 """Generates test runner factory and tests for GTests."""
6 # pylint: disable=W0212
15 from pylib import cmd_helper
16 from pylib import constants
18 from pylib.base import base_test_result
19 from pylib.base import test_dispatcher
20 from pylib.gtest import test_package_apk
21 from pylib.gtest import test_package_exe
22 from pylib.gtest import test_runner
25 os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
27 import unittest_util # pylint: disable=F0401
30 _ISOLATE_FILE_PATHS = {
31 'base_unittests': 'base/base_unittests.isolate',
32 'blink_heap_unittests':
33 'third_party/WebKit/Source/platform/heap/BlinkHeapUnitTests.isolate',
34 'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
35 'cc_perftests': 'cc/cc_perftests.isolate',
36 'components_unittests': 'components/components_unittests.isolate',
37 'content_browsertests': 'content/content_browsertests.isolate',
38 'content_unittests': 'content/content_unittests.isolate',
39 'media_perftests': 'media/media_perftests.isolate',
40 'media_unittests': 'media/media_unittests.isolate',
41 'net_unittests': 'net/net_unittests.isolate',
42 'sql_unittests': 'sql/sql_unittests.isolate',
43 'ui_unittests': 'ui/base/ui_base_tests.isolate',
44 'unit_tests': 'chrome/unit_tests.isolate',
46 'third_party/WebKit/Source/web/WebKitUnitTests.isolate',
49 # Used for filtering large data deps at a finer grain than what's allowed in
50 # isolate files since pushing deps to devices is expensive.
51 # Wildcards are allowed.
52 _DEPS_EXCLUSION_LIST = [
53 'chrome/test/data/extensions/api_test',
54 'chrome/test/data/extensions/secure_shell',
55 'chrome/test/data/firefox*',
56 'chrome/test/data/gpu',
57 'chrome/test/data/image_decoding',
58 'chrome/test/data/import',
59 'chrome/test/data/page_cycler',
60 'chrome/test/data/perf',
61 'chrome/test/data/pyauto_private',
62 'chrome/test/data/safari_import',
63 'chrome/test/data/scroll',
64 'chrome/test/data/third_party',
65 'third_party/hunspell_dictionaries/*.dic',
67 'webkit/data/bmp_decoder',
68 'webkit/data/ico_decoder',
71 _ISOLATE_SCRIPT = os.path.join(
72 constants.DIR_SOURCE_ROOT, 'tools', 'swarming_client', 'isolate.py')
75 def _GenerateDepsDirUsingIsolate(suite_name, isolate_file_path=None):
76 """Generate the dependency dir for the test suite using isolate.
79 suite_name: Name of the test suite (e.g. base_unittests).
80 isolate_file_path: .isolate file path to use. If there is a default .isolate
81 file path for the suite_name, this will override it.
83 if os.path.isdir(constants.ISOLATE_DEPS_DIR):
84 shutil.rmtree(constants.ISOLATE_DEPS_DIR)
87 if os.path.isabs(isolate_file_path):
88 isolate_abs_path = isolate_file_path
90 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT,
93 isolate_rel_path = _ISOLATE_FILE_PATHS.get(suite_name)
94 if not isolate_rel_path:
95 logging.info('Did not find an isolate file for the test suite.')
97 isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
99 isolated_abs_path = os.path.join(
100 constants.GetOutDirectory(), '%s.isolated' % suite_name)
101 assert os.path.exists(isolate_abs_path), 'Cannot find %s' % isolate_abs_path
102 # This needs to be kept in sync with the cmd line options for isolate.py
103 # in src/build/isolate.gypi.
105 'python', _ISOLATE_SCRIPT,
107 '--isolate', isolate_abs_path,
108 '--isolated', isolated_abs_path,
109 '--outdir', constants.ISOLATE_DEPS_DIR,
111 '--path-variable', 'DEPTH', constants.DIR_SOURCE_ROOT,
112 '--path-variable', 'PRODUCT_DIR', constants.GetOutDirectory(),
114 '--config-variable', 'OS', 'android',
115 '--config-variable', 'CONFIGURATION_NAME', constants.GetBuildType(),
116 '--config-variable', 'asan', '0',
117 '--config-variable', 'chromeos', '0',
118 '--config-variable', 'component', 'static_library',
119 '--config-variable', 'fastbuild', '0',
120 '--config-variable', 'icu_use_data_file_flag', '1',
121 # TODO(maruel): This may not be always true.
122 '--config-variable', 'target_arch', 'arm',
123 '--config-variable', 'use_openssl', '0',
124 '--config-variable', 'use_ozone', '0',
126 assert not cmd_helper.RunCmd(isolate_cmd)
128 # We're relying on the fact that timestamps are preserved
129 # by the remap command (hardlinked). Otherwise, all the data
130 # will be pushed to the device once we move to using time diff
131 # instead of md5sum. Perform a sanity check here.
132 for root, _, filenames in os.walk(constants.ISOLATE_DEPS_DIR):
134 linked_file = os.path.join(root, filenames[0])
135 orig_file = os.path.join(
136 constants.DIR_SOURCE_ROOT,
137 os.path.relpath(linked_file, constants.ISOLATE_DEPS_DIR))
138 if os.stat(linked_file).st_ino == os.stat(orig_file).st_ino:
141 raise Exception('isolate remap command did not use hardlinks.')
143 # Delete excluded files as defined by _DEPS_EXCLUSION_LIST.
144 old_cwd = os.getcwd()
146 os.chdir(constants.ISOLATE_DEPS_DIR)
147 excluded_paths = [x for y in _DEPS_EXCLUSION_LIST for x in glob.glob(y)]
149 logging.info('Excluding the following from dependency list: %s',
151 for p in excluded_paths:
159 # On Android, all pak files need to be in the top-level 'paks' directory.
160 paks_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'paks')
163 deps_out_dir = os.path.join(
164 constants.ISOLATE_DEPS_DIR,
165 os.path.relpath(os.path.join(constants.GetOutDirectory(), os.pardir),
166 constants.DIR_SOURCE_ROOT))
167 for root, _, filenames in os.walk(deps_out_dir):
168 for filename in fnmatch.filter(filenames, '*.pak'):
169 shutil.move(os.path.join(root, filename), paks_dir)
171 # Move everything in PRODUCT_DIR to top level.
172 deps_product_dir = os.path.join(deps_out_dir, constants.GetBuildType())
173 if os.path.isdir(deps_product_dir):
174 for p in os.listdir(deps_product_dir):
175 shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR)
176 os.rmdir(deps_product_dir)
177 os.rmdir(deps_out_dir)
180 def _GetDisabledTestsFilterFromFile(suite_name):
181 """Returns a gtest filter based on the *_disabled file.
184 suite_name: Name of the test suite (e.g. base_unittests).
187 A gtest filter which excludes disabled tests.
188 Example: '*-StackTrace.*:StringPrintfTest.StringPrintfMisc'
190 filter_file_path = os.path.join(
191 os.path.abspath(os.path.dirname(__file__)),
192 'filter', '%s_disabled' % suite_name)
194 if not filter_file_path or not os.path.exists(filter_file_path):
195 logging.info('No filter file found at %s', filter_file_path)
198 filters = [x for x in [x.strip() for x in file(filter_file_path).readlines()]
199 if x and x[0] != '#']
200 disabled_filter = '*-%s' % ':'.join(filters)
201 logging.info('Applying filter "%s" obtained from %s',
202 disabled_filter, filter_file_path)
203 return disabled_filter
206 def _GetTests(test_options, test_package, devices):
207 """Get a list of tests.
210 test_options: A GTestOptions object.
211 test_package: A TestPackageApk object.
212 devices: A list of attached devices.
215 A list of all the tests in the test suite.
217 def TestListerRunnerFactory(device, _shard_index):
218 class TestListerRunner(test_runner.TestRunner):
220 def PushDataDeps(self):
224 def RunTest(self, _test):
225 result = base_test_result.BaseTestResult(
226 'gtest_list_tests', base_test_result.ResultType.PASS)
227 self.test_package.Install(self.device)
228 result.test_list = self.test_package.GetAllTests(self.device)
229 results = base_test_result.TestRunResults()
230 results.AddResult(result)
232 return TestListerRunner(test_options, device, test_package)
234 results, _no_retry = test_dispatcher.RunTests(
235 ['gtest_list_tests'], TestListerRunnerFactory, devices)
237 for r in results.GetAll():
238 tests.extend(r.test_list)
242 def _FilterTestsUsingPrefixes(all_tests, pre=False, manual=False):
243 """Removes tests with disabled prefixes.
246 all_tests: List of tests to filter.
247 pre: If True, include tests with PRE_ prefix.
248 manual: If True, include tests with MANUAL_ prefix.
251 List of tests remaining.
254 filter_prefixes = ['DISABLED_', 'FLAKY_', 'FAILS_']
257 filter_prefixes.append('PRE_')
260 filter_prefixes.append('MANUAL_')
263 test_case, test = t.split('.', 1)
264 if not any([test_case.startswith(prefix) or test.startswith(prefix) for
265 prefix in filter_prefixes]):
266 filtered_tests.append(t)
267 return filtered_tests
270 def _FilterDisabledTests(tests, suite_name, has_gtest_filter):
271 """Removes disabled tests from |tests|.
273 Applies the following filters in order:
274 1. Remove tests with disabled prefixes.
275 2. Remove tests specified in the *_disabled files in the 'filter' dir
278 tests: List of tests.
279 suite_name: Name of the test suite (e.g. base_unittests).
280 has_gtest_filter: Whether a gtest_filter is provided.
283 List of tests remaining.
285 tests = _FilterTestsUsingPrefixes(
286 tests, has_gtest_filter, has_gtest_filter)
287 tests = unittest_util.FilterTestNames(
288 tests, _GetDisabledTestsFilterFromFile(suite_name))
293 def Setup(test_options, devices):
294 """Create the test runner factory and tests.
297 test_options: A GTestOptions object.
298 devices: A list of attached devices.
301 A tuple of (TestRunnerFactory, tests).
303 test_package = test_package_apk.TestPackageApk(test_options.suite_name)
304 if not os.path.exists(test_package.suite_path):
305 exe_test_package = test_package_exe.TestPackageExecutable(
306 test_options.suite_name)
307 if not os.path.exists(exe_test_package.suite_path):
309 'Did not find %s target. Ensure it has been built.\n'
310 '(not found at %s or %s)'
311 % (test_options.suite_name,
312 test_package.suite_path,
313 exe_test_package.suite_path))
314 test_package = exe_test_package
315 logging.warning('Found target %s', test_package.suite_path)
317 _GenerateDepsDirUsingIsolate(test_options.suite_name,
318 test_options.isolate_file_path)
320 tests = _GetTests(test_options, test_package, devices)
322 # Constructs a new TestRunner with the current options.
323 def TestRunnerFactory(device, _shard_index):
324 return test_runner.TestRunner(
329 if test_options.run_disabled:
330 test_options = test_options._replace(
331 test_arguments=('%s --gtest_also_run_disabled_tests' %
332 test_options.test_arguments))
334 tests = _FilterDisabledTests(tests, test_options.suite_name,
335 bool(test_options.gtest_filter))
336 if test_options.gtest_filter:
337 tests = unittest_util.FilterTestNames(tests, test_options.gtest_filter)
339 # Coalesce unit tests into a single test per device
340 if test_options.suite_name != 'content_browsertests':
341 num_devices = len(devices)
342 tests = [':'.join(tests[i::num_devices]) for i in xrange(num_devices)]
343 tests = [t for t in tests if t]
345 return (TestRunnerFactory, tests)