1""" 2Test discovery functions. 3""" 4 5import copy 6import os 7import sys 8 9from lit.TestingConfig import TestingConfig 10from lit import LitConfig, Test 11 12def chooseConfigFileFromDir(dir, config_names): 13 for name in config_names: 14 p = os.path.join(dir, name) 15 if os.path.exists(p): 16 return p 17 return None 18 19def dirContainsTestSuite(path, lit_config): 20 cfgpath = chooseConfigFileFromDir(path, lit_config.site_config_names) 21 if not cfgpath: 22 cfgpath = chooseConfigFileFromDir(path, lit_config.config_names) 23 return cfgpath 24 25def getTestSuite(item, litConfig, cache): 26 """getTestSuite(item, litConfig, cache) -> (suite, relative_path) 27 28 Find the test suite containing @arg item. 29 30 @retval (None, ...) - Indicates no test suite contains @arg item. 31 @retval (suite, relative_path) - The suite that @arg item is in, and its 32 relative path inside that suite. 33 """ 34 def search1(path): 35 # Check for a site config or a lit config. 36 cfgpath = dirContainsTestSuite(path, litConfig) 37 38 # If we didn't find a config file, keep looking. 39 if not cfgpath: 40 parent,base = os.path.split(path) 41 if parent == path: 42 return (None, ()) 43 44 ts, relative = search(parent) 45 return (ts, relative + (base,)) 46 47 # This is a private builtin parameter which can be used to perform 48 # translation of configuration paths. Specifically, this parameter 49 # can be set to a dictionary that the discovery process will consult 50 # when it finds a configuration it is about to load. If the given 51 # path is in the map, the value of that key is a path to the 52 # configuration to load instead. 53 config_map = litConfig.params.get('config_map') 54 if config_map: 55 cfgpath = os.path.realpath(cfgpath) 56 target = config_map.get(os.path.normcase(cfgpath)) 57 if target: 58 cfgpath = target 59 60 # We found a test suite, create a new config for it and load it. 61 if litConfig.debug: 62 litConfig.note('loading suite config %r' % cfgpath) 63 64 cfg = TestingConfig.fromdefaults(litConfig) 65 cfg.load_from_path(cfgpath, litConfig) 66 source_root = os.path.realpath(cfg.test_source_root or path) 67 exec_root = os.path.realpath(cfg.test_exec_root or path) 68 return Test.TestSuite(cfg.name, source_root, exec_root, cfg), () 69 70 def search(path): 71 # Check for an already instantiated test suite. 72 real_path = os.path.realpath(path) 73 res = cache.get(real_path) 74 if res is None: 75 cache[real_path] = res = search1(path) 76 return res 77 78 # Canonicalize the path. 79 item = os.path.normpath(os.path.join(os.getcwd(), item)) 80 81 # Skip files and virtual components. 82 components = [] 83 while not os.path.isdir(item): 84 parent,base = os.path.split(item) 85 if parent == item: 86 return (None, ()) 87 components.append(base) 88 item = parent 89 components.reverse() 90 91 ts, relative = search(item) 92 return ts, tuple(relative + tuple(components)) 93 94def getLocalConfig(ts, path_in_suite, litConfig, cache): 95 def search1(path_in_suite): 96 # Get the parent config. 97 if not path_in_suite: 98 parent = ts.config 99 else: 100 parent = search(path_in_suite[:-1]) 101 102 # Check if there is a local configuration file. 103 source_path = ts.getSourcePath(path_in_suite) 104 cfgpath = chooseConfigFileFromDir(source_path, litConfig.local_config_names) 105 106 # If not, just reuse the parent config. 107 if not cfgpath: 108 return parent 109 110 # Otherwise, copy the current config and load the local configuration 111 # file into it. 112 config = copy.deepcopy(parent) 113 if litConfig.debug: 114 litConfig.note('loading local config %r' % cfgpath) 115 config.load_from_path(cfgpath, litConfig) 116 return config 117 118 def search(path_in_suite): 119 key = (ts, path_in_suite) 120 res = cache.get(key) 121 if res is None: 122 cache[key] = res = search1(path_in_suite) 123 return res 124 125 return search(path_in_suite) 126 127def getTests(path, litConfig, testSuiteCache, 128 localConfigCache, indirectlyRunCheck): 129 # Find the test suite for this input and its relative path. 130 ts,path_in_suite = getTestSuite(path, litConfig, testSuiteCache) 131 if ts is None: 132 litConfig.warning('unable to find test suite for %r' % path) 133 return (),() 134 135 if litConfig.debug: 136 litConfig.note('resolved input %r to %r::%r' % (path, ts.name, 137 path_in_suite)) 138 139 return ts, getTestsInSuite(ts, path_in_suite, litConfig, 140 testSuiteCache, localConfigCache, indirectlyRunCheck) 141 142def getTestsInSuite(ts, path_in_suite, litConfig, 143 testSuiteCache, localConfigCache, indirectlyRunCheck): 144 # Check that the source path exists (errors here are reported by the 145 # caller). 146 source_path = ts.getSourcePath(path_in_suite) 147 if not os.path.exists(source_path): 148 return 149 150 # Check if the user named a test directly. 151 if not os.path.isdir(source_path): 152 test_dir_in_suite = path_in_suite[:-1] 153 lc = getLocalConfig(ts, test_dir_in_suite, litConfig, localConfigCache) 154 test = Test.Test(ts, path_in_suite, lc) 155 156 # Issue a error if the specified test would not be run if 157 # the user had specified the containing directory instead of 158 # of naming the test directly. This helps to avoid writing 159 # tests which are not executed. The check adds some performance 160 # overhead which might be important if a large number of tests 161 # are being run directly. 162 # This check can be disabled by using --no-indirectly-run-check or 163 # setting the standalone_tests variable in the suite's configuration. 164 if ( 165 indirectlyRunCheck 166 and lc.test_format is not None 167 and not lc.standalone_tests 168 ): 169 found = False 170 for res in lc.test_format.getTestsInDirectory(ts, test_dir_in_suite, 171 litConfig, lc): 172 if test.getFullName() == res.getFullName(): 173 found = True 174 break 175 if not found: 176 litConfig.error( 177 '%r would not be run indirectly: change name or LIT config' 178 '(e.g. suffixes or standalone_tests variables)' 179 % test.getFullName()) 180 181 yield test 182 return 183 184 # Otherwise we have a directory to search for tests, start by getting the 185 # local configuration. 186 lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache) 187 188 # Directory contains tests to be run standalone. Do not try to discover. 189 if lc.standalone_tests: 190 if lc.suffixes or lc.excludes: 191 litConfig.warning( 192 'standalone_tests set in LIT config but suffixes or excludes' 193 ' are also set' 194 ) 195 return 196 197 # Search for tests. 198 if lc.test_format is not None: 199 for res in lc.test_format.getTestsInDirectory(ts, path_in_suite, 200 litConfig, lc): 201 yield res 202 203 # Search subdirectories. 204 for filename in os.listdir(source_path): 205 # FIXME: This doesn't belong here? 206 if filename in ('Output', '.svn', '.git') or filename in lc.excludes: 207 continue 208 209 # Ignore non-directories. 210 file_sourcepath = os.path.join(source_path, filename) 211 if not os.path.isdir(file_sourcepath): 212 continue 213 214 # Check for nested test suites, first in the execpath in case there is a 215 # site configuration and then in the source path. 216 subpath = path_in_suite + (filename,) 217 file_execpath = ts.getExecPath(subpath) 218 if dirContainsTestSuite(file_execpath, litConfig): 219 sub_ts, subpath_in_suite = getTestSuite(file_execpath, litConfig, 220 testSuiteCache) 221 elif dirContainsTestSuite(file_sourcepath, litConfig): 222 sub_ts, subpath_in_suite = getTestSuite(file_sourcepath, litConfig, 223 testSuiteCache) 224 else: 225 sub_ts = None 226 227 # If the this directory recursively maps back to the current test suite, 228 # disregard it (this can happen if the exec root is located inside the 229 # current test suite, for example). 230 if sub_ts is ts: 231 continue 232 233 # Otherwise, load from the nested test suite, if present. 234 if sub_ts is not None: 235 subiter = getTestsInSuite(sub_ts, subpath_in_suite, litConfig, 236 testSuiteCache, localConfigCache, 237 indirectlyRunCheck) 238 else: 239 subiter = getTestsInSuite(ts, subpath, litConfig, testSuiteCache, 240 localConfigCache, indirectlyRunCheck) 241 242 N = 0 243 for res in subiter: 244 N += 1 245 yield res 246 if sub_ts and not N: 247 litConfig.warning('test suite %r contained no tests' % sub_ts.name) 248 249def find_tests_for_inputs(lit_config, inputs, indirectlyRunCheck): 250 """ 251 find_tests_for_inputs(lit_config, inputs) -> [Test] 252 253 Given a configuration object and a list of input specifiers, find all the 254 tests to execute. 255 """ 256 257 # Expand '@...' form in inputs. 258 actual_inputs = [] 259 for input in inputs: 260 if input.startswith('@'): 261 f = open(input[1:]) 262 try: 263 for ln in f: 264 ln = ln.strip() 265 if ln: 266 actual_inputs.append(ln) 267 finally: 268 f.close() 269 else: 270 actual_inputs.append(input) 271 272 # Load the tests from the inputs. 273 tests = [] 274 test_suite_cache = {} 275 local_config_cache = {} 276 for input in actual_inputs: 277 prev = len(tests) 278 tests.extend(getTests(input, lit_config, test_suite_cache, 279 local_config_cache, indirectlyRunCheck)[1]) 280 if prev == len(tests): 281 lit_config.warning('input %r contained no tests' % input) 282 283 # This data is no longer needed but keeping it around causes awful 284 # performance problems while the test suites run. 285 for k, suite in test_suite_cache.items(): 286 if suite[0]: 287 suite[0].test_times = None 288 289 # If there were any errors during test discovery, exit now. 290 if lit_config.numErrors: 291 sys.stderr.write('%d errors, exiting.\n' % lit_config.numErrors) 292 sys.exit(2) 293 294 return tests 295