Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / native_client / pnacl / scripts / llvm-test.py
1 #!/usr/bin/python
2 # Copyright (c) 2013 The Native Client 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 """This script runs the LLVM regression tests and the LLVM testsuite.
7    These tests are tightly coupled to the LLVM build, and require that
8    LLVM has been built on this host by build.sh.  It also assumes that
9    the test suite source has been checked out using gclient (build.sh
10    git-sync).
11
12    The testsuite must be configured, then run, then reported.
13    Currently it requires clean in between runs of different arches.
14
15    The regression tests require nothing more than running 'make check'
16    in the build directory, but currently not all of the upstream tests
17    pass in our source tree, so we currently use the same
18    known-failures mechanism that the testsuite uses. Once we eliminate
19    the locally-caused failures, we should expect 'make check' to
20    always pass and can get rid of the regression known failures.
21 """
22
23 import contextlib
24 import datetime
25 import os
26 import optparse
27 import shutil
28 import subprocess
29 import sys
30 import parse_llvm_test_report
31
32
33 @contextlib.contextmanager
34 def remember_cwd():
35   """Provides a shell 'pushd'/'popd' pattern.
36
37   Use as:
38       with remember_cwd():
39         os.chdir(...)
40         ...
41       # Original cwd restored here
42   """
43   curdir = os.getcwd()
44   try:
45     yield
46   finally:
47     os.chdir(curdir)
48
49
50 def ParseCommandLine(argv):
51   usage = """%prog [options]
52
53 Specify the tests or test subsets in the options; common tests are
54 --llvm-regression and --testsuite-all.
55
56 The --opt arguments control the frontend/backend optimization flags.
57 The default set is {O3f,O2b}, other options are {O0f,O0b}.
58 """
59   parser = optparse.OptionParser(usage=usage)
60   parser.add_option('--arch', dest='arch',
61                     help=('Architecture to test, e.g. x86-32, x86-64, arm; ' +
62                           'required for most tests'))
63   parser.add_option('--opt', dest='opt_attributes', action='append',
64                     default=[],
65                     help=('Add optimization level attribute of ' +
66                           'test configuration'))
67   parser.add_option('--llvm-regression', dest='run_llvm_regression',
68                     action='store_true', default=False,
69                     help='Run the LLVM regression tests')
70   parser.add_option('--libcxx-tests', dest='run_libcxx_tests',
71                     action='store_true', default=False,
72                     help='Run the libc++ tests')
73   parser.add_option('--testsuite-clean', dest='testsuite_clean',
74                     action='store_true', default=False,
75                     help='Clean the testsuite build directory')
76   parser.add_option('--testsuite-prereq', dest='testsuite_prereq',
77                     action='store_true', default=False,
78                     help='Build the testsuite prerequisites')
79   parser.add_option('--testsuite-configure', dest='testsuite_configure',
80                     action='store_true', default=False,
81                     help='Configure the testsuite build directory')
82   parser.add_option('--testsuite-run', dest='testsuite_run',
83                     action='store_true', default=False,
84                     help='Run the testsuite (requires <arch> argument)')
85   parser.add_option('--testsuite-report', dest='testsuite_report',
86                     action='store_true', default=False,
87                     help=('Generate the testsuite report ' +
88                           '(requires <arch> argument)'))
89   parser.add_option('--testsuite-all', dest='testsuite_all',
90                     action='store_true', default=False,
91                     help='Run all testsuite steps (requires <arch> argument)')
92   parser.add_option('--llvm-buildpath', dest='llvm_buildpath',
93                     help='Path to the LLVM build directory')
94   parser.add_option('-v', '--verbose', action='store_true',
95                     default=False, dest='verbose',
96                     help=('[--testsuite-report/regression option] ' +
97                           'Print compilation/run logs of failing tests in'
98                           'testsuite report and print all regression output'))
99   # The following options are specific to parse_llvm_test_report.
100   parser.add_option('-x', '--exclude', action='append', dest='excludes',
101                     default=[],
102                     help=('[--testsuite-report option] ' +
103                           'Add list of excluded tests (expected fails)'))
104   parser.add_option('-c', '--check-excludes', action='store_true',
105                     default=False, dest='check_excludes',
106                     help=('[--testsuite-report option] ' +
107                           'Report tests which unexpectedly pass'))
108   parser.add_option('-p', '--build-path', dest='buildpath',
109                     help=('[--testsuite-report option] ' +
110                           'Path to test-suite build directory'))
111   parser.add_option('-a', '--attribute', dest='attributes', action='append',
112                     default=[],
113                     help=('[--testsuite-report option] ' +
114                           'Add attribute of test configuration (e.g. arch)'))
115   parser.add_option('-t', '--testsuite', action='store_true', dest='testsuite',
116                     default=False,
117                     help=('[--testsuite-report option] ' +
118                           'Signify LLVM testsuite tests'))
119   parser.add_option('-l', '--lit', action='store_true', dest='lit',
120                     default=False,
121                     help=('[--testsuite-report option] ' +
122                           'Signify LLVM LIT regression tests'))
123
124   options, args = parser.parse_args(argv)
125   return options, args
126
127
128 def Fatal(text):
129   """Prints an error message and exits."""
130   print >> sys.stderr, text
131   sys.exit(1)
132
133
134 def ParseConfig(options):
135   """Constructs a frontend/backend dict based on --opt arguments.
136
137   Args:
138     options: The result of OptionParser().parse_args().
139
140   Returns:
141     A simple dict containing keys 'frontend_opt', 'frontend_attr',
142     'backend_opt', and 'backend_attr', each mapped to a valid string
143     value.  The result is a function of the --opt command-line
144     arguments, with defaults in place when there are too few --opt
145     arguments.
146   """
147   configs = dict(O0f={'frontend_opt': '-O0', 'frontend_attr': 'O0f'},
148                  O3f={'frontend_opt': '-O3', 'frontend_attr': 'O3f'},
149                  O0b={'backend_opt': '-translate-fast',
150                       'backend_attr': 'O0b'},
151                  O2b={'backend_opt': '-O2', 'backend_attr': 'O2b'})
152   result = {}
153   # Default is pnacl-clang -O3, pnacl-translate -O2
154   for attr in ['O3f', 'O2b'] + options.opt_attributes:
155     if attr in configs:
156       result.update(configs[attr])
157   return result
158
159
160 def GetConfigSuffix(config):
161   """Create a string to be used as a file suffix.
162
163   Args:
164     config: A dict that was the result of ParseConfig().
165
166   Returns:
167     A string that concatenates the frontend and backend attributes.
168   """
169   return config['frontend_attr'] + '_' + config['backend_attr']
170
171 def SetupEnvironment(options):
172   """Create an environment.
173
174   This is based on the current system, various defaults, and various
175   environment variables.
176
177   Args:
178     options: The result of OptionParser.parse_args()
179   Returns:
180     A dict with various string->string mappings.
181   """
182   env = {}
183   pwd = os.getcwd()
184   if not pwd.endswith(os.sep + 'native_client'):
185     Fatal("ERROR: must be run in native_client/ directory!\n" +
186           "       (Current directory is " + pwd + ")")
187   # Simulate what's needed from common-tools.sh.
188   # We need PNACL_BUILDBOT, BUILD_PLATFORM, and HOST_ARCH.
189   # TODO(dschuff): This should come from toolchain_build or the upcoming common
190   # python infrastructure.
191   env['PNACL_BUILDBOT'] = os.environ.get('PNACL_BUILDBOT', 'false')
192   if sys.platform == 'linux2':
193     env['BUILD_PLATFORM'] = 'linux'
194     env['BUILD_ARCH'] = os.environ.get('BUILD_ARCH', os.uname()[4])
195     env['HOST_ARCH'] = os.environ.get('HOST_ARCH', env['BUILD_ARCH'])
196     env['HOST_TRIPLE'] = 'i686_linux'
197   elif sys.platform == 'cygwin':
198     env['BUILD_PLATFORM'] = 'win'
199     env['HOST_ARCH'] = os.environ.get('HOST_ARCH', 'x86_32')
200     env['HOST_TRIPLE'] = 'i686_pc_cygwin'
201   elif sys.platform == 'darwin':
202     env['BUILD_PLATFORM'] = 'mac'
203     env['HOST_ARCH'] = os.environ.get('HOST_ARCH', 'x86_64')
204     env['HOST_TRIPLE'] = 'x86_64_apple_darwin'
205   elif sys.platform == 'win32':
206     env['BUILD_PLATFORM'] = 'win'
207     env['HOST_ARCH'] = os.environ.get('HOST_ARCH', 'x86_64')
208     env['HOST_TRIPLE'] = 'i686_w64_mingw32'
209     # TODO(dschuff) unify this with toolchain_build_pnacl
210     msys_path = os.environ.get(
211         'MSYS',
212         os.path.join(os.getcwd(), 'mingw32', 'msys', 'bin'))
213     os.environ['PATH'] = os.pathsep.join([os.environ['PATH'], msys_path])
214   else:
215     Fatal("Unknown system " + sys.platform)
216   if env['HOST_ARCH'] in ['i386', 'i686']:
217     env['HOST_ARCH'] = 'x86_32'
218
219
220   # Set up the rest of the environment.
221   env['NACL_ROOT'] = pwd
222   env['LLVM_TESTSUITE_SRC'] = (
223     '{NACL_ROOT}/pnacl/git/llvm-test-suite'.format(**env))
224   env['LLVM_TESTSUITE_BUILD'] = (
225     '{NACL_ROOT}/pnacl/build/llvm-test-suite'.format(**env))
226   env['TC_SRC_LLVM'] = (
227     '{NACL_ROOT}/pnacl/git/llvm'.format(**env))
228   env['TC_BUILD_LLVM'] = options.llvm_buildpath or (
229     '{NACL_ROOT}/toolchain_build/out/llvm_{HOST_TRIPLE}_work'.format(**env))
230   env['TC_BUILD_LIBCXX'] = (
231     ('{NACL_ROOT}/pnacl/build/' +
232      'c++-stdlib-newlib-portable-libc++/pnacl-target').format(**env))
233   env['PNACL_CONCURRENCY'] = os.environ.get('PNACL_CONCURRENCY', '8')
234
235   # The toolchain used may not be the one downloaded, but one that is freshly
236   # built into a different directory,
237   # Overriding the default here will Not affect the sel_ldr
238   # and IRT used to run the tests (they are controlled by run.py)
239   env['PNACL_TOOLCHAIN_LABEL'] = (
240     os.environ.get('PNACL_TOOLCHAIN_LABEL',
241                    'pnacl_{BUILD_PLATFORM}_x86'.format(**env)))
242   env['PNACL_BIN'] = (
243     '{NACL_ROOT}/toolchain/{PNACL_TOOLCHAIN_LABEL}/bin'.format(**env))
244   env['PNACL_SDK_DIR'] = (
245     '{NACL_ROOT}/toolchain/{PNACL_TOOLCHAIN_LABEL}/sdk/lib'
246     .format(**env))
247   env['PNACL_SCRIPTS'] = '{NACL_ROOT}/pnacl/scripts'.format(**env)
248   env['LLVM_REGRESSION_KNOWN_FAILURES'] = (
249       '{pwd}/pnacl/scripts/llvm_regression_known_failures.txt'.format(pwd=pwd))
250   env['LIBCXX_KNOWN_FAILURES'] = (
251       '{pwd}/pnacl/scripts/libcxx_known_failures.txt'.format(pwd=pwd))
252   return env
253
254
255 def RunLitTest(testdir, testarg, lit_failures, env, options):
256   """Run LLVM lit tests, and check failures against known failures.
257
258   Args:
259     testdir: Directory with the make/ninja file to test.
260     testarg: argument to pass to make/ninja.
261     env: The result of SetupEnvironment().
262     options: The result of OptionParser().parse_args().
263
264   Returns:
265     0 always
266   """
267   with remember_cwd():
268     if not os.path.exists(testdir) or len(os.listdir(testdir)) == 0:
269       # TODO(dschuff): Because this script is run directly from the buildbot
270       # script and not as part of a toolchain_build rule, we do not know
271       # whether the llvm target was actually built (in which case the working
272       # directory is still there) or whether it was just retrieved from cache
273       # (in which case it was clobbered, since the bots run with --clobber).
274       # So we have to just exit rather than fail here.
275       print 'Working directory %s is empty. Not running tests' % testdir
276       return 0
277     os.chdir(testdir)
278
279     sub_env = os.environ.copy()
280     # Tell run.py to use the architecture specified by --arch, or the
281     # current host architecture if none was provided.
282     sub_env['PNACL_RUN_ARCH'] = options.arch or env['HOST_ARCH']
283
284     maker = 'ninja' if os.path.isfile('./build.ninja') else 'make'
285     cmd = [maker, testarg, '-v' if maker == 'ninja' else 'VERBOSE=1']
286     print 'Running lit test:', ' '.join(cmd)
287     make_pipe = subprocess.Popen(cmd, env=sub_env, stdout=subprocess.PIPE)
288
289     lines = []
290     # When run by a buildbot, we need to incrementally tee the 'make'
291     # stdout to our stdout, rather than collect its entire stdout and
292     # print it at the end.  Otherwise the watchdog may try to kill the
293     # process after too long without any output.
294     #
295     # Note: We use readline instead of 'for line in make_pipe.stdout'
296     # because otherwise the process on the Cygwin bot seems to hang
297     # when the 'make' process completes (with slightly truncated
298     # output).  The readline avoids buffering when reading from a
299     # pipe in Python 2, which may be complicit in the problem.
300     for line in iter(make_pipe.stdout.readline, ''):
301       if env['PNACL_BUILDBOT'] != 'false' or options.verbose:
302         # The buildbots need to be fully verbose and print all output.
303         print str(datetime.datetime.now()) + ' ' + line,
304       lines.append(line)
305     print (str(datetime.datetime.now()) + ' ' +
306            "Waiting for '%s' to complete." % cmd)
307     make_pipe.wait()
308     make_stdout = ''.join(lines)
309
310     parse_options = vars(options)
311     parse_options['lit'] = True
312     parse_options['excludes'].append(env[lit_failures])
313     parse_options['attributes'].append(env['BUILD_PLATFORM'])
314     print (str(datetime.datetime.now()) + ' ' +
315            'Parsing LIT test report output.')
316     ret = parse_llvm_test_report.Report(parse_options, filecontents=make_stdout)
317   return ret
318
319
320 def EnsureSdkExists(env):
321   """Ensure that the SDK directory exists.  Exits if not.
322
323   Args:
324     env: The result of SetupEnvironment().
325   """
326   if not os.path.isdir(env['PNACL_SDK_DIR']):
327     Fatal("""
328 ERROR: sdk dir does not seem to exist
329 ERROR: have you run 'pnacl/build.sh sdk newlib' ?
330 """)
331
332
333 def TestsuitePrereq(env, options):
334   """Run the LLVM test suite prerequisites.
335
336   Args:
337     env: The result of SetupEnvironment().
338     options: The result of OptionParser().parse_args().
339
340   Returns:
341     0 for success, non-zero integer on failure.
342   """
343   arch = options.arch or Fatal("Error: missing --arch argument")
344   return subprocess.call(['./scons',
345                           'platform=' + arch,
346                           'irt_core',
347                           'sel_ldr',
348                           '-j{PNACL_CONCURRENCY}'.format(**env)])
349
350
351 def TestsuiteRun(env, config, options):
352   """Run the LLVM test suite.
353
354   Args:
355     env: The result of SetupEnvironment().
356     config: A dict that was the result of ParseConfig().  This
357         determines the specific optimization levels.
358     options: The result of OptionParser().parse_args().
359
360   Returns:
361     0 for success, non-zero integer on failure.
362   """
363   arch = options.arch or Fatal("Error: missing --arch argument")
364   EnsureSdkExists(env)
365   suffix = GetConfigSuffix(config)
366   opt_clang = config['frontend_opt']
367   opt_trans = config['backend_opt']
368   build_path = env['LLVM_TESTSUITE_BUILD']
369   if not os.path.isdir(build_path):
370     os.makedirs(build_path)
371   with remember_cwd():
372     os.chdir(build_path)
373     if not os.path.exists('Makefile'):
374       result = TestsuiteConfigure(env)
375       if result:
376         return result
377     result = subprocess.call(['make',
378                               '-j{PNACL_CONCURRENCY}'.format(**env),
379                               'OPTFLAGS=' + opt_clang,
380                               'PNACL_TRANSLATE_FLAGS=' + opt_trans,
381                               'PNACL_BIN={PNACL_BIN}'.format(**env),
382                               'PNACL_RUN={NACL_ROOT}/run.py'.format(**env),
383                               'COLLATE=true',
384                               'PNACL_ARCH=' + arch,
385                               'ENABLE_PARALLEL_REPORT=true',
386                               'DISABLE_CBE=true',
387                               'DISABLE_JIT=true',
388                               'RUNTIMELIMIT=850',
389                               'TEST=pnacl',
390                               'report.csv'])
391     if result:
392       return result
393     os.rename('report.pnacl.csv', 'report.pnacl.{arch}.{suffix}.csv'
394               .format(arch=arch, suffix=suffix))
395     os.rename('report.pnacl.raw.out',
396               ('report.pnacl.{arch}.{suffix}.raw.out'
397                .format(arch=arch, suffix=suffix)))
398   return 0
399
400
401 def TestsuiteConfigure(env):
402   """Run the LLVM test suite configure script.
403
404   Args:
405     env: The result of SetupEnvironment().
406
407   Returns:
408     0 for success, non-zero integer on failure.
409   """
410   build_path = env['LLVM_TESTSUITE_BUILD']
411   if not os.path.isdir(build_path):
412     os.makedirs(build_path)
413   with remember_cwd():
414     os.chdir(build_path)
415     args = ['{LLVM_TESTSUITE_SRC}/configure'.format(**env),
416             '--with-llvmcc=clang',
417             '--with-clang={PNACL_BIN}/pnacl-clang'.format(**env),
418             '--with-llvmsrc={TC_SRC_LLVM}'.format(**env),
419             '--with-llvmobj={TC_BUILD_LLVM}'.format(**env)]
420     result = subprocess.call(args)
421   return result
422
423
424 def TestsuiteClean(env):
425   """Clean the LLVM test suite build directory.
426
427   Args:
428     env: The result of SetupEnvironment().
429
430   Returns:
431     0 always
432
433   Raises:
434     OSError: The LLVM_TESTSUITE_BUILD directory couldn't be removed
435     for some reason.
436   """
437   if os.path.isdir(env['LLVM_TESTSUITE_BUILD']):
438     shutil.rmtree(env['LLVM_TESTSUITE_BUILD'])
439   elif os.path.isfile(env['LLVM_TESTSUITE_BUILD']):
440     os.remove(env['LLVM_TESTSUITE_BUILD'])
441   return 0
442
443
444 def TestsuiteReport(env, config, options):
445   """Generate a report from the prior LLVM test suite run.
446
447   Args:
448     env: The result of SetupEnvironment().
449     config: A dict that was the result of ParseConfig().  This
450         determines the specific optimization levels.
451     options: The result of OptionParser().parse_args().
452
453   Returns:
454     0 for success, non-zero integer on failure.
455   """
456   arch = options.arch or Fatal("Error: missing --arch argument")
457   suffix = GetConfigSuffix(config)
458   report_file = ('{LLVM_TESTSUITE_BUILD}/report.pnacl.{arch}.{suffix}.csv'
459                  .format(arch=arch, suffix=suffix, **env))
460   failures1 = '{PNACL_SCRIPTS}/testsuite_known_failures_base.txt'.format(**env)
461   failures2 = '{PNACL_SCRIPTS}/testsuite_known_failures_pnacl.txt'.format(**env)
462   parse_options = vars(options)
463   parse_options['excludes'].extend([failures1, failures2])
464   parse_options['buildpath'] = env['LLVM_TESTSUITE_BUILD']
465   parse_options['attributes'].extend([arch,
466                                       config['frontend_attr'],
467                                       config['backend_attr']])
468   parse_options['testsuite'] = True
469   return parse_llvm_test_report.Report(parse_options, filename=report_file)
470
471
472 def main(argv):
473   options, args = ParseCommandLine(argv[1:])
474   if len(args):
475     Fatal("Unknown arguments: " + ', '.join(args))
476   config = ParseConfig(options)
477   env = SetupEnvironment(options)
478   result = 0
479   # Run each specified test in sequence, and return on the first failure.
480   if options.run_llvm_regression:
481     result = result or RunLitTest(env['TC_BUILD_LLVM'], 'check-all',
482                                   'LLVM_REGRESSION_KNOWN_FAILURES',
483                                   env, options)
484   if options.run_libcxx_tests:
485     result = result or RunLitTest(env['TC_BUILD_LIBCXX'], 'check-libcxx',
486                                   'LIBCXX_KNOWN_FAILURES',
487                                   env, options)
488   if options.testsuite_all or options.testsuite_prereq:
489     result = result or TestsuitePrereq(env, options)
490   if options.testsuite_all or options.testsuite_clean:
491     result = result or TestsuiteClean(env)
492   if options.testsuite_all or options.testsuite_configure:
493     result = result or TestsuiteConfigure(env)
494   if options.testsuite_all or options.testsuite_run:
495     result = result or TestsuiteRun(env, config, options)
496   if options.testsuite_all or options.testsuite_report:
497     result = result or TestsuiteReport(env, config, options)
498   return result
499
500 if __name__ == '__main__':
501   sys.exit(main(sys.argv))