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