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