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