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.
14 import buildbot_common
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'))
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'))
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')}
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
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
73 # TODO(binji): figure out a way to inject the testing code without
74 # modifying the example; maybe an extension?
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
):
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.')
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',
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'
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'])
139 url
+= '?tc=%s&config=%s&test=true' % (toolchain
, config
)
140 args
.extend(['--url', url
])
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'] = ''
156 def RunTestOnce(desc
, toolchain
, config
):
157 args
= GetBrowserTesterCommand(desc
, toolchain
, config
)
158 env
= GetBrowserTesterEnv()
159 start_time
= time
.time()
161 subprocess
.check_call(args
, env
=env
)
163 except subprocess
.CalledProcessError
:
165 elapsed
= (time
.time() - start_time
) * 1000
166 return result
, elapsed
169 def RunTestNTimes(desc
, toolchain
, config
, times
):
171 for _
in xrange(times
):
172 result
, elapsed
= RunTestOnce(desc
, toolchain
, config
)
173 total_elapsed
+= elapsed
175 # Success, stop retrying.
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
)
189 def WriteGtestHeader(test_name
):
190 print '\n[ RUN ] %s' % test_name
195 def WriteGtestFooter(success
, test_name
, elapsed
):
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
):
211 if type(value
) not in (list, tuple):
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
]):
223 'name': desc
['NAME'],
224 'toolchain': toolchain
,
229 for disabled_test
in DISABLED_TESTS
:
230 if TestMatchesDisabled(test_values
, disabled_test
):
235 def WriteHorizontalBar():
239 def WriteBanner(message
):
245 def RunAllTestsInTree(tree
, toolchains
, configs
, retry_on_failure_times
):
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
)
259 if IsTestDisabled(desc
, toolchain
, config
):
260 disabled
.append(test_name
)
264 success
= RunTestWithGtestOutput(desc
, toolchain
, config
,
265 retry_on_failure_times
)
267 failed
.append(test_name
)
270 WriteBanner('FAILED TESTS')
272 print ' %s failed.' % test
275 WriteBanner('DISABLED TESTS')
276 for test
in disabled
:
277 print ' %s disabled.' % test
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
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'}
305 return parse_dsc
.LoadProjectTree(SDK_SRC_DIR
, include
=include
,
307 except parse_dsc
.ValidationError
as e
:
308 buildbot_common
.ErrorExit(str(e
))
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.',
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
)
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
349 include
['DEST'] = options
.dest
350 print 'Filter by type: ' + str(options
.dest
)
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
)
359 BuildAllTestsInTree(project_tree
, options
.toolchain
, options
.config
)
361 return RunAllTestsInTree(project_tree
, options
.toolchain
, options
.config
,
365 if __name__
== '__main__':
366 script_name
= os
.path
.basename(sys
.argv
[0])
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
)