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