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