xref: /netbsd-src/external/apache2/llvm/dist/llvm/utils/lit/lit/discovery.py (revision 82d56013d7b633d116a93943de88e08335357a7c)
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