ae6e58c561
git-svn-id: https://llvm.org/svn/llvm-project/libcxx/trunk@216314 91177308-0d34-0410-b5e6-96231b3b80d8
437 lines
18 KiB
Python
437 lines
18 KiB
Python
# -*- Python -*- vim: set syntax=python tabstop=4 expandtab cc=80:
|
|
|
|
# Configuration file for the 'lit' test runner.
|
|
|
|
import errno
|
|
import locale
|
|
import os
|
|
import platform
|
|
import re
|
|
import shlex
|
|
import signal
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
|
|
import lit.Test
|
|
import lit.formats
|
|
import lit.util
|
|
|
|
class LibcxxTestFormat(lit.formats.FileBasedTest):
|
|
"""
|
|
Custom test format handler for use with the test format use by libc++.
|
|
|
|
Tests fall into two categories:
|
|
FOO.pass.cpp - Executable test which should compile, run, and exit with
|
|
code 0.
|
|
FOO.fail.cpp - Negative test case which is expected to fail compilation.
|
|
"""
|
|
|
|
def __init__(self, cxx_under_test, cpp_flags, ld_flags, exec_env):
|
|
self.cxx_under_test = cxx_under_test
|
|
self.cpp_flags = list(cpp_flags)
|
|
self.ld_flags = list(ld_flags)
|
|
self.exec_env = dict(exec_env)
|
|
|
|
def execute_command(self, command, in_dir=None):
|
|
kwargs = {
|
|
'stdin' :subprocess.PIPE,
|
|
'stdout':subprocess.PIPE,
|
|
'stderr':subprocess.PIPE,
|
|
}
|
|
if in_dir:
|
|
kwargs['cwd'] = in_dir
|
|
p = subprocess.Popen(command, **kwargs)
|
|
out,err = p.communicate()
|
|
exitCode = p.wait()
|
|
|
|
# Detect Ctrl-C in subprocess.
|
|
if exitCode == -signal.SIGINT:
|
|
raise KeyboardInterrupt
|
|
|
|
return out, err, exitCode
|
|
|
|
def execute(self, test, lit_config):
|
|
while True:
|
|
try:
|
|
return self._execute(test, lit_config)
|
|
except OSError, oe:
|
|
if oe.errno != errno.ETXTBSY:
|
|
raise
|
|
time.sleep(0.1)
|
|
|
|
def _execute(self, test, lit_config):
|
|
# Extract test metadata from the test file.
|
|
requires = []
|
|
unsupported = []
|
|
with open(test.getSourcePath()) as f:
|
|
for ln in f:
|
|
if 'XFAIL:' in ln:
|
|
items = ln[ln.index('XFAIL:') + 6:].split(',')
|
|
test.xfails.extend([s.strip() for s in items])
|
|
elif 'REQUIRES:' in ln:
|
|
items = ln[ln.index('REQUIRES:') + 9:].split(',')
|
|
requires.extend([s.strip() for s in items])
|
|
elif 'UNSUPPORTED:' in ln:
|
|
items = ln[ln.index('UNSUPPORTED:') + 12:].split(',')
|
|
unsupported.extend([s.strip() for s in items])
|
|
elif not ln.strip().startswith("//") and ln.strip():
|
|
# Stop at the first non-empty line that is not a C++
|
|
# comment.
|
|
break
|
|
|
|
# Check that we have the required features.
|
|
#
|
|
# FIXME: For now, this is cribbed from lit.TestRunner, to avoid
|
|
# introducing a dependency there. What we more ideally would like to do
|
|
# is lift the "requires" handling to be a core lit framework feature.
|
|
missing_required_features = [f for f in requires
|
|
if f not in test.config.available_features]
|
|
if missing_required_features:
|
|
return (lit.Test.UNSUPPORTED,
|
|
"Test requires the following features: %s" % (
|
|
', '.join(missing_required_features),))
|
|
|
|
unsupported_features = [f for f in unsupported
|
|
if f in test.config.available_features]
|
|
if unsupported_features:
|
|
return (lit.Test.UNSUPPORTED,
|
|
"Test is unsupported with the following features: %s" % (
|
|
', '.join(unsupported_features),))
|
|
|
|
# Evaluate the test.
|
|
return self._evaluate_test(test, lit_config)
|
|
|
|
def _evaluate_test(self, test, lit_config):
|
|
name = test.path_in_suite[-1]
|
|
source_path = test.getSourcePath()
|
|
source_dir = os.path.dirname(source_path)
|
|
|
|
# Check what kind of test this is.
|
|
assert name.endswith('.pass.cpp') or name.endswith('.fail.cpp')
|
|
expected_compile_fail = name.endswith('.fail.cpp')
|
|
|
|
# If this is a compile (failure) test, build it and check for failure.
|
|
if expected_compile_fail:
|
|
cmd = [self.cxx_under_test, '-c',
|
|
'-o', '/dev/null', source_path] + self.cpp_flags
|
|
out, err, exitCode = self.execute_command(cmd)
|
|
if exitCode == 1:
|
|
return lit.Test.PASS, ""
|
|
else:
|
|
report = """Command: %s\n""" % ' '.join(["'%s'" % a
|
|
for a in cmd])
|
|
report += """Exit Code: %d\n""" % exitCode
|
|
if out:
|
|
report += """Standard Output:\n--\n%s--""" % out
|
|
if err:
|
|
report += """Standard Error:\n--\n%s--""" % err
|
|
report += "\n\nExpected compilation to fail!"
|
|
return lit.Test.FAIL, report
|
|
else:
|
|
exec_file = tempfile.NamedTemporaryFile(suffix="exe", delete=False)
|
|
exec_path = exec_file.name
|
|
exec_file.close()
|
|
|
|
try:
|
|
compile_cmd = [self.cxx_under_test, '-o', exec_path,
|
|
source_path] + self.cpp_flags + self.ld_flags
|
|
cmd = compile_cmd
|
|
out, err, exitCode = self.execute_command(cmd)
|
|
if exitCode != 0:
|
|
report = """Command: %s\n""" % ' '.join(["'%s'" % a
|
|
for a in cmd])
|
|
report += """Exit Code: %d\n""" % exitCode
|
|
if out:
|
|
report += """Standard Output:\n--\n%s--""" % out
|
|
if err:
|
|
report += """Standard Error:\n--\n%s--""" % err
|
|
report += "\n\nCompilation failed unexpectedly!"
|
|
return lit.Test.FAIL, report
|
|
|
|
cmd = []
|
|
if self.exec_env:
|
|
cmd.append('env')
|
|
cmd.extend('%s=%s' % (name, value)
|
|
for name,value in self.exec_env.items())
|
|
cmd.append(exec_path)
|
|
if lit_config.useValgrind:
|
|
cmd = lit_config.valgrindArgs + cmd
|
|
out, err, exitCode = self.execute_command(cmd, source_dir)
|
|
if exitCode != 0:
|
|
report = """Compiled With: %s\n""" % \
|
|
' '.join(["'%s'" % a for a in compile_cmd])
|
|
report += """Command: %s\n""" % \
|
|
' '.join(["'%s'" % a for a in cmd])
|
|
report += """Exit Code: %d\n""" % exitCode
|
|
if out:
|
|
report += """Standard Output:\n--\n%s--""" % out
|
|
if err:
|
|
report += """Standard Error:\n--\n%s--""" % err
|
|
report += "\n\nCompiled test failed unexpectedly!"
|
|
return lit.Test.FAIL, report
|
|
finally:
|
|
try:
|
|
os.remove(exec_path)
|
|
except:
|
|
pass
|
|
return lit.Test.PASS, ""
|
|
|
|
|
|
class Configuration(object):
|
|
def __init__(self, lit_config, config):
|
|
self.lit_config = lit_config
|
|
self.config = config
|
|
self.cxx = None
|
|
self.src_root = None
|
|
self.obj_root = None
|
|
self.env = {}
|
|
self.compile_flags = []
|
|
self.link_flags = []
|
|
self.use_system_lib = False
|
|
|
|
if platform.system() not in ('Darwin', 'FreeBSD', 'Linux'):
|
|
self.lit_config.fatal("unrecognized system")
|
|
|
|
def get_lit_conf(self, name, default=None):
|
|
val = self.lit_config.params.get(name, None)
|
|
if val is None:
|
|
val = getattr(self.config, name, None)
|
|
if val is None:
|
|
val = default
|
|
return val
|
|
|
|
def configure(self):
|
|
self.configure_cxx()
|
|
self.configure_triple()
|
|
self.configure_src_root()
|
|
self.configure_obj_root()
|
|
self.configure_use_system_lib()
|
|
self.configure_env()
|
|
self.configure_std_flag()
|
|
self.configure_compile_flags()
|
|
self.configure_link_flags()
|
|
self.configure_sanitizer()
|
|
self.configure_features()
|
|
|
|
def get_test_format(self):
|
|
return LibcxxTestFormat(
|
|
self.cxx,
|
|
cpp_flags=['-nostdinc++'] + self.compile_flags,
|
|
ld_flags=['-nodefaultlibs'] + self.link_flags,
|
|
exec_env=self.env)
|
|
|
|
def configure_cxx(self):
|
|
# Gather various compiler parameters.
|
|
self.cxx = self.get_lit_conf('cxx_under_test')
|
|
|
|
# If no specific cxx_under_test was given, attempt to infer it as
|
|
# clang++.
|
|
if self.cxx is None:
|
|
clangxx = lit.util.which('clang++',
|
|
self.config.environment['PATH'])
|
|
if clangxx:
|
|
self.cxx = clangxx
|
|
self.lit_config.note(
|
|
"inferred cxx_under_test as: %r" % self.cxx)
|
|
if not self.cxx:
|
|
self.lit_config.fatal('must specify user parameter cxx_under_test '
|
|
'(e.g., --param=cxx_under_test=clang++)')
|
|
|
|
def configure_src_root(self):
|
|
self.src_root = self.get_lit_conf(
|
|
'libcxx_src_root', os.path.dirname(self.config.test_source_root))
|
|
|
|
def configure_obj_root(self):
|
|
self.obj_root = self.get_lit_conf('libcxx_obj_root', self.src_root)
|
|
|
|
def configure_use_system_lib(self):
|
|
# This test suite supports testing against either the system library or
|
|
# the locally built one; the former mode is useful for testing ABI
|
|
# compatibility between the current headers and a shipping dynamic
|
|
# library.
|
|
use_system_lib_str = self.get_lit_conf('use_system_lib')
|
|
if use_system_lib_str:
|
|
if use_system_lib_str.lower() in ('1', 'true'):
|
|
self.use_system_lib = True
|
|
elif use_system_lib_str.lower() in ('', '0', 'false'):
|
|
self.use_system_lib = False
|
|
else:
|
|
self.lit_config.fatal(
|
|
'user parameter use_system_lib should be 0 or 1')
|
|
else:
|
|
# Default to testing against the locally built libc++ library.
|
|
self.use_system_lib = False
|
|
self.lit_config.note(
|
|
"inferred use_system_lib as: %r" % self.use_system_lib)
|
|
|
|
def configure_features(self):
|
|
# Figure out which of the required locales we support
|
|
locales = {
|
|
'Darwin': {
|
|
'en_US.UTF-8': 'en_US.UTF-8',
|
|
'cs_CZ.ISO8859-2': 'cs_CZ.ISO8859-2',
|
|
'fr_FR.UTF-8': 'fr_FR.UTF-8',
|
|
'fr_CA.ISO8859-1': 'cs_CZ.ISO8859-1',
|
|
'ru_RU.UTF-8': 'ru_RU.UTF-8',
|
|
'zh_CN.UTF-8': 'zh_CN.UTF-8',
|
|
},
|
|
'FreeBSD': {
|
|
'en_US.UTF-8': 'en_US.UTF-8',
|
|
'cs_CZ.ISO8859-2': 'cs_CZ.ISO8859-2',
|
|
'fr_FR.UTF-8': 'fr_FR.UTF-8',
|
|
'fr_CA.ISO8859-1': 'fr_CA.ISO8859-1',
|
|
'ru_RU.UTF-8': 'ru_RU.UTF-8',
|
|
'zh_CN.UTF-8': 'zh_CN.UTF-8',
|
|
},
|
|
'Linux': {
|
|
'en_US.UTF-8': 'en_US.UTF-8',
|
|
'cs_CZ.ISO8859-2': 'cs_CZ.ISO-8859-2',
|
|
'fr_FR.UTF-8': 'fr_FR.UTF-8',
|
|
'fr_CA.ISO8859-1': 'fr_CA.ISO-8859-1',
|
|
'ru_RU.UTF-8': 'ru_RU.UTF-8',
|
|
'zh_CN.UTF-8': 'zh_CN.UTF-8',
|
|
},
|
|
'Windows': {
|
|
'en_US.UTF-8': 'English_United States.1252',
|
|
'cs_CZ.ISO8859-2': 'Czech_Czech Republic.1250',
|
|
'fr_FR.UTF-8': 'French_France.1252',
|
|
'fr_CA.ISO8859-1': 'French_Canada.1252',
|
|
'ru_RU.UTF-8': 'Russian_Russia.1251',
|
|
'zh_CN.UTF-8': 'Chinese_China.936',
|
|
},
|
|
}
|
|
|
|
default_locale = locale.setlocale(locale.LC_ALL)
|
|
for feature, loc in locales[platform.system()].items():
|
|
try:
|
|
locale.setlocale(locale.LC_ALL, loc)
|
|
self.config.available_features.add('locale.{0}'.format(feature))
|
|
except:
|
|
self.lit_config.warning('The locale {0} is not supported by '
|
|
'your platform. Some tests will be '
|
|
'unsupported.'.format(loc))
|
|
locale.setlocale(locale.LC_ALL, default_locale)
|
|
|
|
# Write an "available feature" that combines the triple when
|
|
# use_system_lib is enabled. This is so that we can easily write XFAIL
|
|
# markers for tests that are known to fail with versions of libc++ as
|
|
# were shipped with a particular triple.
|
|
if self.use_system_lib:
|
|
# Drop sub-major version components from the triple, because the
|
|
# current XFAIL handling expects exact matches for feature checks.
|
|
sanitized_triple = re.sub(
|
|
r"([^-]+)-([^-]+)-([^-.]+).*", r"\1-\2-\3",
|
|
self.config.target_triple)
|
|
self.config.available_features.add(
|
|
'with_system_lib=%s' % sanitized_triple)
|
|
|
|
def configure_compile_flags(self):
|
|
# Configure extra compiler flags.
|
|
self.compile_flags += ['-I' + self.src_root + '/include',
|
|
'-I' + self.src_root + '/test/support']
|
|
|
|
def configure_link_flags(self):
|
|
self.link_flags += ['-L' + self.obj_root + '/lib', '-lc++']
|
|
link_flags_str = self.get_lit_conf('link_flags')
|
|
if link_flags_str is None:
|
|
cxx_abi = self.get_lit_conf('cxx_abi', 'libcxxabi')
|
|
if cxx_abi == 'libstdc++':
|
|
self.link_flags += ['-lstdc++']
|
|
elif cxx_abi == 'libsupc++':
|
|
self.link_flags += ['-lsupc++']
|
|
elif cxx_abi == 'libcxxabi':
|
|
self.link_flags += ['-lc++abi']
|
|
elif cxx_abi == 'libcxxrt':
|
|
self.link_flags += ['-lcxxrt']
|
|
elif cxx_abi == 'none':
|
|
pass
|
|
else:
|
|
self.lit_config.fatal(
|
|
'C++ ABI setting %s unsupported for tests' % cxx_abi)
|
|
|
|
if sys.platform == 'darwin':
|
|
self.link_flags += ['-lSystem']
|
|
elif sys.platform == 'linux2':
|
|
self.link_flags += ['-lgcc_eh', '-lc', '-lm', '-lpthread',
|
|
'-lrt', '-lgcc_s']
|
|
elif sys.platform.startswith('freebsd'):
|
|
self.link_flags += ['-lc', '-lm', '-pthread', '-lgcc_s']
|
|
else:
|
|
self.lit_config.fatal("unrecognized system")
|
|
|
|
self.lit_config.note(
|
|
"inferred link_flags as: %r" % self.link_flags)
|
|
if link_flags_str:
|
|
self.link_flags += shlex.split(link_flags_str)
|
|
|
|
if sys.platform == 'linux2':
|
|
if not self.use_system_lib:
|
|
self.link_flags += ['-Wl,-R', self.obj_root + '/lib']
|
|
self.compile_flags += ['-D__STDC_FORMAT_MACROS',
|
|
'-D__STDC_LIMIT_MACROS',
|
|
'-D__STDC_CONSTANT_MACROS']
|
|
elif sys.platform.startswith('freebsd'):
|
|
if not self.use_system_lib:
|
|
self.link_flags += ['-Wl,-R', self.obj_root + '/lib']
|
|
|
|
def configure_std_flag(self):
|
|
# Try and get the std version from the command line. Fall back to
|
|
# default given in lit.site.cfg is not present. If default is not
|
|
# present then force c++11.
|
|
std = self.get_lit_conf('std')
|
|
if std is None:
|
|
std = 'c++11'
|
|
self.lit_config.note('using default std: \'-std=c++11\'')
|
|
self.compile_flags += ['-std={0}'.format(std)]
|
|
self.config.available_features.add(std)
|
|
|
|
def configure_sanitizer(self):
|
|
san = self.get_lit_conf('llvm_use_sanitizer', '').strip()
|
|
if san:
|
|
self.compile_flags += ['-fno-omit-frame-pointer']
|
|
if san == 'Address':
|
|
self.compile_flags += ['-fsanitize=address']
|
|
self.config.available_features.add('asan')
|
|
elif san == 'Memory' or san == 'MemoryWithOrigins':
|
|
self.compile_flags += ['-fsanitize=memory']
|
|
if san == 'MemoryWithOrigins':
|
|
self.compile_flags += ['-fsanitize-memory-track-origins']
|
|
self.config.available_features.add('msan')
|
|
else:
|
|
self.lit_config.fatal('unsupported value for '
|
|
'libcxx_use_san: {0}'.format(san))
|
|
|
|
def configure_triple(self):
|
|
# Get or infer the target triple.
|
|
self.config.target_triple = self.get_lit_conf('target_triple')
|
|
# If no target triple was given, try to infer it from the compiler
|
|
# under test.
|
|
if not self.config.target_triple:
|
|
self.config.target_triple = lit.util.capture(
|
|
[self.cxx, '-dumpmachine']).strip()
|
|
self.lit_config.note(
|
|
"inferred target_triple as: %r" % self.config.target_triple)
|
|
|
|
def configure_env(self):
|
|
# Configure extra linker parameters.
|
|
if sys.platform == 'darwin':
|
|
if not self.use_system_lib:
|
|
self.env['DYLD_LIBRARY_PATH'] = os.path.join(self.obj_root,
|
|
'lib')
|
|
|
|
|
|
# name: The name of this test suite.
|
|
config.name = 'libc++'
|
|
|
|
# suffixes: A list of file extensions to treat as test files.
|
|
config.suffixes = ['.cpp']
|
|
|
|
# test_source_root: The root path where tests are located.
|
|
config.test_source_root = os.path.dirname(__file__)
|
|
|
|
configuration = Configuration(lit_config, config)
|
|
configuration.configure()
|
|
config.test_format = configuration.get_test_format()
|