setup.py revision ab8f6f0bd665d3c1ff476eb06c58c42630e462d4
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.
4
5"""Generates test runner factory and tests for GTests."""
6# pylint: disable=W0212
7
8import fnmatch
9import glob
10import logging
11import os
12import shutil
13import sys
14
15from pylib import cmd_helper
16from pylib import constants
17
18from pylib.base import base_test_result
19from pylib.base import test_dispatcher
20from pylib.gtest import test_package_apk
21from pylib.gtest import test_package_exe
22from pylib.gtest import test_runner
23
24sys.path.insert(0,
25                os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
26                             'common'))
27import unittest_util # pylint: disable=F0401
28
29
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',
45    'webkit_unit_tests':
46      'third_party/WebKit/Source/web/WebKitUnitTests.isolate',
47}
48
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',
66    # crbug.com/258690
67    'webkit/data/bmp_decoder',
68    'webkit/data/ico_decoder',
69]
70
71_ISOLATE_SCRIPT = os.path.join(
72    constants.DIR_SOURCE_ROOT, 'tools', 'swarming_client', 'isolate.py')
73
74
75def _GenerateDepsDirUsingIsolate(suite_name, isolate_file_path=None):
76  """Generate the dependency dir for the test suite using isolate.
77
78  Args:
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.
82  """
83  if os.path.isdir(constants.ISOLATE_DEPS_DIR):
84    shutil.rmtree(constants.ISOLATE_DEPS_DIR)
85
86  if isolate_file_path:
87    if os.path.isabs(isolate_file_path):
88      isolate_abs_path = isolate_file_path
89    else:
90      isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT,
91                                      isolate_file_path)
92  else:
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.')
96      return
97    isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
98
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.
104  isolate_cmd = [
105      'python', _ISOLATE_SCRIPT,
106      'remap',
107      '--isolate', isolate_abs_path,
108      '--isolated', isolated_abs_path,
109      '--outdir', constants.ISOLATE_DEPS_DIR,
110
111      '--path-variable', 'DEPTH', constants.DIR_SOURCE_ROOT,
112      '--path-variable', 'PRODUCT_DIR', constants.GetOutDirectory(),
113
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      '--config-variable', 'libpeer_target_type', 'static_library',
122      # TODO(maruel): This may not be always true.
123      '--config-variable', 'target_arch', 'arm',
124      '--config-variable', 'use_openssl', '0',
125      '--config-variable', 'use_ozone', '0',
126  ]
127  assert not cmd_helper.RunCmd(isolate_cmd)
128
129  # We're relying on the fact that timestamps are preserved
130  # by the remap command (hardlinked). Otherwise, all the data
131  # will be pushed to the device once we move to using time diff
132  # instead of md5sum. Perform a sanity check here.
133  for root, _, filenames in os.walk(constants.ISOLATE_DEPS_DIR):
134    if filenames:
135      linked_file = os.path.join(root, filenames[0])
136      orig_file = os.path.join(
137          constants.DIR_SOURCE_ROOT,
138          os.path.relpath(linked_file, constants.ISOLATE_DEPS_DIR))
139      if os.stat(linked_file).st_ino == os.stat(orig_file).st_ino:
140        break
141      else:
142        raise Exception('isolate remap command did not use hardlinks.')
143
144  # Delete excluded files as defined by _DEPS_EXCLUSION_LIST.
145  old_cwd = os.getcwd()
146  try:
147    os.chdir(constants.ISOLATE_DEPS_DIR)
148    excluded_paths = [x for y in _DEPS_EXCLUSION_LIST for x in glob.glob(y)]
149    if excluded_paths:
150      logging.info('Excluding the following from dependency list: %s',
151                   excluded_paths)
152    for p in excluded_paths:
153      if os.path.isdir(p):
154        shutil.rmtree(p)
155      else:
156        os.remove(p)
157  finally:
158    os.chdir(old_cwd)
159
160  # On Android, all pak files need to be in the top-level 'paks' directory.
161  paks_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'paks')
162  os.mkdir(paks_dir)
163
164  deps_out_dir = os.path.join(
165      constants.ISOLATE_DEPS_DIR,
166      os.path.relpath(os.path.join(constants.GetOutDirectory(), os.pardir),
167                      constants.DIR_SOURCE_ROOT))
168  for root, _, filenames in os.walk(deps_out_dir):
169    for filename in fnmatch.filter(filenames, '*.pak'):
170      shutil.move(os.path.join(root, filename), paks_dir)
171
172  # Move everything in PRODUCT_DIR to top level.
173  deps_product_dir = os.path.join(deps_out_dir, constants.GetBuildType())
174  if os.path.isdir(deps_product_dir):
175    for p in os.listdir(deps_product_dir):
176      shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR)
177    os.rmdir(deps_product_dir)
178    os.rmdir(deps_out_dir)
179
180
181def _GetDisabledTestsFilterFromFile(suite_name):
182  """Returns a gtest filter based on the *_disabled file.
183
184  Args:
185    suite_name: Name of the test suite (e.g. base_unittests).
186
187  Returns:
188    A gtest filter which excludes disabled tests.
189    Example: '*-StackTrace.*:StringPrintfTest.StringPrintfMisc'
190  """
191  filter_file_path = os.path.join(
192      os.path.abspath(os.path.dirname(__file__)),
193      'filter', '%s_disabled' % suite_name)
194
195  if not filter_file_path or not os.path.exists(filter_file_path):
196    logging.info('No filter file found at %s', filter_file_path)
197    return '*'
198
199  filters = [x for x in [x.strip() for x in file(filter_file_path).readlines()]
200             if x and x[0] != '#']
201  disabled_filter = '*-%s' % ':'.join(filters)
202  logging.info('Applying filter "%s" obtained from %s',
203               disabled_filter, filter_file_path)
204  return disabled_filter
205
206
207def _GetTests(test_options, test_package, devices):
208  """Get a list of tests.
209
210  Args:
211    test_options: A GTestOptions object.
212    test_package: A TestPackageApk object.
213    devices: A list of attached devices.
214
215  Returns:
216    A list of all the tests in the test suite.
217  """
218  def TestListerRunnerFactory(device, _shard_index):
219    class TestListerRunner(test_runner.TestRunner):
220      #override
221      def PushDataDeps(self):
222        pass
223
224      #override
225      def RunTest(self, _test):
226        result = base_test_result.BaseTestResult(
227            'gtest_list_tests', base_test_result.ResultType.PASS)
228        self.test_package.Install(self.device)
229        result.test_list = self.test_package.GetAllTests(self.device)
230        results = base_test_result.TestRunResults()
231        results.AddResult(result)
232        return results, None
233    return TestListerRunner(test_options, device, test_package)
234
235  results, _no_retry = test_dispatcher.RunTests(
236      ['gtest_list_tests'], TestListerRunnerFactory, devices)
237  tests = []
238  for r in results.GetAll():
239    tests.extend(r.test_list)
240  return tests
241
242
243def _FilterTestsUsingPrefixes(all_tests, pre=False, manual=False):
244  """Removes tests with disabled prefixes.
245
246  Args:
247    all_tests: List of tests to filter.
248    pre: If True, include tests with PRE_ prefix.
249    manual: If True, include tests with MANUAL_ prefix.
250
251  Returns:
252    List of tests remaining.
253  """
254  filtered_tests = []
255  filter_prefixes = ['DISABLED_', 'FLAKY_', 'FAILS_']
256
257  if not pre:
258    filter_prefixes.append('PRE_')
259
260  if not manual:
261    filter_prefixes.append('MANUAL_')
262
263  for t in all_tests:
264    test_case, test = t.split('.', 1)
265    if not any([test_case.startswith(prefix) or test.startswith(prefix) for
266                prefix in filter_prefixes]):
267      filtered_tests.append(t)
268  return filtered_tests
269
270
271def _FilterDisabledTests(tests, suite_name, has_gtest_filter):
272  """Removes disabled tests from |tests|.
273
274  Applies the following filters in order:
275    1. Remove tests with disabled prefixes.
276    2. Remove tests specified in the *_disabled files in the 'filter' dir
277
278  Args:
279    tests: List of tests.
280    suite_name: Name of the test suite (e.g. base_unittests).
281    has_gtest_filter: Whether a gtest_filter is provided.
282
283  Returns:
284    List of tests remaining.
285  """
286  tests = _FilterTestsUsingPrefixes(
287      tests, has_gtest_filter, has_gtest_filter)
288  tests = unittest_util.FilterTestNames(
289      tests, _GetDisabledTestsFilterFromFile(suite_name))
290
291  return tests
292
293
294def Setup(test_options, devices):
295  """Create the test runner factory and tests.
296
297  Args:
298    test_options: A GTestOptions object.
299    devices: A list of attached devices.
300
301  Returns:
302    A tuple of (TestRunnerFactory, tests).
303  """
304  test_package = test_package_apk.TestPackageApk(test_options.suite_name)
305  if not os.path.exists(test_package.suite_path):
306    exe_test_package = test_package_exe.TestPackageExecutable(
307        test_options.suite_name)
308    if not os.path.exists(exe_test_package.suite_path):
309      raise Exception(
310          'Did not find %s target. Ensure it has been built.\n'
311          '(not found at %s or %s)'
312          % (test_options.suite_name,
313             test_package.suite_path,
314             exe_test_package.suite_path))
315    test_package = exe_test_package
316  logging.warning('Found target %s', test_package.suite_path)
317
318  _GenerateDepsDirUsingIsolate(test_options.suite_name,
319                               test_options.isolate_file_path)
320
321  tests = _GetTests(test_options, test_package, devices)
322
323  # Constructs a new TestRunner with the current options.
324  def TestRunnerFactory(device, _shard_index):
325    return test_runner.TestRunner(
326        test_options,
327        device,
328        test_package)
329
330  if test_options.run_disabled:
331    test_options = test_options._replace(
332        test_arguments=('%s --gtest_also_run_disabled_tests' %
333                        test_options.test_arguments))
334  else:
335    tests = _FilterDisabledTests(tests, test_options.suite_name,
336                                 bool(test_options.gtest_filter))
337  if test_options.gtest_filter:
338    tests = unittest_util.FilterTestNames(tests, test_options.gtest_filter)
339
340  # Coalesce unit tests into a single test per device
341  if test_options.suite_name != 'content_browsertests':
342    num_devices = len(devices)
343    tests = [':'.join(tests[i::num_devices]) for i in xrange(num_devices)]
344    tests = [t for t in tests if t]
345
346  return (TestRunnerFactory, tests)
347