setup.py revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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
7import fnmatch
8import glob
9import logging
10import os
11import shutil
12import sys
13
14from pylib import android_commands
15from pylib import cmd_helper
16from pylib import constants
17from pylib import ports
18
19import test_package_apk
20import test_package_exe
21import test_runner
22
23sys.path.insert(0,
24                os.path.join(constants.DIR_SOURCE_ROOT, 'build', 'util', 'lib',
25                             'common'))
26import unittest_util
27
28
29_ISOLATE_FILE_PATHS = {
30    'base_unittests': 'base/base_unittests.isolate',
31    'breakpad_unittests': 'breakpad/breakpad_unittests.isolate',
32    'cc_perftests': 'cc/cc_perftests.isolate',
33    'components_unittests': 'components/components_unittests.isolate',
34    'content_browsertests': 'content/content_browsertests.isolate',
35    'content_unittests': 'content/content_unittests.isolate',
36    'media_unittests': 'media/media_unittests.isolate',
37    'net_unittests': 'net/net_unittests.isolate',
38    'ui_unittests': 'ui/ui_unittests.isolate',
39    'unit_tests': 'chrome/unit_tests.isolate',
40    'webkit_unit_tests':
41      'third_party/WebKit/Source/web/WebKitUnitTests.isolate',
42}
43
44# Paths relative to third_party/webrtc/ (kept separate for readability).
45_WEBRTC_ISOLATE_FILE_PATHS = {
46    'audio_decoder_unittests':
47      'modules/audio_coding/neteq4/audio_decoder_unittests.isolate',
48    'common_audio_unittests': 'common_audio/common_audio_unittests.isolate',
49    'common_video_unittests': 'common_video/common_video_unittests.isolate',
50    'metrics_unittests': 'test/metrics_unittests.isolate',
51    'modules_tests': 'modules/modules_tests.isolate',
52    'modules_unittests': 'modules/modules_unittests.isolate',
53    'neteq_unittests': 'modules/audio_coding/neteq/neteq_unittests.isolate',
54    'system_wrappers_unittests':
55      'system_wrappers/source/system_wrappers_unittests.isolate',
56    'test_support_unittests': 'test/test_support_unittests.isolate',
57    'tools_unittests': 'tools/tools_unittests.isolate',
58    'video_engine_core_unittests':
59      'video_engine/video_engine_core_unittests.isolate',
60    'voice_engine_unittests': 'voice_engine/voice_engine_unittests.isolate',
61}
62
63# Append the WebRTC tests with the full path from Chromium's src/ root.
64for test,isolate_path in _WEBRTC_ISOLATE_FILE_PATHS.items():
65  _ISOLATE_FILE_PATHS[test] = 'third_party/webrtc/%s' % isolate_path
66
67# Used for filtering large data deps at a finer grain than what's allowed in
68# isolate files since pushing deps to devices is expensive.
69# Wildcards are allowed.
70_DEPS_EXCLUSION_LIST = [
71    'chrome/test/data/extensions/api_test',
72    'chrome/test/data/extensions/secure_shell',
73    'chrome/test/data/firefox*',
74    'chrome/test/data/gpu',
75    'chrome/test/data/image_decoding',
76    'chrome/test/data/import',
77    'chrome/test/data/page_cycler',
78    'chrome/test/data/perf',
79    'chrome/test/data/pyauto_private',
80    'chrome/test/data/safari_import',
81    'chrome/test/data/scroll',
82    'chrome/test/data/third_party',
83    'third_party/hunspell_dictionaries/*.dic',
84    # crbug.com/258690
85    'webkit/data/bmp_decoder',
86    'webkit/data/ico_decoder',
87]
88
89_ISOLATE_SCRIPT = os.path.join(
90    constants.DIR_SOURCE_ROOT, 'tools', 'swarm_client', 'isolate.py')
91
92
93def _GenerateDepsDirUsingIsolate(suite_name):
94  """Generate the dependency dir for the test suite using isolate.
95
96  Args:
97    suite_name: Name of the test suite (e.g. base_unittests).
98  """
99  product_dir = os.path.join(cmd_helper.OutDirectory.get(),
100      constants.GetBuildType())
101  assert os.path.isabs(product_dir)
102
103  if os.path.isdir(constants.ISOLATE_DEPS_DIR):
104    shutil.rmtree(constants.ISOLATE_DEPS_DIR)
105
106  isolate_rel_path = _ISOLATE_FILE_PATHS.get(suite_name)
107  if not isolate_rel_path:
108    logging.info('Did not find an isolate file for the test suite.')
109    return
110
111  isolate_abs_path = os.path.join(constants.DIR_SOURCE_ROOT, isolate_rel_path)
112  isolated_abs_path = os.path.join(
113      product_dir, '%s.isolated' % suite_name)
114  assert os.path.exists(isolate_abs_path)
115  isolate_cmd = [
116      'python', _ISOLATE_SCRIPT,
117      'remap',
118      '--isolate', isolate_abs_path,
119      '--isolated', isolated_abs_path,
120      '-V', 'PRODUCT_DIR=%s' % product_dir,
121      '-V', 'OS=android',
122      '--outdir', constants.ISOLATE_DEPS_DIR,
123  ]
124  assert not cmd_helper.RunCmd(isolate_cmd)
125
126  # We're relying on the fact that timestamps are preserved
127  # by the remap command (hardlinked). Otherwise, all the data
128  # will be pushed to the device once we move to using time diff
129  # instead of md5sum. Perform a sanity check here.
130  for root, _, filenames in os.walk(constants.ISOLATE_DEPS_DIR):
131    if filenames:
132      linked_file = os.path.join(root, filenames[0])
133      orig_file = os.path.join(
134          constants.DIR_SOURCE_ROOT,
135          os.path.relpath(linked_file, constants.ISOLATE_DEPS_DIR))
136      if os.stat(linked_file).st_ino == os.stat(orig_file).st_ino:
137        break
138      else:
139        raise Exception('isolate remap command did not use hardlinks.')
140
141  # Delete excluded files as defined by _DEPS_EXCLUSION_LIST.
142  old_cwd = os.getcwd()
143  try:
144    os.chdir(constants.ISOLATE_DEPS_DIR)
145    excluded_paths = [x for y in _DEPS_EXCLUSION_LIST for x in glob.glob(y)]
146    if excluded_paths:
147      logging.info('Excluding the following from dependency list: %s',
148                   excluded_paths)
149    for p in excluded_paths:
150      if os.path.isdir(p):
151        shutil.rmtree(p)
152      else:
153        os.remove(p)
154  finally:
155    os.chdir(old_cwd)
156
157  # On Android, all pak files need to be in the top-level 'paks' directory.
158  paks_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'paks')
159  os.mkdir(paks_dir)
160  for root, _, filenames in os.walk(os.path.join(constants.ISOLATE_DEPS_DIR,
161                                                 'out')):
162    for filename in fnmatch.filter(filenames, '*.pak'):
163      shutil.move(os.path.join(root, filename), paks_dir)
164
165  # Move everything in PRODUCT_DIR to top level.
166  deps_product_dir = os.path.join(constants.ISOLATE_DEPS_DIR, 'out',
167      constants.GetBuildType())
168  if os.path.isdir(deps_product_dir):
169    for p in os.listdir(deps_product_dir):
170      shutil.move(os.path.join(deps_product_dir, p), constants.ISOLATE_DEPS_DIR)
171    os.rmdir(deps_product_dir)
172    os.rmdir(os.path.join(constants.ISOLATE_DEPS_DIR, 'out'))
173
174
175def _GetDisabledTestsFilterFromFile(suite_name):
176  """Returns a gtest filter based on the *_disabled file.
177
178  Args:
179    suite_name: Name of the test suite (e.g. base_unittests).
180
181  Returns:
182    A gtest filter which excludes disabled tests.
183    Example: '*-StackTrace.*:StringPrintfTest.StringPrintfMisc'
184  """
185  filter_file_path = os.path.join(
186      os.path.abspath(os.path.dirname(__file__)),
187      'filter', '%s_disabled' % suite_name)
188
189  if not filter_file_path or not os.path.exists(filter_file_path):
190    logging.info('No filter file found at %s', filter_file_path)
191    return '*'
192
193  filters = [x for x in [x.strip() for x in file(filter_file_path).readlines()]
194             if x and x[0] != '#']
195  disabled_filter = '*-%s' % ':'.join(filters)
196  logging.info('Applying filter "%s" obtained from %s',
197               disabled_filter, filter_file_path)
198  return disabled_filter
199
200
201def _GetTestsFromDevice(runner_factory, devices):
202  """Get a list of tests from a device.
203
204  Args:
205    runner_factory: Callable that takes device and shard_index and returns
206        a TestRunner.
207    devices: A list of device ids.
208
209  Returns:
210    All the tests in the test suite.
211  """
212  for device in devices:
213    try:
214      logging.info('Obtaining tests from %s', device)
215      return runner_factory(device, 0).GetAllTests()
216    except (android_commands.errors.WaitForResponseTimedOutError,
217            android_commands.errors.DeviceUnresponsiveError), e:
218      logging.warning('Failed obtaining tests from %s with exception: %s',
219                      device, e)
220  raise Exception('No device available to get the list of tests.')
221
222
223def _FilterTestsUsingPrefixes(all_tests, pre=False, manual=False):
224  """Removes tests with disabled prefixes.
225
226  Args:
227    all_tests: List of tests to filter.
228    pre: If True, include tests with PRE_ prefix.
229    manual: If True, include tests with MANUAL_ prefix.
230
231  Returns:
232    List of tests remaining.
233  """
234  filtered_tests = []
235  filter_prefixes = ['DISABLED_', 'FLAKY_', 'FAILS_']
236
237  if not pre:
238    filter_prefixes.append('PRE_')
239
240  if not manual:
241    filter_prefixes.append('MANUAL_')
242
243  for t in all_tests:
244    test_case, test = t.split('.', 1)
245    if not any([test_case.startswith(prefix) or test.startswith(prefix) for
246                prefix in filter_prefixes]):
247      filtered_tests.append(t)
248  return filtered_tests
249
250
251def _GetTestsFiltered(suite_name, gtest_filter, runner_factory, devices):
252  """Get all tests in the suite and filter them.
253
254  Obtains a list of tests from the test package on the device, and
255  applies the following filters in order:
256    1. Remove tests with disabled prefixes.
257    2. Remove tests specified in the *_disabled files in the 'filter' dir
258    3. Applies |gtest_filter|.
259
260  Args:
261    suite_name: Name of the test suite (e.g. base_unittests).
262    gtest_filter: A filter including negative and/or positive patterns.
263    runner_factory: callable that takes a device and index and returns a
264      TestRunner object.
265    devices: List of devices.
266
267  Returns:
268    List of tests remaining.
269  """
270  tests = _GetTestsFromDevice(runner_factory, devices)
271  tests = _FilterTestsUsingPrefixes(
272      tests, bool(gtest_filter), bool(gtest_filter))
273  tests = unittest_util.FilterTestNames(
274      tests, _GetDisabledTestsFilterFromFile(suite_name))
275
276  if gtest_filter:
277    tests = unittest_util.FilterTestNames(tests, gtest_filter)
278
279  return tests
280
281
282def Setup(test_options, devices):
283  """Create the test runner factory and tests.
284
285  Args:
286    test_options: A GTestOptions object.
287    devices: A list of attached devices.
288
289  Returns:
290    A tuple of (TestRunnerFactory, tests).
291  """
292
293  if not ports.ResetTestServerPortAllocation():
294    raise Exception('Failed to reset test server port.')
295
296  test_package = test_package_apk.TestPackageApk(test_options.suite_name)
297  if not os.path.exists(test_package.suite_path):
298    test_package = test_package_exe.TestPackageExecutable(
299        test_options.suite_name)
300    if not os.path.exists(test_package.suite_path):
301      raise Exception(
302          'Did not find %s target. Ensure it has been built.'
303          % test_options.suite_name)
304  logging.warning('Found target %s', test_package.suite_path)
305
306  _GenerateDepsDirUsingIsolate(test_options.suite_name)
307
308  # Constructs a new TestRunner with the current options.
309  def TestRunnerFactory(device, shard_index):
310    return test_runner.TestRunner(
311        test_options,
312        device,
313        test_package)
314
315  tests = _GetTestsFiltered(test_options.suite_name, test_options.gtest_filter,
316                            TestRunnerFactory, devices)
317  # Coalesce unit tests into a single test per device
318  if test_options.suite_name != 'content_browsertests':
319    num_devices = len(devices)
320    tests = [':'.join(tests[i::num_devices]) for i in xrange(num_devices)]
321    tests = [t for t in tests if t]
322
323  return (TestRunnerFactory, tests)
324