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