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.
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
12 The testsuite must be configured, then run, then reported.
13 Currently it requires clean in between runs of different arches.
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.
30 import parse_llvm_test_report
33 @contextlib.contextmanager
35 """Provides a shell 'pushd'/'popd' pattern.
41 # Original cwd restored here
50 def ParseCommandLine(argv):
51 usage = """%prog [options]
53 Specify the tests or test subsets in the options; common tests are
54 --llvm-regression and --testsuite-all.
56 The --opt arguments control the frontend/backend optimization flags.
57 The default set is {O3f,O2b}, other options are {O0f,O0b}.
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',
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',
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',
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',
117 help=('[--testsuite-report option] ' +
118 'Signify LLVM testsuite tests'))
119 parser.add_option('-l', '--lit', action='store_true', dest='lit',
121 help=('[--testsuite-report option] ' +
122 'Signify LLVM LIT regression tests'))
124 options, args = parser.parse_args(argv)
129 """Prints an error message and exits."""
130 print >> sys.stderr, text
134 def ParseConfig(options):
135 """Constructs a frontend/backend dict based on --opt arguments.
138 options: The result of OptionParser().parse_args().
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
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'})
153 # Default is pnacl-clang -O3, pnacl-translate -O2
154 for attr in ['O3f', 'O2b'] + options.opt_attributes:
156 result.update(configs[attr])
160 def GetConfigSuffix(config):
161 """Create a string to be used as a file suffix.
164 config: A dict that was the result of ParseConfig().
167 A string that concatenates the frontend and backend attributes.
169 return config['frontend_attr'] + '_' + config['backend_attr']
171 def SetupEnvironment(options):
172 """Create an environment.
174 This is based on the current system, various defaults, and various
175 environment variables.
178 options: The result of OptionParser.parse_args()
180 A dict with various string->string mappings.
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(
212 os.path.join(os.getcwd(), 'mingw32', 'msys', 'bin'))
213 os.environ['PATH'] = os.pathsep.join([os.environ['PATH'], msys_path])
215 Fatal("Unknown system " + sys.platform)
216 if env['HOST_ARCH'] in ['i386', 'i686']:
217 env['HOST_ARCH'] = 'x86_32'
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')
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)))
243 '{NACL_ROOT}/toolchain/{PNACL_TOOLCHAIN_LABEL}/bin'.format(**env))
244 env['PNACL_SDK_DIR'] = (
245 '{NACL_ROOT}/toolchain/{PNACL_TOOLCHAIN_LABEL}/sdk/lib'
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))
255 def RunLitTest(testdir, testarg, lit_failures, env, options):
256 """Run LLVM lit tests, and check failures against known failures.
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().
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
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']
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)
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.
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,
305 print (str(datetime.datetime.now()) + ' ' +
306 "Waiting for '%s' to complete." % cmd)
308 make_stdout = ''.join(lines)
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)
320 def EnsureSdkExists(env):
321 """Ensure that the SDK directory exists. Exits if not.
324 env: The result of SetupEnvironment().
326 if not os.path.isdir(env['PNACL_SDK_DIR']):
328 ERROR: sdk dir does not seem to exist
329 ERROR: have you run 'pnacl/build.sh sdk newlib' ?
333 def TestsuitePrereq(env, options):
334 """Run the LLVM test suite prerequisites.
337 env: The result of SetupEnvironment().
338 options: The result of OptionParser().parse_args().
341 0 for success, non-zero integer on failure.
343 arch = options.arch or Fatal("Error: missing --arch argument")
344 return subprocess.call(['./scons',
348 '-j{PNACL_CONCURRENCY}'.format(**env)])
351 def TestsuiteRun(env, config, options):
352 """Run the LLVM test suite.
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().
361 0 for success, non-zero integer on failure.
363 arch = options.arch or Fatal("Error: missing --arch argument")
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)
373 if not os.path.exists('Makefile'):
374 result = TestsuiteConfigure(env)
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),
384 'PNACL_ARCH=' + arch,
385 'ENABLE_PARALLEL_REPORT=true',
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)))
401 def TestsuiteConfigure(env):
402 """Run the LLVM test suite configure script.
405 env: The result of SetupEnvironment().
408 0 for success, non-zero integer on failure.
410 build_path = env['LLVM_TESTSUITE_BUILD']
411 if not os.path.isdir(build_path):
412 os.makedirs(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)
424 def TestsuiteClean(env):
425 """Clean the LLVM test suite build directory.
428 env: The result of SetupEnvironment().
434 OSError: The LLVM_TESTSUITE_BUILD directory couldn't be removed
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'])
444 def TestsuiteReport(env, config, options):
445 """Generate a report from the prior LLVM test suite run.
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().
454 0 for success, non-zero integer on failure.
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)
473 options, args = ParseCommandLine(argv[1:])
475 Fatal("Unknown arguments: " + ', '.join(args))
476 config = ParseConfig(options)
477 env = SetupEnvironment(options)
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',
484 if options.run_libcxx_tests:
485 result = result or RunLitTest(env['TC_BUILD_LIBCXX'], 'check-libcxx',
486 'LIBCXX_KNOWN_FAILURES',
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)
500 if __name__ == '__main__':
501 sys.exit(main(sys.argv))