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