xref: /llvm-project/llvm/utils/lit/lit/discovery.py (revision 5ccfa15681300323effe314348bb415f6a59c97f)
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