1"""
2Test discovery functions.
3"""
4
5import copy
6import os
7import sys
8
9import lit70.run
10from lit70.TestingConfig import TestingConfig
11from lit70 import LitConfig, Test
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
20def dirContainsTestSuite(path, lit_config):
21    cfgpath = chooseConfigFileFromDir(path, lit_config.site_config_names)
22    if not cfgpath:
23        cfgpath = chooseConfigFileFromDir(path, lit_config.config_names)
24    return cfgpath
25
26def getTestSuite(item, litConfig, cache):
27    """getTestSuite(item, litConfig, cache) -> (suite, relative_path)
28
29    Find the test suite containing @arg item.
30
31    @retval (None, ...) - Indicates no test suite contains @arg item.
32    @retval (suite, relative_path) - The suite that @arg item is in, and its
33    relative path inside that suite.
34    """
35    def search1(path):
36        # Check for a site config or a lit config.
37        cfgpath = dirContainsTestSuite(path, litConfig)
38
39        # If we didn't find a config file, keep looking.
40        if not cfgpath:
41            parent,base = os.path.split(path)
42            if parent == path:
43                return (None, ())
44
45            ts, relative = search(parent)
46            return (ts, relative + (base,))
47
48        # This is a private builtin parameter which can be used to perform
49        # translation of configuration paths.  Specifically, this parameter
50        # can be set to a dictionary that the discovery process will consult
51        # when it finds a configuration it is about to load.  If the given
52        # path is in the map, the value of that key is a path to the
53        # configuration to load instead.
54        config_map = litConfig.params.get('config_map')
55        if config_map:
56            cfgpath = os.path.realpath(cfgpath)
57            cfgpath = os.path.normcase(cfgpath)
58            target = config_map.get(cfgpath)
59            if target:
60                cfgpath = target
61
62        # We found a test suite, create a new config for it and load it.
63        if litConfig.debug:
64            litConfig.note('loading suite config %r' % cfgpath)
65
66        cfg = TestingConfig.fromdefaults(litConfig)
67        cfg.load_from_path(cfgpath, litConfig)
68        source_root = os.path.realpath(cfg.test_source_root or path)
69        exec_root = os.path.realpath(cfg.test_exec_root or path)
70        return Test.TestSuite(cfg.name, source_root, exec_root, cfg), ()
71
72    def search(path):
73        # Check for an already instantiated test suite.
74        real_path = os.path.realpath(path)
75        res = cache.get(real_path)
76        if res is None:
77            cache[real_path] = res = search1(path)
78        return res
79
80    # Canonicalize the path.
81    item = os.path.normpath(os.path.join(os.getcwd(), item))
82
83    # Skip files and virtual components.
84    components = []
85    while not os.path.isdir(item):
86        parent,base = os.path.split(item)
87        if parent == item:
88            return (None, ())
89        components.append(base)
90        item = parent
91    components.reverse()
92
93    ts, relative = search(item)
94    return ts, tuple(relative + tuple(components))
95
96def getLocalConfig(ts, path_in_suite, litConfig, cache):
97    def search1(path_in_suite):
98        # Get the parent config.
99        if not path_in_suite:
100            parent = ts.config
101        else:
102            parent = search(path_in_suite[:-1])
103
104        # Check if there is a local configuration file.
105        source_path = ts.getSourcePath(path_in_suite)
106        cfgpath = chooseConfigFileFromDir(source_path, litConfig.local_config_names)
107
108        # If not, just reuse the parent config.
109        if not cfgpath:
110            return parent
111
112        # Otherwise, copy the current config and load the local configuration
113        # file into it.
114        config = copy.deepcopy(parent)
115        if litConfig.debug:
116            litConfig.note('loading local config %r' % cfgpath)
117        config.load_from_path(cfgpath, litConfig)
118        return config
119
120    def search(path_in_suite):
121        key = (ts, path_in_suite)
122        res = cache.get(key)
123        if res is None:
124            cache[key] = res = search1(path_in_suite)
125        return res
126
127    return search(path_in_suite)
128
129def getTests(path, litConfig, testSuiteCache, localConfigCache):
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)
142
143def getTestsInSuite(ts, path_in_suite, litConfig,
144                    testSuiteCache, localConfigCache):
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        lc = getLocalConfig(ts, path_in_suite[:-1], litConfig, localConfigCache)
154        yield Test.Test(ts, path_in_suite, lc)
155        return
156
157    # Otherwise we have a directory to search for tests, start by getting the
158    # local configuration.
159    lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache)
160
161    # Search for tests.
162    if lc.test_format is not None:
163        for res in lc.test_format.getTestsInDirectory(ts, path_in_suite,
164                                                      litConfig, lc):
165            yield res
166
167    # Search subdirectories.
168    for filename in os.listdir(source_path):
169        # FIXME: This doesn't belong here?
170        if filename in ('Output', '.svn', '.git') or filename in lc.excludes:
171            continue
172
173        # Ignore non-directories.
174        file_sourcepath = os.path.join(source_path, filename)
175        if not os.path.isdir(file_sourcepath):
176            continue
177
178        # Check for nested test suites, first in the execpath in case there is a
179        # site configuration and then in the source path.
180        subpath = path_in_suite + (filename,)
181        file_execpath = ts.getExecPath(subpath)
182        if dirContainsTestSuite(file_execpath, litConfig):
183            sub_ts, subpath_in_suite = getTestSuite(file_execpath, litConfig,
184                                                    testSuiteCache)
185        elif dirContainsTestSuite(file_sourcepath, litConfig):
186            sub_ts, subpath_in_suite = getTestSuite(file_sourcepath, litConfig,
187                                                    testSuiteCache)
188        else:
189            sub_ts = None
190
191        # If the this directory recursively maps back to the current test suite,
192        # disregard it (this can happen if the exec root is located inside the
193        # current test suite, for example).
194        if sub_ts is ts:
195            continue
196
197        # Otherwise, load from the nested test suite, if present.
198        if sub_ts is not None:
199            subiter = getTestsInSuite(sub_ts, subpath_in_suite, litConfig,
200                                      testSuiteCache, localConfigCache)
201        else:
202            subiter = getTestsInSuite(ts, subpath, litConfig, testSuiteCache,
203                                      localConfigCache)
204
205        N = 0
206        for res in subiter:
207            N += 1
208            yield res
209        if sub_ts and not N:
210            litConfig.warning('test suite %r contained no tests' % sub_ts.name)
211
212def find_tests_for_inputs(lit_config, inputs):
213    """
214    find_tests_for_inputs(lit_config, inputs) -> [Test]
215
216    Given a configuration object and a list of input specifiers, find all the
217    tests to execute.
218    """
219
220    # Expand '@...' form in inputs.
221    actual_inputs = []
222    for input in inputs:
223        if input.startswith('@'):
224            f = open(input[1:])
225            try:
226                for ln in f:
227                    ln = ln.strip()
228                    if ln:
229                        actual_inputs.append(ln)
230            finally:
231                f.close()
232        else:
233            actual_inputs.append(input)
234
235    # Load the tests from the inputs.
236    tests = []
237    test_suite_cache = {}
238    local_config_cache = {}
239    for input in actual_inputs:
240        prev = len(tests)
241        tests.extend(getTests(input, lit_config,
242                              test_suite_cache, local_config_cache)[1])
243        if prev == len(tests):
244            lit_config.warning('input %r contained no tests' % input)
245
246    # If there were any errors during test discovery, exit now.
247    if lit_config.numErrors:
248        sys.stderr.write('%d errors, exiting.\n' % lit_config.numErrors)
249        sys.exit(2)
250
251    return tests
252
253def load_test_suite(inputs):
254    import platform
255    import unittest
256    from lit70.LitTestCase import LitTestCase
257
258    # Create the global config object.
259    litConfig = LitConfig.LitConfig(progname = 'lit',
260                                    path = [],
261                                    quiet = False,
262                                    useValgrind = False,
263                                    valgrindLeakCheck = False,
264                                    valgrindArgs = [],
265                                    singleProcess=False,
266                                    noExecute = False,
267                                    debug = False,
268                                    isWindows = (platform.system()=='Windows'),
269                                    params = {})
270
271    # Perform test discovery.
272    run = lit70.run.Run(litConfig, find_tests_for_inputs(litConfig, inputs))
273
274    # Return a unittest test suite which just runs the tests in order.
275    return unittest.TestSuite([LitTestCase(test, run)
276                               for test in run.tests])
277