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