1from __future__ import absolute_import
2import os
3import shlex
4import subprocess
5import sys
6
7import lit.Test
8import lit.TestRunner
9import lit.util
10from .base import TestFormat
11
12kIsWindows = sys.platform in ['win32', 'cygwin']
13
14class GoogleTest(TestFormat):
15    def __init__(self, test_sub_dirs, test_suffix, run_under = []):
16        self.test_sub_dirs = str(test_sub_dirs).split(';')
17
18        # On Windows, assume tests will also end in '.exe'.
19        exe_suffix = str(test_suffix)
20        if kIsWindows:
21            exe_suffix += '.exe'
22
23        # Also check for .py files for testing purposes.
24        self.test_suffixes = {exe_suffix, test_suffix + '.py'}
25        self.run_under = run_under
26
27    def getGTestTests(self, path, litConfig, localConfig):
28        """getGTestTests(path) - [name]
29
30        Return the tests available in gtest executable.
31
32        Args:
33          path: String path to a gtest executable
34          litConfig: LitConfig instance
35          localConfig: TestingConfig instance"""
36
37        list_test_cmd = self.prepareCmd([path, '--gtest_list_tests'])
38
39        try:
40            output = subprocess.check_output(list_test_cmd,
41                                             env=localConfig.environment)
42        except subprocess.CalledProcessError as exc:
43            litConfig.warning(
44                "unable to discover google-tests in %r: %s. Process output: %s"
45                % (path, sys.exc_info()[1], exc.output))
46            # This doesn't look like a valid gtest file.  This can
47            # have a number of causes, none of them good.  For
48            # instance, we could have created a broken executable.
49            # Alternatively, someone has cruft in their test
50            # directory.  If we don't return a test here, then no
51            # failures will get reported, so return a dummy test name
52            # so that the failure is reported later.
53            yield 'failed_to_discover_tests_from_gtest'
54            return
55
56        nested_tests = []
57        for ln in output.splitlines(False):  # Don't keep newlines.
58            ln = lit.util.to_string(ln)
59
60            if 'Running main() from gtest_main.cc' in ln:
61                # Upstream googletest prints this to stdout prior to running
62                # tests. LLVM removed that print statement in r61540, but we
63                # handle it here in case upstream googletest is being used.
64                continue
65
66            # The test name list includes trailing comments beginning with
67            # a '#' on some lines, so skip those. We don't support test names
68            # that use escaping to embed '#' into their name as the names come
69            # from C++ class and method names where such things are hard and
70            # uninteresting to support.
71            ln = ln.split('#', 1)[0].rstrip()
72            if not ln.lstrip():
73                continue
74
75            index = 0
76            while ln[index*2:index*2+2] == '  ':
77                index += 1
78            while len(nested_tests) > index:
79                nested_tests.pop()
80
81            ln = ln[index*2:]
82            if ln.endswith('.'):
83                nested_tests.append(ln)
84            elif any([name.startswith('DISABLED_')
85                      for name in nested_tests + [ln]]):
86                # Gtest will internally skip these tests. No need to launch a
87                # child process for it.
88                continue
89            else:
90                yield ''.join(nested_tests) + ln
91
92    def getTestsInDirectory(self, testSuite, path_in_suite,
93                            litConfig, localConfig):
94        source_path = testSuite.getSourcePath(path_in_suite)
95        for subdir in self.test_sub_dirs:
96            dir_path = os.path.join(source_path, subdir)
97            if not os.path.isdir(dir_path):
98                continue
99            for fn in lit.util.listdir_files(dir_path,
100                                             suffixes=self.test_suffixes):
101                # Discover the tests in this executable.
102                execpath = os.path.join(source_path, subdir, fn)
103                testnames = self.getGTestTests(execpath, litConfig, localConfig)
104                for testname in testnames:
105                    testPath = path_in_suite + (subdir, fn, testname)
106                    yield lit.Test.Test(testSuite, testPath, localConfig,
107                                        file_path=execpath)
108
109    def execute(self, test, litConfig):
110        testPath,testName = os.path.split(test.getSourcePath())
111        while not os.path.exists(testPath):
112            # Handle GTest parametrized and typed tests, whose name includes
113            # some '/'s.
114            testPath, namePrefix = os.path.split(testPath)
115            testName = namePrefix + '/' + testName
116
117        cmd = [testPath, '--gtest_filter=' + testName]
118        cmd = self.prepareCmd(cmd)
119        if litConfig.useValgrind:
120            cmd = litConfig.valgrindArgs + cmd
121
122        if litConfig.noExecute:
123            return lit.Test.PASS, ''
124
125        header = f"Script:\n--\n{' '.join(cmd)}\n--\n"
126
127        try:
128            out, err, exitCode = lit.util.executeCommand(
129                cmd, env=test.config.environment,
130                timeout=litConfig.maxIndividualTestTime)
131        except lit.util.ExecuteCommandTimeoutException:
132            return (lit.Test.TIMEOUT,
133                    f'{header}Reached timeout of '
134                    f'{litConfig.maxIndividualTestTime} seconds')
135
136        if exitCode:
137            return lit.Test.FAIL, header + out + err
138
139        if '[  SKIPPED ] 1 test,' in out:
140            return lit.Test.SKIPPED, ''
141
142        passing_test_line = '[  PASSED  ] 1 test.'
143        if passing_test_line not in out:
144            return (lit.Test.UNRESOLVED,
145                    f'{header}Unable to find {passing_test_line} '
146                    f'in gtest output:\n\n{out}{err}')
147
148        return lit.Test.PASS,''
149
150    def prepareCmd(self, cmd):
151        """Insert interpreter if needed.
152
153        It inserts the python exe into the command if cmd[0] ends in .py or caller
154        specified run_under.
155        We cannot rely on the system to interpret shebang lines for us on
156        Windows, so add the python executable to the command if this is a .py
157        script.
158        """
159        if cmd[0].endswith('.py'):
160            cmd = [sys.executable] + cmd
161        if self.run_under:
162            if isinstance(self.run_under, list):
163                cmd = self.run_under + cmd
164            else:
165                cmd = shlex.split(self.run_under) + cmd
166        return cmd
167