Home | History | Annotate | Download | only in test
      1 # -*- Python -*- vim: set syntax=python tabstop=4 expandtab cc=80:
      2 
      3 # Configuration file for the 'lit' test runner.
      4 
      5 import errno
      6 import os
      7 import platform
      8 import re
      9 import shlex
     10 import signal
     11 import subprocess
     12 import sys
     13 import tempfile
     14 import time
     15 
     16 import lit.Test
     17 import lit.formats
     18 import lit.util
     19 
     20 class LibcxxTestFormat(lit.formats.FileBasedTest):
     21     """
     22     Custom test format handler for use with the test format use by libc++.
     23 
     24     Tests fall into two categories:
     25       FOO.pass.cpp - Executable test which should compile, run, and exit with
     26                      code 0.
     27       FOO.fail.cpp - Negative test case which is expected to fail compilation.
     28     """
     29 
     30     def __init__(self, cxx_under_test, cpp_flags, ld_flags, exec_env):
     31         self.cxx_under_test = cxx_under_test
     32         self.cpp_flags = list(cpp_flags)
     33         self.ld_flags = list(ld_flags)
     34         self.exec_env = dict(exec_env)
     35 
     36     def execute_command(self, command, in_dir=None):
     37         kwargs = {
     38             'stdin' :subprocess.PIPE,
     39             'stdout':subprocess.PIPE,
     40             'stderr':subprocess.PIPE,
     41         }
     42         if in_dir:
     43             kwargs['cwd'] = in_dir
     44         p = subprocess.Popen(command, **kwargs)
     45         out,err = p.communicate()
     46         exitCode = p.wait()
     47 
     48         # Detect Ctrl-C in subprocess.
     49         if exitCode == -signal.SIGINT:
     50             raise KeyboardInterrupt
     51 
     52         return out, err, exitCode
     53 
     54     def execute(self, test, lit_config):
     55         while True:
     56             try:
     57                 return self._execute(test, lit_config)
     58             except OSError, oe:
     59                 if oe.errno != errno.ETXTBSY:
     60                     raise
     61                 time.sleep(0.1)
     62 
     63     def _execute(self, test, lit_config):
     64         # Extract test metadata from the test file.
     65         requires = []
     66         with open(test.getSourcePath()) as f:
     67             for ln in f:
     68                 if 'XFAIL:' in ln:
     69                     items = ln[ln.index('XFAIL:') + 6:].split(',')
     70                     test.xfails.extend([s.strip() for s in items])
     71                 elif 'REQUIRES:' in ln:
     72                     items = ln[ln.index('REQUIRES:') + 9:].split(',')
     73                     requires.extend([s.strip() for s in items])
     74                 elif not ln.startswith("//") and ln.strip():
     75                     # Stop at the first non-empty line that is not a C++
     76                     # comment.
     77                     break
     78 
     79         # Check that we have the required features.
     80         #
     81         # FIXME: For now, this is cribbed from lit.TestRunner, to avoid
     82         # introducing a dependency there. What we more ideally would like to do
     83         # is lift the "requires" handling to be a core lit framework feature.
     84         missing_required_features = [f for f in requires
     85                                      if f not in test.config.available_features]
     86         if missing_required_features:
     87             return (lit.Test.UNSUPPORTED,
     88                     "Test requires the following features: %s" % (
     89                       ', '.join(missing_required_features),))
     90 
     91         # Evaluate the test.
     92         return self._evaluate_test(test, lit_config)
     93 
     94     def _evaluate_test(self, test, lit_config):
     95         name = test.path_in_suite[-1]
     96         source_path = test.getSourcePath()
     97         source_dir = os.path.dirname(source_path)
     98 
     99         # Check what kind of test this is.
    100         assert name.endswith('.pass.cpp') or name.endswith('.fail.cpp')
    101         expected_compile_fail = name.endswith('.fail.cpp')
    102 
    103         # If this is a compile (failure) test, build it and check for failure.
    104         if expected_compile_fail:
    105             cmd = [self.cxx_under_test, '-c',
    106                    '-o', '/dev/null', source_path] + self.cpp_flags
    107             out, err, exitCode = self.execute_command(cmd)
    108             if exitCode == 1:
    109                 return lit.Test.PASS, ""
    110             else:
    111                 report = """Command: %s\n""" % ' '.join(["'%s'" % a
    112                                                          for a in cmd])
    113                 report += """Exit Code: %d\n""" % exitCode
    114                 if out:
    115                     report += """Standard Output:\n--\n%s--""" % out
    116                 if err:
    117                     report += """Standard Error:\n--\n%s--""" % err
    118                 report += "\n\nExpected compilation to fail!"
    119                 return lit.Test.FAIL, report
    120         else:
    121             exec_file = tempfile.NamedTemporaryFile(suffix="exe", delete=False)
    122             exec_path = exec_file.name
    123             exec_file.close()
    124 
    125             try:
    126                 compile_cmd = [self.cxx_under_test, '-o', exec_path,
    127                        source_path] + self.cpp_flags + self.ld_flags
    128                 cmd = compile_cmd
    129                 out, err, exitCode = self.execute_command(cmd)
    130                 if exitCode != 0:
    131                     report = """Command: %s\n""" % ' '.join(["'%s'" % a
    132                                                              for a in cmd])
    133                     report += """Exit Code: %d\n""" % exitCode
    134                     if out:
    135                         report += """Standard Output:\n--\n%s--""" % out
    136                     if err:
    137                         report += """Standard Error:\n--\n%s--""" % err
    138                     report += "\n\nCompilation failed unexpectedly!"
    139                     return lit.Test.FAIL, report
    140 
    141                 cmd = []
    142                 if self.exec_env:
    143                     cmd.append('env')
    144                     cmd.extend('%s=%s' % (name, value)
    145                                for name,value in self.exec_env.items())
    146                 cmd.append(exec_path)
    147                 if lit_config.useValgrind:
    148                     cmd = lit_config.valgrindArgs + cmd
    149                 out, err, exitCode = self.execute_command(cmd, source_dir)
    150                 if exitCode != 0:
    151                     report = """Compiled With: %s\n""" % \
    152                         ' '.join(["'%s'" % a for a in compile_cmd])
    153                     report += """Command: %s\n""" % \
    154                         ' '.join(["'%s'" % a for a in cmd])
    155                     report += """Exit Code: %d\n""" % exitCode
    156                     if out:
    157                         report += """Standard Output:\n--\n%s--""" % out
    158                     if err:
    159                         report += """Standard Error:\n--\n%s--""" % err
    160                     report += "\n\nCompiled test failed unexpectedly!"
    161                     return lit.Test.FAIL, report
    162             finally:
    163                 try:
    164                     os.remove(exec_path)
    165                 except:
    166                     pass
    167         return lit.Test.PASS, ""
    168 
    169 # name: The name of this test suite.
    170 config.name = 'libc++'
    171 
    172 # suffixes: A list of file extensions to treat as test files.
    173 config.suffixes = ['.cpp']
    174 
    175 # test_source_root: The root path where tests are located.
    176 config.test_source_root = os.path.dirname(__file__)
    177 
    178 # Gather various compiler parameters.
    179 cxx_under_test = lit_config.params.get('cxx_under_test', None)
    180 if cxx_under_test is None:
    181     cxx_under_test = getattr(config, 'cxx_under_test', None)
    182 
    183     # If no specific cxx_under_test was given, attempt to infer it as clang++.
    184     if cxx_under_test is None:
    185         clangxx = lit.util.which('clang++', config.environment['PATH'])
    186         if clangxx is not None:
    187             cxx_under_test = clangxx
    188     lit_config.note("inferred cxx_under_test as: %r" % (cxx_under_test,))
    189 if cxx_under_test is None:
    190     lit_config.fatal('must specify user parameter cxx_under_test '
    191                      '(e.g., --param=cxx_under_test=clang++)')
    192 
    193 libcxx_src_root = lit_config.params.get('libcxx_src_root', None)
    194 if libcxx_src_root is None:
    195     libcxx_src_root = getattr(config, 'libcxx_src_root', None)
    196     if libcxx_src_root is None:
    197         libcxx_src_root = os.path.dirname(config.test_source_root)
    198 
    199 libcxx_obj_root = lit_config.params.get('libcxx_obj_root', None)
    200 if libcxx_obj_root is None:
    201     libcxx_obj_root = getattr(config, 'libcxx_obj_root', None)
    202     if libcxx_obj_root is None:
    203         libcxx_obj_root = libcxx_src_root
    204 
    205 cxx_has_stdcxx0x_flag_str = lit_config.params.get('cxx_has_stdcxx0x_flag', None)
    206 if cxx_has_stdcxx0x_flag_str is not None:
    207     if cxx_has_stdcxx0x_flag_str.lower() in ('1', 'true'):
    208         cxx_has_stdcxx0x_flag = True
    209     elif cxx_has_stdcxx0x_flag_str.lower() in ('', '0', 'false'):
    210         cxx_has_stdcxx0x_flag = False
    211     else:
    212         lit_config.fatal(
    213             'user parameter cxx_has_stdcxx0x_flag_str should be 0 or 1')
    214 else:
    215     cxx_has_stdcxx0x_flag = getattr(config, 'cxx_has_stdcxx0x_flag', True)
    216 
    217 # This test suite supports testing against either the system library or the
    218 # locally built one; the former mode is useful for testing ABI compatibility
    219 # between the current headers and a shipping dynamic library.
    220 use_system_lib_str = lit_config.params.get('use_system_lib', None)
    221 if use_system_lib_str is not None:
    222     if use_system_lib_str.lower() in ('1', 'true'):
    223         use_system_lib = True
    224     elif use_system_lib_str.lower() in ('', '0', 'false'):
    225         use_system_lib = False
    226     else:
    227         lit_config.fatal('user parameter use_system_lib should be 0 or 1')
    228 else:
    229     # Default to testing against the locally built libc++ library.
    230     use_system_lib = False
    231     lit_config.note("inferred use_system_lib as: %r" % (use_system_lib,))
    232 
    233 link_flags = []
    234 link_flags_str = lit_config.params.get('link_flags', None)
    235 if link_flags_str is None:
    236     link_flags_str = getattr(config, 'link_flags', None)
    237     if link_flags_str is None:
    238       cxx_abi = getattr(config, 'cxx_abi', 'libcxxabi')
    239       if cxx_abi == 'libstdc++':
    240         link_flags += ['-lstdc++']
    241       elif cxx_abi == 'libsupc++':
    242         link_flags += ['-lsupc++']
    243       elif cxx_abi == 'libcxxabi':
    244         link_flags += ['-lc++abi']
    245       elif cxx_abi == 'none':
    246         pass
    247       else:
    248         lit_config.fatal('C++ ABI setting %s unsupported for tests' % cxx_abi)
    249 
    250       if sys.platform == 'darwin':
    251         link_flags += ['-lSystem']
    252       elif sys.platform == 'linux2':
    253         link_flags += [ '-lgcc_eh', '-lc', '-lm', '-lpthread',
    254               '-lrt', '-lgcc_s']
    255       else:
    256         lit_config.fatal("unrecognized system")
    257 
    258       lit_config.note("inferred link_flags as: %r" % (link_flags,))
    259 if not link_flags_str is None:
    260     link_flags += shlex.split(link_flags_str)
    261 
    262 # Configure extra compiler flags.
    263 include_paths = ['-I' + libcxx_src_root + '/include',
    264     '-I' + libcxx_src_root + '/test/support']
    265 library_paths = ['-L' + libcxx_obj_root + '/lib']
    266 compile_flags = []
    267 if cxx_has_stdcxx0x_flag:
    268     compile_flags += ['-std=c++0x']
    269 
    270 # Configure extra linker parameters.
    271 exec_env = {}
    272 if sys.platform == 'darwin':
    273     if not use_system_lib:
    274         exec_env['DYLD_LIBRARY_PATH'] = os.path.join(libcxx_obj_root, 'lib')
    275 elif sys.platform == 'linux2':
    276     if not use_system_lib:
    277         link_flags += ['-Wl,-R', libcxx_obj_root + '/lib']
    278     compile_flags += ['-D__STDC_FORMAT_MACROS', '-D__STDC_LIMIT_MACROS',
    279         '-D__STDC_CONSTANT_MACROS']
    280 else:
    281     lit_config.fatal("unrecognized system")
    282 
    283 config.test_format = LibcxxTestFormat(
    284     cxx_under_test,
    285     cpp_flags = ['-nostdinc++'] + compile_flags + include_paths,
    286     ld_flags = ['-nodefaultlibs'] + library_paths + ['-lc++'] + link_flags,
    287     exec_env = exec_env)
    288 
    289 # Get or infer the target triple.
    290 config.target_triple = lit_config.params.get('target_triple', None)
    291 # If no target triple was given, try to infer it from the compiler under test.
    292 if config.target_triple is None:
    293     config.target_triple = lit.util.capture(
    294         [cxx_under_test, '-dumpmachine']).strip()
    295     lit_config.note("inferred target_triple as: %r" % (config.target_triple,))
    296 
    297 # Write an "available feature" that combines the triple when use_system_lib is
    298 # enabled. This is so that we can easily write XFAIL markers for tests that are
    299 # known to fail with versions of libc++ as were shipped with a particular
    300 # triple.
    301 if use_system_lib:
    302     # Drop sub-major version components from the triple, because the current
    303     # XFAIL handling expects exact matches for feature checks.
    304     sanitized_triple = re.sub(r"([^-]+)-([^-]+)-([^-.]+).*", r"\1-\2-\3",
    305                               config.target_triple)
    306     config.available_features.add('with_system_lib=%s' % (sanitized_triple,))
    307