1# -*- Python -*- vim: set syntax=python tabstop=4 expandtab cc=80:
2
3# Configuration file for the 'lit' test runner.
4
5import errno
6import os
7import platform
8import re
9import shlex
10import signal
11import subprocess
12import sys
13import tempfile
14import time
15
16import lit.Test
17import lit.formats
18import lit.util
19
20class 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.
170config.name = 'libc++'
171
172# suffixes: A list of file extensions to treat as test files.
173config.suffixes = ['.cpp']
174
175# test_source_root: The root path where tests are located.
176config.test_source_root = os.path.dirname(__file__)
177
178# Gather various compiler parameters.
179cxx_under_test = lit_config.params.get('cxx_under_test', None)
180if 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,))
189if 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
193libcxx_src_root = lit_config.params.get('libcxx_src_root', None)
194if 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
199libcxx_obj_root = lit_config.params.get('libcxx_obj_root', None)
200if 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
205cxx_has_stdcxx0x_flag_str = lit_config.params.get('cxx_has_stdcxx0x_flag', None)
206if 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')
214else:
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.
220use_system_lib_str = lit_config.params.get('use_system_lib', None)
221if 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')
228else:
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
233link_flags = []
234link_flags_str = lit_config.params.get('link_flags', None)
235if 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,))
259if not link_flags_str is None:
260    link_flags += shlex.split(link_flags_str)
261
262# Configure extra compiler flags.
263include_paths = ['-I' + libcxx_src_root + '/include',
264    '-I' + libcxx_src_root + '/test/support']
265library_paths = ['-L' + libcxx_obj_root + '/lib']
266compile_flags = []
267if cxx_has_stdcxx0x_flag:
268    compile_flags += ['-std=c++0x']
269
270# Configure extra linker parameters.
271exec_env = {}
272if sys.platform == 'darwin':
273    if not use_system_lib:
274        exec_env['DYLD_LIBRARY_PATH'] = os.path.join(libcxx_obj_root, 'lib')
275elif 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']
280else:
281    lit_config.fatal("unrecognized system")
282
283config.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.
290config.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.
292if 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.
301if 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