| """ |
| Test discovery functions. |
| """ |
| |
| import copy |
| import os |
| import sys |
| |
| from lit.TestingConfig import TestingConfig |
| from lit import LitConfig, Test |
| |
| |
| def chooseConfigFileFromDir(dir, config_names): |
| for name in config_names: |
| p = os.path.join(dir, name) |
| if os.path.exists(p): |
| return p |
| return None |
| |
| |
| def dirContainsTestSuite(path, lit_config): |
| cfgpath = chooseConfigFileFromDir(path, lit_config.site_config_names) |
| if not cfgpath: |
| cfgpath = chooseConfigFileFromDir(path, lit_config.config_names) |
| return cfgpath |
| |
| |
| def getTestSuite(item, litConfig, cache): |
| """getTestSuite(item, litConfig, cache) -> (suite, relative_path) |
| |
| Find the test suite containing @arg item. |
| |
| @retval (None, ...) - Indicates no test suite contains @arg item. |
| @retval (suite, relative_path) - The suite that @arg item is in, and its |
| relative path inside that suite. |
| """ |
| |
| def search1(path): |
| # Check for a site config or a lit config. |
| cfgpath = dirContainsTestSuite(path, litConfig) |
| |
| # If we didn't find a config file, keep looking. |
| if not cfgpath: |
| parent, base = os.path.split(path) |
| if parent == path: |
| return (None, ()) |
| |
| ts, relative = search(parent) |
| return (ts, relative + (base,)) |
| |
| # This is a private builtin parameter which can be used to perform |
| # translation of configuration paths. Specifically, this parameter |
| # can be set to a dictionary that the discovery process will consult |
| # when it finds a configuration it is about to load. If the given |
| # path is in the map, the value of that key is a path to the |
| # configuration to load instead. |
| config_map = litConfig.params.get("config_map") |
| if config_map: |
| cfgpath = os.path.realpath(cfgpath) |
| target = config_map.get(os.path.normcase(cfgpath)) |
| if target: |
| cfgpath = target |
| |
| # We found a test suite, create a new config for it and load it. |
| if litConfig.debug: |
| litConfig.note("loading suite config %r" % cfgpath) |
| |
| cfg = TestingConfig.fromdefaults(litConfig) |
| cfg.load_from_path(cfgpath, litConfig) |
| source_root = os.path.realpath(cfg.test_source_root or path) |
| exec_root = os.path.realpath(cfg.test_exec_root or path) |
| return Test.TestSuite(cfg.name, source_root, exec_root, cfg), () |
| |
| def search(path): |
| # Check for an already instantiated test suite. |
| real_path = os.path.realpath(path) |
| res = cache.get(real_path) |
| if res is None: |
| cache[real_path] = res = search1(path) |
| return res |
| |
| # Canonicalize the path. |
| item = os.path.normpath(os.path.join(os.getcwd(), item)) |
| |
| # Skip files and virtual components. |
| components = [] |
| while not os.path.isdir(item): |
| parent, base = os.path.split(item) |
| if parent == item: |
| return (None, ()) |
| components.append(base) |
| item = parent |
| components.reverse() |
| |
| ts, relative = search(item) |
| return ts, tuple(relative + tuple(components)) |
| |
| |
| def getLocalConfig(ts, path_in_suite, litConfig, cache): |
| def search1(path_in_suite): |
| # Get the parent config. |
| if not path_in_suite: |
| parent = ts.config |
| else: |
| parent = search(path_in_suite[:-1]) |
| |
| # Check if there is a local configuration file. |
| source_path = ts.getSourcePath(path_in_suite) |
| cfgpath = chooseConfigFileFromDir(source_path, litConfig.local_config_names) |
| |
| # If not, just reuse the parent config. |
| if not cfgpath: |
| return parent |
| |
| # Otherwise, copy the current config and load the local configuration |
| # file into it. |
| config = copy.deepcopy(parent) |
| if litConfig.debug: |
| litConfig.note("loading local config %r" % cfgpath) |
| config.load_from_path(cfgpath, litConfig) |
| return config |
| |
| def search(path_in_suite): |
| key = (ts, path_in_suite) |
| res = cache.get(key) |
| if res is None: |
| cache[key] = res = search1(path_in_suite) |
| return res |
| |
| return search(path_in_suite) |
| |
| |
| def getTests(path, litConfig, testSuiteCache, localConfigCache, indirectlyRunCheck): |
| # Find the test suite for this input and its relative path. |
| ts, path_in_suite = getTestSuite(path, litConfig, testSuiteCache) |
| if ts is None: |
| litConfig.warning("unable to find test suite for %r" % path) |
| return (), () |
| |
| if litConfig.debug: |
| litConfig.note("resolved input %r to %r::%r" % (path, ts.name, path_in_suite)) |
| |
| return ts, getTestsInSuite( |
| ts, |
| path_in_suite, |
| litConfig, |
| testSuiteCache, |
| localConfigCache, |
| indirectlyRunCheck, |
| ) |
| |
| |
| def getTestsInSuite( |
| ts, path_in_suite, litConfig, testSuiteCache, localConfigCache, indirectlyRunCheck |
| ): |
| # Check that the source path exists (errors here are reported by the |
| # caller). |
| source_path = ts.getSourcePath(path_in_suite) |
| if not os.path.exists(source_path): |
| return |
| |
| # Check if the user named a test directly. |
| if not os.path.isdir(source_path): |
| test_dir_in_suite = path_in_suite[:-1] |
| lc = getLocalConfig(ts, test_dir_in_suite, litConfig, localConfigCache) |
| test = Test.Test(ts, path_in_suite, lc) |
| |
| # Issue a error if the specified test would not be run if |
| # the user had specified the containing directory instead of |
| # of naming the test directly. This helps to avoid writing |
| # tests which are not executed. The check adds some performance |
| # overhead which might be important if a large number of tests |
| # are being run directly. |
| # This check can be disabled by using --no-indirectly-run-check or |
| # setting the standalone_tests variable in the suite's configuration. |
| if ( |
| indirectlyRunCheck |
| and lc.test_format is not None |
| and not lc.standalone_tests |
| ): |
| found = False |
| for res in lc.test_format.getTestsInDirectory( |
| ts, test_dir_in_suite, litConfig, lc |
| ): |
| if test.getFullName() == res.getFullName(): |
| found = True |
| break |
| if not found: |
| litConfig.error( |
| "%r would not be run indirectly: change name or LIT config" |
| "(e.g. suffixes or standalone_tests variables)" % test.getFullName() |
| ) |
| |
| yield test |
| return |
| |
| # Otherwise we have a directory to search for tests, start by getting the |
| # local configuration. |
| lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache) |
| |
| # Directory contains tests to be run standalone. Do not try to discover. |
| if lc.standalone_tests: |
| if lc.suffixes or lc.excludes: |
| litConfig.warning( |
| "standalone_tests set in LIT config but suffixes or excludes" |
| " are also set" |
| ) |
| return |
| |
| # Search for tests. |
| if lc.test_format is not None: |
| for res in lc.test_format.getTestsInDirectory(ts, path_in_suite, litConfig, lc): |
| yield res |
| |
| # Search subdirectories. |
| for filename in os.listdir(source_path): |
| # FIXME: This doesn't belong here? |
| if filename in ("Output", ".svn", ".git") or filename in lc.excludes: |
| continue |
| |
| # Ignore non-directories. |
| file_sourcepath = os.path.join(source_path, filename) |
| if not os.path.isdir(file_sourcepath): |
| continue |
| |
| # Check for nested test suites, first in the execpath in case there is a |
| # site configuration and then in the source path. |
| subpath = path_in_suite + (filename,) |
| file_execpath = ts.getExecPath(subpath) |
| if dirContainsTestSuite(file_execpath, litConfig): |
| sub_ts, subpath_in_suite = getTestSuite( |
| file_execpath, litConfig, testSuiteCache |
| ) |
| elif dirContainsTestSuite(file_sourcepath, litConfig): |
| sub_ts, subpath_in_suite = getTestSuite( |
| file_sourcepath, litConfig, testSuiteCache |
| ) |
| else: |
| sub_ts = None |
| |
| # If the this directory recursively maps back to the current test suite, |
| # disregard it (this can happen if the exec root is located inside the |
| # current test suite, for example). |
| if sub_ts is ts: |
| continue |
| |
| # Otherwise, load from the nested test suite, if present. |
| if sub_ts is not None: |
| subiter = getTestsInSuite( |
| sub_ts, |
| subpath_in_suite, |
| litConfig, |
| testSuiteCache, |
| localConfigCache, |
| indirectlyRunCheck, |
| ) |
| else: |
| subiter = getTestsInSuite( |
| ts, |
| subpath, |
| litConfig, |
| testSuiteCache, |
| localConfigCache, |
| indirectlyRunCheck, |
| ) |
| |
| N = 0 |
| for res in subiter: |
| N += 1 |
| yield res |
| if sub_ts and not N: |
| litConfig.warning("test suite %r contained no tests" % sub_ts.name) |
| |
| |
| def find_tests_for_inputs(lit_config, inputs, indirectlyRunCheck): |
| """ |
| find_tests_for_inputs(lit_config, inputs) -> [Test] |
| |
| Given a configuration object and a list of input specifiers, find all the |
| tests to execute. |
| """ |
| |
| # Expand '@...' form in inputs. |
| actual_inputs = [] |
| for input in inputs: |
| if input.startswith("@"): |
| f = open(input[1:]) |
| try: |
| for ln in f: |
| ln = ln.strip() |
| if ln: |
| actual_inputs.append(ln) |
| finally: |
| f.close() |
| else: |
| actual_inputs.append(input) |
| |
| # Load the tests from the inputs. |
| tests = [] |
| test_suite_cache = {} |
| local_config_cache = {} |
| for input in actual_inputs: |
| prev = len(tests) |
| tests.extend( |
| getTests( |
| input, |
| lit_config, |
| test_suite_cache, |
| local_config_cache, |
| indirectlyRunCheck, |
| )[1] |
| ) |
| if prev == len(tests): |
| lit_config.warning("input %r contained no tests" % input) |
| |
| # This data is no longer needed but keeping it around causes awful |
| # performance problems while the test suites run. |
| for k, suite in test_suite_cache.items(): |
| if suite[0]: |
| suite[0].test_times = None |
| |
| # If there were any errors during test discovery, exit now. |
| if lit_config.numErrors: |
| sys.stderr.write("%d errors, exiting.\n" % lit_config.numErrors) |
| sys.exit(2) |
| |
| return tests |