1from __future__ import absolute_import
2import os
3import tempfile
4import subprocess
5import sys
6
7import lit.Test
8import lit.TestRunner
9import lit.util
10from lit.formats.base import TestFormat
11
12def getBuildDir(cmd):
13    found = False
14    for arg in cmd:
15        if found:
16            return arg
17        if arg == '--build-dir':
18            found = True
19    return None
20
21def mkdir_p(path):
22    import errno
23    try:
24        os.makedirs(path)
25    except OSError as e:
26        if e.errno != errno.EEXIST:
27            raise
28    if not os.path.isdir(path):
29        raise OSError(errno.ENOTDIR, "%s is not a directory"%path)
30
31class LLDBTest(TestFormat):
32    def __init__(self, dotest_cmd):
33        self.dotest_cmd = dotest_cmd
34
35    def getTestsInDirectory(self, testSuite, path_in_suite, litConfig,
36                            localConfig):
37        source_path = testSuite.getSourcePath(path_in_suite)
38        for filename in os.listdir(source_path):
39            # Ignore dot files and excluded tests.
40            if (filename.startswith('.') or filename in localConfig.excludes):
41                continue
42
43            # Ignore files that don't start with 'Test'.
44            if not filename.startswith('Test'):
45                continue
46
47            filepath = os.path.join(source_path, filename)
48            if not os.path.isdir(filepath):
49                base, ext = os.path.splitext(filename)
50                if ext in localConfig.suffixes:
51                    yield lit.Test.Test(testSuite, path_in_suite +
52                                        (filename, ), localConfig)
53
54    def execute(self, test, litConfig):
55        if litConfig.noExecute:
56            return lit.Test.PASS, ''
57
58        if not test.config.lldb_enable_python:
59            return (lit.Test.UNSUPPORTED, 'Python module disabled')
60
61        if test.config.unsupported:
62            return (lit.Test.UNSUPPORTED, 'Test is unsupported')
63
64        testPath, testFile = os.path.split(test.getSourcePath())
65
66        # The Python used to run lit can be different from the Python LLDB was
67        # build with.
68        executable = test.config.python_executable
69
70        # On Windows, the system does not always correctly interpret
71        # shebang lines.  To make sure we can execute the tests, add
72        # python exe as the first parameter of the command.
73        cmd = [executable] + self.dotest_cmd + [testPath, '-p', testFile]
74
75        builddir = getBuildDir(cmd)
76        mkdir_p(builddir)
77
78        # The macOS system integrity protection (SIP) doesn't allow injecting
79        # libraries into system binaries, but this can be worked around by
80        # copying the binary into a different location.
81        if 'DYLD_INSERT_LIBRARIES' in test.config.environment and \
82                (executable.startswith('/System/') or \
83                executable.startswith('/usr/bin/')):
84            copied_python = os.path.join(builddir, 'copied-system-python')
85            if not os.path.isfile(copied_python):
86                import shutil, subprocess
87                python = subprocess.check_output([
88                    executable,
89                    '-c',
90                    'import sys; print(sys.executable)'
91                ]).decode('utf-8').strip()
92                shutil.copy(python, copied_python)
93            cmd[0] = copied_python
94
95        if 'lldb-repro-capture' in test.config.available_features or \
96           'lldb-repro-replay' in test.config.available_features:
97            reproducer_root = os.path.join(builddir, 'reproducers')
98            mkdir_p(reproducer_root)
99            reproducer_path = os.path.join(reproducer_root, testFile)
100            if 'lldb-repro-capture' in test.config.available_features:
101                cmd.extend(['--capture-path', reproducer_path])
102            else:
103                cmd.extend(['--replay-path', reproducer_path])
104
105        timeoutInfo = None
106        try:
107            out, err, exitCode = lit.util.executeCommand(
108                cmd,
109                env=test.config.environment,
110                timeout=litConfig.maxIndividualTestTime)
111        except lit.util.ExecuteCommandTimeoutException as e:
112            out = e.out
113            err = e.err
114            exitCode = e.exitCode
115            timeoutInfo = 'Reached timeout of {} seconds'.format(
116                litConfig.maxIndividualTestTime)
117
118        if sys.version_info.major == 2:
119            # In Python 2, string objects can contain Unicode characters.
120            out = out.decode('utf-8')
121            err = err.decode('utf-8')
122
123        output = """Script:\n--\n%s\n--\nExit Code: %d\n""" % (
124            ' '.join(cmd), exitCode)
125        if timeoutInfo is not None:
126            output += """Timeout: %s\n""" % (timeoutInfo,)
127        output += "\n"
128
129        if out:
130            output += """Command Output (stdout):\n--\n%s\n--\n""" % (out,)
131        if err:
132            output += """Command Output (stderr):\n--\n%s\n--\n""" % (err,)
133
134        if timeoutInfo:
135            return lit.Test.TIMEOUT, output
136
137        if exitCode:
138            if 'XPASS:' in out or 'XPASS:' in err:
139                return lit.Test.XPASS, output
140
141            # Otherwise this is just a failure.
142            return lit.Test.FAIL, output
143
144        has_unsupported_tests = 'UNSUPPORTED:' in out or 'UNSUPPORTED:' in err
145        has_passing_tests = 'PASS:' in out or 'PASS:' in err
146        if has_unsupported_tests and not has_passing_tests:
147            return lit.Test.UNSUPPORTED, output
148
149        passing_test_line = 'RESULT: PASSED'
150        if passing_test_line not in out and passing_test_line not in err:
151            return lit.Test.UNRESOLVED, output
152
153        return lit.Test.PASS, output
154