[SyncFS] Build indexes from FileTracker entries on disk.
[chromium-blink-merge.git] / native_client_sdk / src / build_tools / test_projects.py
blob703a33a41af93070891663ab6323895d916fb6ab
1 #!/usr/bin/env python
2 # Copyright (c) 2013 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 import optparse
7 import os
8 import subprocess
9 import sys
10 import time
12 import build_projects
13 import build_version
14 import buildbot_common
15 import parse_dsc
17 from build_paths import OUT_DIR, SRC_DIR, SDK_SRC_DIR, SCRIPT_DIR
19 sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
20 import getos
21 platform = getos.GetPlatform()
23 # TODO(binji): ugly hack -- can I get the browser in a cleaner way?
24 sys.path.append(os.path.join(SRC_DIR, 'chrome', 'test', 'nacl_test_injection'))
25 import find_chrome
26 browser_path = find_chrome.FindChrome(SRC_DIR, ['Debug', 'Release'])
29 pepper_ver = str(int(build_version.ChromeMajorVersion()))
30 pepperdir = os.path.join(OUT_DIR, 'pepper_' + pepper_ver)
32 browser_tester_py = os.path.join(SRC_DIR, 'ppapi', 'native_client', 'tools',
33 'browser_tester', 'browser_tester.py')
36 ALL_CONFIGS = ['Debug', 'Release']
37 ALL_TOOLCHAINS = ['newlib', 'glibc', 'pnacl', 'win', 'linux', 'mac']
39 # Values you can filter by:
40 # name: The name of the test. (e.g. "pi_generator")
41 # config: See ALL_CONFIGS above.
42 # toolchain: See ALL_TOOLCHAINS above.
43 # platform: mac/win/linux.
45 # All keys must be matched, but any value that matches in a sequence is
46 # considered a match for that key. For example:
48 # {'name': ('pi_generator', 'input_event'), 'toolchain': ('newlib', 'pnacl')}
50 # Will match 8 tests:
51 # pi_generator.newlib_debug_test
52 # pi_generator.newlib_release_test
53 # input_event.newlib_debug_test
54 # input_event.newlib_release_test
55 # pi_generator.glibc_debug_test
56 # pi_generator.glibc_release_test
57 # input_event.glibc_debug_test
58 # input_event.glibc_release_test
59 DISABLED_TESTS = [
60 # TODO(binji): Disable 3D examples on linux/win/mac. See
61 # http://crbug.com/262379.
62 {'name': 'graphics_3d', 'platform': ('win', 'linux', 'mac')},
63 {'name': 'video_decode', 'platform': ('win', 'linux', 'mac')},
64 # media_stream_audio uses audio input devices which are not supported.
65 {'name': 'media_stream_audio', 'platform': ('win', 'linux', 'mac')},
66 # media_stream_video uses 3D and webcam which are not supported.
67 {'name': 'media_stream_video', 'platform': ('win', 'linux', 'mac')},
68 # TODO(binji): These tests timeout on the trybots because the NEXEs take
69 # more than 40 seconds to load (!). See http://crbug.com/280753
70 {'name': 'nacl_io_test', 'platform': 'win', 'toolchain': 'glibc'},
71 # We don't test "getting_started/part1" because it would complicate the
72 # example.
73 # TODO(binji): figure out a way to inject the testing code without
74 # modifying the example; maybe an extension?
75 {'name': 'part1'},
78 def ValidateToolchains(toolchains):
79 invalid_toolchains = set(toolchains) - set(ALL_TOOLCHAINS)
80 if invalid_toolchains:
81 buildbot_common.ErrorExit('Invalid toolchain(s): %s' % (
82 ', '.join(invalid_toolchains)))
85 def GetServingDirForProject(desc):
86 dest = desc['DEST']
87 path = os.path.join(pepperdir, *dest.split('/'))
88 return os.path.join(path, desc['NAME'])
91 def GetRepoServingDirForProject(desc):
92 # This differs from GetServingDirForProject, because it returns the location
93 # within the Chrome repository of the project, not the "pepperdir".
94 return os.path.dirname(desc['FILEPATH'])
97 def GetExecutableDirForProject(desc, toolchain, config):
98 return os.path.join(GetServingDirForProject(desc), toolchain, config)
101 def GetBrowserTesterCommand(desc, toolchain, config):
102 if browser_path is None:
103 buildbot_common.ErrorExit('Failed to find chrome browser using FindChrome.')
105 args = [
106 sys.executable,
107 browser_tester_py,
108 '--browser_path', browser_path,
109 '--timeout', '30.0', # seconds
110 # Prevent the infobar that shows up when requesting filesystem quota.
111 '--browser_flag', '--unlimited-storage',
112 '--enable_sockets',
113 # Prevent installing a new copy of PNaCl.
114 '--browser_flag', '--disable-component-update',
117 args.extend(['--serving_dir', GetServingDirForProject(desc)])
118 # Fall back on the example directory in the Chromium repo, to find test.js.
119 args.extend(['--serving_dir', GetRepoServingDirForProject(desc)])
120 # If it is not found there, fall back on the dummy one (in this directory.)
121 args.extend(['--serving_dir', SCRIPT_DIR])
123 if toolchain == platform:
124 exe_dir = GetExecutableDirForProject(desc, toolchain, config)
125 ppapi_plugin = os.path.join(exe_dir, desc['NAME'])
126 if platform == 'win':
127 ppapi_plugin += '.dll'
128 else:
129 ppapi_plugin += '.so'
130 args.extend(['--ppapi_plugin', ppapi_plugin])
132 ppapi_plugin_mimetype = 'application/x-ppapi-%s' % config.lower()
133 args.extend(['--ppapi_plugin_mimetype', ppapi_plugin_mimetype])
135 if toolchain == 'pnacl':
136 args.extend(['--browser_flag', '--enable-pnacl'])
138 url = 'index.html'
139 url += '?tc=%s&config=%s&test=true' % (toolchain, config)
140 args.extend(['--url', url])
141 return args
144 def GetBrowserTesterEnv():
145 # browser_tester imports tools/valgrind/memcheck_analyze, which imports
146 # tools/valgrind/common. Well, it tries to, anyway, but instead imports
147 # common from PYTHONPATH first (which on the buildbots, is a
148 # common/__init__.py file...).
150 # Clear the PYTHONPATH so it imports the correct file.
151 env = dict(os.environ)
152 env['PYTHONPATH'] = ''
153 return env
156 def RunTestOnce(desc, toolchain, config):
157 args = GetBrowserTesterCommand(desc, toolchain, config)
158 env = GetBrowserTesterEnv()
159 start_time = time.time()
160 try:
161 subprocess.check_call(args, env=env)
162 result = True
163 except subprocess.CalledProcessError:
164 result = False
165 elapsed = (time.time() - start_time) * 1000
166 return result, elapsed
169 def RunTestNTimes(desc, toolchain, config, times):
170 total_elapsed = 0
171 for _ in xrange(times):
172 result, elapsed = RunTestOnce(desc, toolchain, config)
173 total_elapsed += elapsed
174 if result:
175 # Success, stop retrying.
176 break
177 return result, total_elapsed
180 def RunTestWithGtestOutput(desc, toolchain, config, retry_on_failure_times):
181 test_name = GetTestName(desc, toolchain, config)
182 WriteGtestHeader(test_name)
183 result, elapsed = RunTestNTimes(desc, toolchain, config,
184 retry_on_failure_times)
185 WriteGtestFooter(result, test_name, elapsed)
186 return result
189 def WriteGtestHeader(test_name):
190 print '\n[ RUN ] %s' % test_name
191 sys.stdout.flush()
192 sys.stderr.flush()
195 def WriteGtestFooter(success, test_name, elapsed):
196 sys.stdout.flush()
197 sys.stderr.flush()
198 if success:
199 message = '[ OK ]'
200 else:
201 message = '[ FAILED ]'
202 print '%s %s (%d ms)' % (message, test_name, elapsed)
205 def GetTestName(desc, toolchain, config):
206 return '%s.%s_%s_test' % (desc['NAME'], toolchain, config.lower())
209 def IsTestDisabled(desc, toolchain, config):
210 def AsList(value):
211 if type(value) not in (list, tuple):
212 return [value]
213 return value
215 def TestMatchesDisabled(test_values, disabled_test):
216 for key in test_values:
217 if key in disabled_test:
218 if test_values[key] not in AsList(disabled_test[key]):
219 return False
220 return True
222 test_values = {
223 'name': desc['NAME'],
224 'toolchain': toolchain,
225 'config': config,
226 'platform': platform
229 for disabled_test in DISABLED_TESTS:
230 if TestMatchesDisabled(test_values, disabled_test):
231 return True
232 return False
235 def WriteHorizontalBar():
236 print '-' * 80
239 def WriteBanner(message):
240 WriteHorizontalBar()
241 print message
242 WriteHorizontalBar()
245 def RunAllTestsInTree(tree, toolchains, configs, retry_on_failure_times):
246 tests_run = 0
247 total_tests = 0
248 failed = []
249 disabled = []
251 for _, desc in parse_dsc.GenerateProjects(tree):
252 desc_configs = desc.get('CONFIGS', ALL_CONFIGS)
253 valid_toolchains = set(toolchains) & set(desc['TOOLS'])
254 valid_configs = set(configs) & set(desc_configs)
255 for toolchain in sorted(valid_toolchains):
256 for config in sorted(valid_configs):
257 test_name = GetTestName(desc, toolchain, config)
258 total_tests += 1
259 if IsTestDisabled(desc, toolchain, config):
260 disabled.append(test_name)
261 continue
263 tests_run += 1
264 success = RunTestWithGtestOutput(desc, toolchain, config,
265 retry_on_failure_times)
266 if not success:
267 failed.append(test_name)
269 if failed:
270 WriteBanner('FAILED TESTS')
271 for test in failed:
272 print ' %s failed.' % test
274 if disabled:
275 WriteBanner('DISABLED TESTS')
276 for test in disabled:
277 print ' %s disabled.' % test
279 WriteHorizontalBar()
280 print 'Tests run: %d/%d (%d disabled).' % (
281 tests_run, total_tests, len(disabled))
282 print 'Tests succeeded: %d/%d.' % (tests_run - len(failed), tests_run)
284 success = len(failed) != 0
285 return success
288 def BuildAllTestsInTree(tree, toolchains, configs):
289 for branch, desc in parse_dsc.GenerateProjects(tree):
290 desc_configs = desc.get('CONFIGS', ALL_CONFIGS)
291 valid_toolchains = set(toolchains) & set(desc['TOOLS'])
292 valid_configs = set(configs) & set(desc_configs)
293 for toolchain in sorted(valid_toolchains):
294 for config in sorted(valid_configs):
295 name = '%s/%s' % (branch, desc['NAME'])
296 build_projects.BuildProjectsBranch(pepperdir, name, deps=False,
297 clean=False, config=config,
298 args=['TOOLCHAIN=%s' % toolchain])
301 def GetProjectTree(include):
302 # Everything in src is a library, and cannot be run.
303 exclude = {'DEST': 'src'}
304 try:
305 return parse_dsc.LoadProjectTree(SDK_SRC_DIR, include=include,
306 exclude=exclude)
307 except parse_dsc.ValidationError as e:
308 buildbot_common.ErrorExit(str(e))
311 def main(args):
312 parser = optparse.OptionParser()
313 parser.add_option('-c', '--config',
314 help='Choose configuration to run (Debug or Release). Runs both '
315 'by default', action='append')
316 parser.add_option('-x', '--experimental',
317 help='Run experimental projects', action='store_true')
318 parser.add_option('-t', '--toolchain',
319 help='Run using toolchain. Can be passed more than once.',
320 action='append', default=[])
321 parser.add_option('-d', '--dest',
322 help='Select which destinations (project types) are valid.',
323 action='append')
324 parser.add_option('-b', '--build',
325 help='Build each project before testing.', action='store_true')
326 parser.add_option('--retry-times',
327 help='Number of types to retry on failure (Default: %default)',
328 type='int', default=1)
330 options, args = parser.parse_args(args[1:])
332 if not options.toolchain:
333 options.toolchain = ['newlib', 'glibc', 'pnacl', 'host']
335 if 'host' in options.toolchain:
336 options.toolchain.remove('host')
337 options.toolchain.append(platform)
338 print 'Adding platform: ' + platform
340 ValidateToolchains(options.toolchain)
342 include = {}
343 if options.toolchain:
344 include['TOOLS'] = options.toolchain
345 print 'Filter by toolchain: ' + str(options.toolchain)
346 if not options.experimental:
347 include['EXPERIMENTAL'] = False
348 if options.dest:
349 include['DEST'] = options.dest
350 print 'Filter by type: ' + str(options.dest)
351 if args:
352 include['NAME'] = args
353 print 'Filter by name: ' + str(args)
354 if not options.config:
355 options.config = ALL_CONFIGS
357 project_tree = GetProjectTree(include)
358 if options.build:
359 BuildAllTestsInTree(project_tree, options.toolchain, options.config)
361 return RunAllTestsInTree(project_tree, options.toolchain, options.config,
362 options.retry_times)
365 if __name__ == '__main__':
366 script_name = os.path.basename(sys.argv[0])
367 try:
368 sys.exit(main(sys.argv))
369 except parse_dsc.ValidationError as e:
370 buildbot_common.ErrorExit('%s: %s' % (script_name, e))
371 except KeyboardInterrupt:
372 buildbot_common.ErrorExit('%s: interrupted' % script_name)