- add sources.
[platform/framework/web/crosswalk.git] / src / native_client_sdk / src / build_tools / test_projects.py
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.
5
6 import optparse
7 import os
8 import subprocess
9 import sys
10 import time
11
12 import buildbot_common
13 import build_version
14 import parse_dsc
15
16 from build_paths import OUT_DIR, SRC_DIR, SDK_SRC_DIR, SCRIPT_DIR
17
18 sys.path.append(os.path.join(SDK_SRC_DIR, 'tools'))
19 import getos
20 platform = getos.GetPlatform()
21
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'))
24 import find_chrome
25 browser_path = find_chrome.FindChrome(SRC_DIR, ['Debug', 'Release'])
26
27
28 pepper_ver = str(int(build_version.ChromeMajorVersion()))
29 pepperdir = os.path.join(OUT_DIR, 'pepper_' + pepper_ver)
30
31 browser_tester_py = os.path.join(SRC_DIR, 'ppapi', 'native_client', 'tools',
32     'browser_tester', 'browser_tester.py')
33
34
35 ALL_CONFIGS = ['Debug', 'Release']
36 ALL_TOOLCHAINS = ['newlib', 'glibc', 'pnacl', 'win', 'linux', 'mac']
37
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.
43 #
44 # All keys must be matched, but any value that matches in a sequence is
45 # considered a match for that key. For example:
46 #
47 #   {'name': ('pi_generator', 'input_event'), 'toolchain': ('newlib', 'pnacl')}
48 #
49 # Will match 8 tests:
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
58 DISABLED_TESTS = [
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
66     # example.
67     # TODO(binji): figure out a way to inject the testing code without
68     # modifying the example; maybe an extension?
69     {'name': 'part1'},
70 ]
71
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)))
77
78
79 def GetServingDirForProject(desc):
80   dest = desc['DEST']
81   path = os.path.join(pepperdir, *dest.split('/'))
82   return os.path.join(path, desc['NAME'])
83
84
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'])
89
90
91 def GetExecutableDirForProject(desc, toolchain, config):
92   return os.path.join(GetServingDirForProject(desc), toolchain, config)
93
94
95 def GetBrowserTesterCommand(desc, toolchain, config):
96   if browser_path is None:
97     buildbot_common.ErrorExit('Failed to find chrome browser using FindChrome.')
98
99   args = [
100     sys.executable,
101     browser_tester_py,
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',
106     '--enable_sockets',
107   ]
108
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])
114
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'
120     else:
121       ppapi_plugin += '.so'
122     args.extend(['--ppapi_plugin', ppapi_plugin])
123
124     ppapi_plugin_mimetype = 'application/x-ppapi-%s' % config.lower()
125     args.extend(['--ppapi_plugin_mimetype', ppapi_plugin_mimetype])
126
127   if toolchain == 'pnacl':
128     args.extend(['--browser_flag', '--enable-pnacl'])
129
130   url = 'index.html'
131   url += '?tc=%s&config=%s&test=true' % (toolchain, config)
132   args.extend(['--url', url])
133   return args
134
135
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...).
141   #
142   # Clear the PYTHONPATH so it imports the correct file.
143   env = dict(os.environ)
144   env['PYTHONPATH'] = ''
145   return env
146
147
148 def RunTestOnce(desc, toolchain, config):
149   args = GetBrowserTesterCommand(desc, toolchain, config)
150   env = GetBrowserTesterEnv()
151   start_time = time.time()
152   try:
153     subprocess.check_call(args, env=env)
154     result = True
155   except subprocess.CalledProcessError:
156     result = False
157   elapsed = (time.time() - start_time) * 1000
158   return result, elapsed
159
160
161 def RunTestNTimes(desc, toolchain, config, times):
162   total_elapsed = 0
163   for _ in xrange(times):
164     result, elapsed = RunTestOnce(desc, toolchain, config)
165     total_elapsed += elapsed
166     if result:
167       # Success, stop retrying.
168       break
169   return result, total_elapsed
170
171
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)
178   return result
179
180
181 def WriteGtestHeader(test_name):
182   print '\n[ RUN      ] %s' % test_name
183   sys.stdout.flush()
184   sys.stderr.flush()
185
186
187 def WriteGtestFooter(success, test_name, elapsed):
188   sys.stdout.flush()
189   sys.stderr.flush()
190   if success:
191     message = '[       OK ]'
192   else:
193     message = '[  FAILED  ]'
194   print '%s %s (%d ms)' % (message, test_name, elapsed)
195
196
197 def GetTestName(desc, toolchain, config):
198   return '%s.%s_%s_test' % (desc['NAME'], toolchain, config.lower())
199
200
201 def IsTestDisabled(desc, toolchain, config):
202   def AsList(value):
203     if type(value) not in (list, tuple):
204       return [value]
205     return value
206
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]):
211           return False
212     return True
213
214   test_values = {
215       'name': desc['NAME'],
216       'toolchain': toolchain,
217       'config': config,
218       'platform': platform
219   }
220
221   for disabled_test in DISABLED_TESTS:
222     if TestMatchesDisabled(test_values, disabled_test):
223       return True
224   return False
225
226
227 def WriteHorizontalBar():
228   print '-' * 80
229
230
231 def WriteBanner(message):
232   WriteHorizontalBar()
233   print message
234   WriteHorizontalBar()
235
236
237 def RunAllTestsInTree(tree, toolchains, configs, retry_on_failure_times):
238   tests_run = 0
239   total_tests = 0
240   failed = []
241   disabled = []
242
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)
250         total_tests += 1
251         if IsTestDisabled(desc, toolchain, config):
252           disabled.append(test_name)
253           continue
254
255         tests_run += 1
256         success = RunTestWithGtestOutput(desc, toolchain, config,
257                                          retry_on_failure_times)
258         if not success:
259           failed.append(test_name)
260
261   if failed:
262     WriteBanner('FAILED TESTS')
263     for test in failed:
264       print '  %s failed.' % test
265
266   if disabled:
267     WriteBanner('DISABLED TESTS')
268     for test in disabled:
269       print '  %s disabled.' % test
270
271   WriteHorizontalBar()
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)
275
276   success = len(failed) != 0
277   return success
278
279
280 def GetProjectTree(include):
281   # Everything in src is a library, and cannot be run.
282   exclude = {'DEST': 'src'}
283   try:
284     return parse_dsc.LoadProjectTree(SDK_SRC_DIR, include=include,
285                                      exclude=exclude)
286   except parse_dsc.ValidationError as e:
287     buildbot_common.ErrorExit(str(e))
288
289
290 def main(args):
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.',
302       action='append')
303   parser.add_option('-p', '--project',
304       help='Select which projects are valid.',
305       action='append')
306   parser.add_option('--retry-times',
307       help='Number of types to retry on failure (Default: %default)',
308           type='int', default=1)
309
310   options, args = parser.parse_args(args[1:])
311   if options.project:
312     parser.error('The -p/--project option is deprecated.\n'
313                  'Just use positional paramaters instead.')
314
315   if not options.toolchain:
316     options.toolchain = ['newlib', 'glibc', 'pnacl', 'host']
317
318   if 'host' in options.toolchain:
319     options.toolchain.remove('host')
320     options.toolchain.append(platform)
321     print 'Adding platform: ' + platform
322
323   ValidateToolchains(options.toolchain)
324
325   include = {}
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
331   if options.dest:
332     include['DEST'] = options.dest
333     print 'Filter by type: ' + str(options.dest)
334   if args:
335     include['NAME'] = args
336     print 'Filter by name: ' + str(args)
337   if not options.config:
338     options.config = ALL_CONFIGS
339
340   project_tree = GetProjectTree(include)
341   return RunAllTestsInTree(project_tree, options.toolchain, options.config,
342                            options.retry_times)
343
344
345 if __name__ == '__main__':
346   script_name = os.path.basename(sys.argv[0])
347   try:
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)