1from __future__ import absolute_import 2import inspect 3import os 4import platform 5import sys 6 7import lit.Test 8import lit.formats 9import lit.TestingConfig 10import lit.util 11 12# LitConfig must be a new style class for properties to work 13class LitConfig(object): 14 """LitConfig - Configuration data for a 'lit' test runner instance, shared 15 across all tests. 16 17 The LitConfig object is also used to communicate with client configuration 18 files, it is always passed in as the global variable 'lit' so that 19 configuration files can access common functionality and internal components 20 easily. 21 """ 22 23 def __init__( 24 self, 25 progname, 26 path, 27 quiet, 28 useValgrind, 29 valgrindLeakCheck, 30 valgrindArgs, 31 noExecute, 32 debug, 33 isWindows, 34 order, 35 params, 36 config_prefix=None, 37 maxIndividualTestTime=0, 38 parallelism_groups={}, 39 per_test_coverage=False, 40 gtest_sharding=True, 41 ): 42 # The name of the test runner. 43 self.progname = progname 44 # The items to add to the PATH environment variable. 45 self.path = [str(p) for p in path] 46 self.quiet = bool(quiet) 47 self.useValgrind = bool(useValgrind) 48 self.valgrindLeakCheck = bool(valgrindLeakCheck) 49 self.valgrindUserArgs = list(valgrindArgs) 50 self.noExecute = noExecute 51 self.debug = debug 52 self.isWindows = bool(isWindows) 53 self.order = order 54 self.params = dict(params) 55 self.bashPath = None 56 57 # Configuration files to look for when discovering test suites. 58 self.config_prefix = config_prefix or "lit" 59 self.suffixes = ["cfg.py", "cfg"] 60 self.config_names = ["%s.%s" % (self.config_prefix, x) for x in self.suffixes] 61 self.site_config_names = [ 62 "%s.site.%s" % (self.config_prefix, x) for x in self.suffixes 63 ] 64 self.local_config_names = [ 65 "%s.local.%s" % (self.config_prefix, x) for x in self.suffixes 66 ] 67 68 self.numErrors = 0 69 self.numWarnings = 0 70 71 self.valgrindArgs = [] 72 if self.useValgrind: 73 self.valgrindArgs = [ 74 "valgrind", 75 "-q", 76 "--run-libc-freeres=no", 77 "--tool=memcheck", 78 "--trace-children=yes", 79 "--error-exitcode=123", 80 ] 81 if self.valgrindLeakCheck: 82 self.valgrindArgs.append("--leak-check=full") 83 else: 84 # The default is 'summary'. 85 self.valgrindArgs.append("--leak-check=no") 86 self.valgrindArgs.extend(self.valgrindUserArgs) 87 88 self.maxIndividualTestTime = maxIndividualTestTime 89 self.parallelism_groups = parallelism_groups 90 self.per_test_coverage = per_test_coverage 91 self.gtest_sharding = bool(gtest_sharding) 92 93 @property 94 def maxIndividualTestTime(self): 95 """ 96 Interface for getting maximum time to spend executing 97 a single test 98 """ 99 return self._maxIndividualTestTime 100 101 @property 102 def maxIndividualTestTimeIsSupported(self): 103 """ 104 Returns a tuple (<supported> , <error message>) 105 where 106 `<supported>` is True if setting maxIndividualTestTime is supported 107 on the current host, returns False otherwise. 108 `<error message>` is an empty string if `<supported>` is True, 109 otherwise is contains a string describing why setting 110 maxIndividualTestTime is not supported. 111 """ 112 return lit.util.killProcessAndChildrenIsSupported() 113 114 @maxIndividualTestTime.setter 115 def maxIndividualTestTime(self, value): 116 """ 117 Interface for setting maximum time to spend executing 118 a single test 119 """ 120 if not isinstance(value, int): 121 self.fatal("maxIndividualTestTime must set to a value of type int.") 122 self._maxIndividualTestTime = value 123 if self.maxIndividualTestTime > 0: 124 # The current implementation needs psutil on some platforms to set 125 # a timeout per test. Check it's available. 126 # See lit.util.killProcessAndChildren() 127 supported, errormsg = self.maxIndividualTestTimeIsSupported 128 if not supported: 129 self.fatal("Setting a timeout per test not supported. " + errormsg) 130 elif self.maxIndividualTestTime < 0: 131 self.fatal("The timeout per test must be >= 0 seconds") 132 133 @property 134 def per_test_coverage(self): 135 """ 136 Interface for getting the per_test_coverage value 137 """ 138 return self._per_test_coverage 139 140 @per_test_coverage.setter 141 def per_test_coverage(self, value): 142 """ 143 Interface for setting the per_test_coverage value 144 """ 145 if not isinstance(value, bool): 146 self.fatal("per_test_coverage must set to a value of type bool.") 147 self._per_test_coverage = value 148 149 def load_config(self, config, path): 150 """load_config(config, path) - Load a config object from an alternate 151 path.""" 152 if self.debug: 153 self.note("load_config from %r" % path) 154 config.load_from_path(path, self) 155 return config 156 157 def getBashPath(self): 158 """getBashPath - Get the path to 'bash'""" 159 if self.bashPath is not None: 160 return self.bashPath 161 162 self.bashPath = lit.util.which("bash", os.pathsep.join(self.path)) 163 if self.bashPath is None: 164 self.bashPath = lit.util.which("bash") 165 166 if self.bashPath is None: 167 self.bashPath = "" 168 169 # Check whether the found version of bash is able to cope with paths in 170 # the host path format. If not, don't return it as it can't be used to 171 # run scripts. For example, WSL's bash.exe requires '/mnt/c/foo' rather 172 # than 'C:\\foo' or 'C:/foo'. 173 if self.isWindows and self.bashPath: 174 command = [ 175 self.bashPath, 176 "-c", 177 '[[ -f "%s" ]]' % self.bashPath.replace("\\", "\\\\"), 178 ] 179 _, _, exitCode = lit.util.executeCommand(command) 180 if exitCode: 181 self.note( 182 "bash command failed: %s" % (" ".join('"%s"' % c for c in command)) 183 ) 184 self.bashPath = "" 185 186 if not self.bashPath: 187 self.warning("Unable to find a usable version of bash.") 188 189 return self.bashPath 190 191 def getToolsPath(self, dir, paths, tools): 192 if dir is not None and os.path.isabs(dir) and os.path.isdir(dir): 193 if not lit.util.checkToolsPath(dir, tools): 194 return None 195 else: 196 dir = lit.util.whichTools(tools, paths) 197 198 # bash 199 self.bashPath = lit.util.which("bash", dir) 200 if self.bashPath is None: 201 self.bashPath = "" 202 203 return dir 204 205 def _write_message(self, kind, message): 206 # Get the file/line where this message was generated. 207 f = inspect.currentframe() 208 # Step out of _write_message, and then out of wrapper. 209 f = f.f_back.f_back 210 file = os.path.abspath(inspect.getsourcefile(f)) 211 line = inspect.getlineno(f) 212 sys.stderr.write( 213 "%s: %s:%d: %s: %s\n" % (self.progname, file, line, kind, message) 214 ) 215 if self.isWindows: 216 # In a git bash terminal, the writes to sys.stderr aren't visible 217 # on screen immediately. Flush them here to avoid broken/misoredered 218 # output. 219 sys.stderr.flush() 220 221 def substitute(self, string): 222 """substitute - Interpolate params into a string""" 223 try: 224 return string % self.params 225 except KeyError as e: 226 (key,) = e.args 227 self.fatal( 228 "unable to find %r parameter, use '--param=%s=VALUE'" % (key, key) 229 ) 230 231 def note(self, message): 232 if not self.quiet: 233 self._write_message("note", message) 234 235 def warning(self, message): 236 if not self.quiet: 237 self._write_message("warning", message) 238 self.numWarnings += 1 239 240 def error(self, message): 241 self._write_message("error", message) 242 self.numErrors += 1 243 244 def fatal(self, message): 245 self._write_message("fatal", message) 246 sys.exit(2) 247