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.
12 import buildbot_common
16 from build_paths import OUT_DIR, SRC_DIR, SDK_SRC_DIR, SCRIPT_DIR
18 sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
20 platform = getos.GetPlatform()
22 # TODO(binji): ugly hack -- can I get the browser in a cleaner way?
23 sys.path.append(os.path.join(SRC_DIR, 'chrome', 'test', 'nacl_test_injection'))
25 browser_path = find_chrome.FindChrome(SRC_DIR, ['Debug', 'Release'])
28 pepper_ver = str(int(build_version.ChromeMajorVersion()))
29 pepperdir = os.path.join(OUT_DIR, 'pepper_' + pepper_ver)
31 browser_tester_py = os.path.join(SRC_DIR, 'ppapi', 'native_client', 'tools',
32 'browser_tester', 'browser_tester.py')
35 ALL_CONFIGS = ['Debug', 'Release']
36 ALL_TOOLCHAINS = ['newlib', 'glibc', 'pnacl', 'win', 'linux', 'mac']
38 # Values you can filter by:
39 # name: The name of the test. (e.g. "pi_generator")
40 # config: See ALL_CONFIGS above.
41 # toolchain: See ALL_TOOLCHAINS above.
42 # platform: mac/win/linux.
44 # All keys must be matched, but any value that matches in a sequence is
45 # considered a match for that key. For example:
47 # {'name': ('pi_generator', 'input_event'), 'toolchain': ('newlib', 'pnacl')}
50 # pi_generator.newlib_debug_test
51 # pi_generator.newlib_release_test
52 # input_event.newlib_debug_test
53 # input_event.newlib_release_test
54 # pi_generator.glibc_debug_test
55 # pi_generator.glibc_release_test
56 # input_event.glibc_debug_test
57 # input_event.glibc_release_test
59 # TODO(binji): Disable 3D examples on linux/win. See
60 # http://crbug.com/262379.
61 {'name': 'graphics_3d', 'platform': ('win', 'linux')},
62 # TODO(binji): These tests timeout on the trybots because the NEXEs take
63 # more than 40 seconds to load (!). See http://crbug.com/280753
64 {'name': 'nacl_io_test', 'platform': 'win', 'toolchain': 'glibc'},
65 # We don't test "getting_started/part1" because it would complicate the
67 # TODO(binji): figure out a way to inject the testing code without
68 # modifying the example; maybe an extension?
72 def ValidateToolchains(toolchains):
73 invalid_toolchains = set(toolchains) - set(ALL_TOOLCHAINS)
74 if invalid_toolchains:
75 buildbot_common.ErrorExit('Invalid toolchain(s): %s' % (
76 ', '.join(invalid_toolchains)))
79 def GetServingDirForProject(desc):
81 path = os.path.join(pepperdir, *dest.split('/'))
82 return os.path.join(path, desc['NAME'])
85 def GetRepoServingDirForProject(desc):
86 # This differs from GetServingDirForProject, because it returns the location
87 # within the Chrome repository of the project, not the "pepperdir".
88 return os.path.dirname(desc['FILEPATH'])
91 def GetExecutableDirForProject(desc, toolchain, config):
92 return os.path.join(GetServingDirForProject(desc), toolchain, config)
95 def GetBrowserTesterCommand(desc, toolchain, config):
96 if browser_path is None:
97 buildbot_common.ErrorExit('Failed to find chrome browser using FindChrome.')
102 '--browser_path', browser_path,
103 '--timeout', '30.0', # seconds
104 # Prevent the infobar that shows up when requesting filesystem quota.
105 '--browser_flag', '--unlimited-storage',
109 args.extend(['--serving_dir', GetServingDirForProject(desc)])
110 # Fall back on the example directory in the Chromium repo, to find test.js.
111 args.extend(['--serving_dir', GetRepoServingDirForProject(desc)])
112 # If it is not found there, fall back on the dummy one (in this directory.)
113 args.extend(['--serving_dir', SCRIPT_DIR])
115 if toolchain == platform:
116 exe_dir = GetExecutableDirForProject(desc, toolchain, config)
117 ppapi_plugin = os.path.join(exe_dir, desc['NAME'])
118 if platform == 'win':
119 ppapi_plugin += '.dll'
121 ppapi_plugin += '.so'
122 args.extend(['--ppapi_plugin', ppapi_plugin])
124 ppapi_plugin_mimetype = 'application/x-ppapi-%s' % config.lower()
125 args.extend(['--ppapi_plugin_mimetype', ppapi_plugin_mimetype])
127 if toolchain == 'pnacl':
128 args.extend(['--browser_flag', '--enable-pnacl'])
131 url += '?tc=%s&config=%s&test=true' % (toolchain, config)
132 args.extend(['--url', url])
136 def GetBrowserTesterEnv():
137 # browser_tester imports tools/valgrind/memcheck_analyze, which imports
138 # tools/valgrind/common. Well, it tries to, anyway, but instead imports
139 # common from PYTHONPATH first (which on the buildbots, is a
140 # common/__init__.py file...).
142 # Clear the PYTHONPATH so it imports the correct file.
143 env = dict(os.environ)
144 env['PYTHONPATH'] = ''
148 def RunTestOnce(desc, toolchain, config):
149 args = GetBrowserTesterCommand(desc, toolchain, config)
150 env = GetBrowserTesterEnv()
151 start_time = time.time()
153 subprocess.check_call(args, env=env)
155 except subprocess.CalledProcessError:
157 elapsed = (time.time() - start_time) * 1000
158 return result, elapsed
161 def RunTestNTimes(desc, toolchain, config, times):
163 for _ in xrange(times):
164 result, elapsed = RunTestOnce(desc, toolchain, config)
165 total_elapsed += elapsed
167 # Success, stop retrying.
169 return result, total_elapsed
172 def RunTestWithGtestOutput(desc, toolchain, config, retry_on_failure_times):
173 test_name = GetTestName(desc, toolchain, config)
174 WriteGtestHeader(test_name)
175 result, elapsed = RunTestNTimes(desc, toolchain, config,
176 retry_on_failure_times)
177 WriteGtestFooter(result, test_name, elapsed)
181 def WriteGtestHeader(test_name):
182 print '\n[ RUN ] %s' % test_name
187 def WriteGtestFooter(success, test_name, elapsed):
193 message = '[ FAILED ]'
194 print '%s %s (%d ms)' % (message, test_name, elapsed)
197 def GetTestName(desc, toolchain, config):
198 return '%s.%s_%s_test' % (desc['NAME'], toolchain, config.lower())
201 def IsTestDisabled(desc, toolchain, config):
203 if type(value) not in (list, tuple):
207 def TestMatchesDisabled(test_values, disabled_test):
208 for key in test_values:
209 if key in disabled_test:
210 if test_values[key] not in AsList(disabled_test[key]):
215 'name': desc['NAME'],
216 'toolchain': toolchain,
221 for disabled_test in DISABLED_TESTS:
222 if TestMatchesDisabled(test_values, disabled_test):
227 def WriteHorizontalBar():
231 def WriteBanner(message):
237 def RunAllTestsInTree(tree, toolchains, configs, retry_on_failure_times):
243 for _, desc in parse_dsc.GenerateProjects(tree):
244 desc_configs = desc.get('CONFIGS', ALL_CONFIGS)
245 valid_toolchains = set(toolchains) & set(desc['TOOLS'])
246 valid_configs = set(configs) & set(desc_configs)
247 for toolchain in sorted(valid_toolchains):
248 for config in sorted(valid_configs):
249 test_name = GetTestName(desc, toolchain, config)
251 if IsTestDisabled(desc, toolchain, config):
252 disabled.append(test_name)
256 success = RunTestWithGtestOutput(desc, toolchain, config,
257 retry_on_failure_times)
259 failed.append(test_name)
262 WriteBanner('FAILED TESTS')
264 print ' %s failed.' % test
267 WriteBanner('DISABLED TESTS')
268 for test in disabled:
269 print ' %s disabled.' % test
272 print 'Tests run: %d/%d (%d disabled).' % (
273 tests_run, total_tests, len(disabled))
274 print 'Tests succeeded: %d/%d.' % (tests_run - len(failed), tests_run)
276 success = len(failed) != 0
280 def GetProjectTree(include):
281 # Everything in src is a library, and cannot be run.
282 exclude = {'DEST': 'src'}
284 return parse_dsc.LoadProjectTree(SDK_SRC_DIR, include=include,
286 except parse_dsc.ValidationError as e:
287 buildbot_common.ErrorExit(str(e))
291 parser = optparse.OptionParser()
292 parser.add_option('-c', '--config',
293 help='Choose configuration to run (Debug or Release). Runs both '
294 'by default', action='append')
295 parser.add_option('-x', '--experimental',
296 help='Run experimental projects', action='store_true')
297 parser.add_option('-t', '--toolchain',
298 help='Run using toolchain. Can be passed more than once.',
299 action='append', default=[])
300 parser.add_option('-d', '--dest',
301 help='Select which destinations (project types) are valid.',
303 parser.add_option('-p', '--project',
304 help='Select which projects are valid.',
306 parser.add_option('--retry-times',
307 help='Number of types to retry on failure (Default: %default)',
308 type='int', default=1)
310 options, args = parser.parse_args(args[1:])
312 parser.error('The -p/--project option is deprecated.\n'
313 'Just use positional paramaters instead.')
315 if not options.toolchain:
316 options.toolchain = ['newlib', 'glibc', 'pnacl', 'host']
318 if 'host' in options.toolchain:
319 options.toolchain.remove('host')
320 options.toolchain.append(platform)
321 print 'Adding platform: ' + platform
323 ValidateToolchains(options.toolchain)
326 if options.toolchain:
327 include['TOOLS'] = options.toolchain
328 print 'Filter by toolchain: ' + str(options.toolchain)
329 if not options.experimental:
330 include['EXPERIMENTAL'] = False
332 include['DEST'] = options.dest
333 print 'Filter by type: ' + str(options.dest)
335 include['NAME'] = args
336 print 'Filter by name: ' + str(args)
337 if not options.config:
338 options.config = ALL_CONFIGS
340 project_tree = GetProjectTree(include)
341 return RunAllTestsInTree(project_tree, options.toolchain, options.config,
345 if __name__ == '__main__':
346 script_name = os.path.basename(sys.argv[0])
348 sys.exit(main(sys.argv))
349 except parse_dsc.ValidationError as e:
350 buildbot_common.ErrorExit('%s: %s' % (script_name, e))
351 except KeyboardInterrupt:
352 buildbot_common.ErrorExit('%s: interrupted' % script_name)