main.py revision b94be5f21eb43f057a8ea2f8afda3c5fecf9e2d8
1#!/usr/bin/env python
2
3"""
4lit - LLVM Integrated Tester.
5
6See lit.pod for more information.
7"""
8
9from __future__ import absolute_import
10import math, os, platform, random, re, sys, time
11
12import lit.ProgressBar
13import lit.LitConfig
14import lit.Test
15import lit.run
16import lit.util
17import lit.discovery
18
19class TestingProgressDisplay(object):
20    def __init__(self, opts, numTests, progressBar=None):
21        self.opts = opts
22        self.numTests = numTests
23        self.current = None
24        self.progressBar = progressBar
25        self.completed = 0
26
27    def finish(self):
28        if self.progressBar:
29            self.progressBar.clear()
30        elif self.opts.quiet:
31            pass
32        elif self.opts.succinct:
33            sys.stdout.write('\n')
34
35    def update(self, test):
36        self.completed += 1
37        if self.progressBar:
38            self.progressBar.update(float(self.completed)/self.numTests,
39                                    test.getFullName())
40
41        if not test.result.code.isFailure and \
42                (self.opts.quiet or self.opts.succinct):
43            return
44
45        if self.progressBar:
46            self.progressBar.clear()
47
48        # Show the test result line.
49        test_name = test.getFullName()
50        print('%s: %s (%d of %d)' % (test.result.code.name, test_name,
51                                     self.completed, self.numTests))
52
53        # Show the test failure output, if requested.
54        if test.result.code.isFailure and self.opts.showOutput:
55            print("%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(),
56                                              '*'*20))
57            print(test.result.output)
58            print("*" * 20)
59
60        # Report test metrics, if present.
61        if test.result.metrics:
62            print("%s TEST '%s' RESULTS %s" % ('*'*10, test.getFullName(),
63                                               '*'*10))
64            items = sorted(test.result.metrics.items())
65            for metric_name, value in items:
66                print('%s: %s ' % (metric_name, value.format()))
67            print("*" * 10)
68
69        # Ensure the output is flushed.
70        sys.stdout.flush()
71
72def write_test_results(run, lit_config, testing_time, output_path):
73    try:
74        import json
75    except ImportError:
76        lit_config.fatal('test output unsupported with Python 2.5')
77
78    # Construct the data we will write.
79    data = {}
80    # Encode the current lit version as a schema version.
81    data['__version__'] = lit.__versioninfo__
82    data['elapsed'] = testing_time
83    # FIXME: Record some information on the lit configuration used?
84    # FIXME: Record information from the individual test suites?
85
86    # Encode the tests.
87    data['tests'] = tests_data = []
88    for test in run.tests:
89        test_data = {
90            'name' : test.getFullName(),
91            'code' : test.result.code.name,
92            'output' : test.result.output,
93            'elapsed' : test.result.elapsed }
94
95        # Add test metrics, if present.
96        if test.result.metrics:
97            test_data['metrics'] = metrics_data = {}
98            for key, value in test.result.metrics.items():
99                metrics_data[key] = value.todata()
100
101        tests_data.append(test_data)
102
103    # Write the output.
104    f = open(output_path, 'w')
105    try:
106        json.dump(data, f, indent=2, sort_keys=True)
107        f.write('\n')
108    finally:
109        f.close()
110
111def main(builtinParameters = {}):
112    # Use processes by default on Unix platforms.
113    isWindows = platform.system() == 'Windows'
114    useProcessesIsDefault = (not isWindows) and platform.system() != 'OpenBSD'
115
116    global options
117    from optparse import OptionParser, OptionGroup
118    parser = OptionParser("usage: %prog [options] {file-or-path}")
119
120    parser.add_option("-j", "--threads", dest="numThreads", metavar="N",
121                      help="Number of testing threads",
122                      type=int, action="store", default=None)
123    parser.add_option("", "--config-prefix", dest="configPrefix",
124                      metavar="NAME", help="Prefix for 'lit' config files",
125                      action="store", default=None)
126    parser.add_option("", "--param", dest="userParameters",
127                      metavar="NAME=VAL",
128                      help="Add 'NAME' = 'VAL' to the user defined parameters",
129                      type=str, action="append", default=[])
130
131    group = OptionGroup(parser, "Output Format")
132    # FIXME: I find these names very confusing, although I like the
133    # functionality.
134    group.add_option("-q", "--quiet", dest="quiet",
135                     help="Suppress no error output",
136                     action="store_true", default=False)
137    group.add_option("-s", "--succinct", dest="succinct",
138                     help="Reduce amount of output",
139                     action="store_true", default=False)
140    group.add_option("-v", "--verbose", dest="showOutput",
141                     help="Show all test output",
142                     action="store_true", default=False)
143    group.add_option("-o", "--output", dest="output_path",
144                     help="Write test results to the provided path",
145                     action="store", type=str, metavar="PATH")
146    group.add_option("", "--no-progress-bar", dest="useProgressBar",
147                     help="Do not use curses based progress bar",
148                     action="store_false", default=True)
149    parser.add_option_group(group)
150
151    group = OptionGroup(parser, "Test Execution")
152    group.add_option("", "--path", dest="path",
153                     help="Additional paths to add to testing environment",
154                     action="append", type=str, default=[])
155    group.add_option("", "--vg", dest="useValgrind",
156                     help="Run tests under valgrind",
157                     action="store_true", default=False)
158    group.add_option("", "--vg-leak", dest="valgrindLeakCheck",
159                     help="Check for memory leaks under valgrind",
160                     action="store_true", default=False)
161    group.add_option("", "--vg-arg", dest="valgrindArgs", metavar="ARG",
162                     help="Specify an extra argument for valgrind",
163                     type=str, action="append", default=[])
164    group.add_option("", "--time-tests", dest="timeTests",
165                     help="Track elapsed wall time for each test",
166                     action="store_true", default=False)
167    group.add_option("", "--no-execute", dest="noExecute",
168                     help="Don't execute any tests (assume PASS)",
169                     action="store_true", default=False)
170    parser.add_option_group(group)
171
172    group = OptionGroup(parser, "Test Selection")
173    group.add_option("", "--max-tests", dest="maxTests", metavar="N",
174                     help="Maximum number of tests to run",
175                     action="store", type=int, default=None)
176    group.add_option("", "--max-time", dest="maxTime", metavar="N",
177                     help="Maximum time to spend testing (in seconds)",
178                     action="store", type=float, default=None)
179    group.add_option("", "--shuffle", dest="shuffle",
180                     help="Run tests in random order",
181                     action="store_true", default=False)
182    group.add_option("", "--filter", dest="filter", metavar="REGEX",
183                     help=("Only run tests with paths matching the given "
184                           "regular expression"),
185                     action="store", default=None)
186    parser.add_option_group(group)
187
188    group = OptionGroup(parser, "Debug and Experimental Options")
189    group.add_option("", "--debug", dest="debug",
190                      help="Enable debugging (for 'lit' development)",
191                      action="store_true", default=False)
192    group.add_option("", "--show-suites", dest="showSuites",
193                      help="Show discovered test suites",
194                      action="store_true", default=False)
195    group.add_option("", "--show-tests", dest="showTests",
196                      help="Show all discovered tests",
197                      action="store_true", default=False)
198    group.add_option("", "--use-processes", dest="useProcesses",
199                      help="Run tests in parallel with processes (not threads)",
200                      action="store_true", default=useProcessesIsDefault)
201    group.add_option("", "--use-threads", dest="useProcesses",
202                      help="Run tests in parallel with threads (not processes)",
203                      action="store_false", default=useProcessesIsDefault)
204    parser.add_option_group(group)
205
206    (opts, args) = parser.parse_args()
207
208    if not args:
209        parser.error('No inputs specified')
210
211    if opts.numThreads is None:
212# Python <2.5 has a race condition causing lit to always fail with numThreads>1
213# http://bugs.python.org/issue1731717
214# I haven't seen this bug occur with 2.5.2 and later, so only enable multiple
215# threads by default there.
216       if sys.hexversion >= 0x2050200:
217               opts.numThreads = lit.util.detectCPUs()
218       else:
219               opts.numThreads = 1
220
221    inputs = args
222
223    # Create the user defined parameters.
224    userParams = dict(builtinParameters)
225    for entry in opts.userParameters:
226        if '=' not in entry:
227            name,val = entry,''
228        else:
229            name,val = entry.split('=', 1)
230        userParams[name] = val
231
232    # Create the global config object.
233    litConfig = lit.LitConfig.LitConfig(
234        progname = os.path.basename(sys.argv[0]),
235        path = opts.path,
236        quiet = opts.quiet,
237        useValgrind = opts.useValgrind,
238        valgrindLeakCheck = opts.valgrindLeakCheck,
239        valgrindArgs = opts.valgrindArgs,
240        noExecute = opts.noExecute,
241        debug = opts.debug,
242        isWindows = isWindows,
243        params = userParams,
244        config_prefix = opts.configPrefix)
245
246    # Perform test discovery.
247    run = lit.run.Run(litConfig,
248                      lit.discovery.find_tests_for_inputs(litConfig, inputs))
249
250    if opts.showSuites or opts.showTests:
251        # Aggregate the tests by suite.
252        suitesAndTests = {}
253        for t in run.tests:
254            if t.suite not in suitesAndTests:
255                suitesAndTests[t.suite] = []
256            suitesAndTests[t.suite].append(t)
257        suitesAndTests = list(suitesAndTests.items())
258        suitesAndTests.sort(key = lambda item: item[0].name)
259
260        # Show the suites, if requested.
261        if opts.showSuites:
262            print('-- Test Suites --')
263            for ts,ts_tests in suitesAndTests:
264                print('  %s - %d tests' %(ts.name, len(ts_tests)))
265                print('    Source Root: %s' % ts.source_root)
266                print('    Exec Root  : %s' % ts.exec_root)
267
268        # Show the tests, if requested.
269        if opts.showTests:
270            print('-- Available Tests --')
271            for ts,ts_tests in suitesAndTests:
272                ts_tests.sort(key = lambda test: test.path_in_suite)
273                for test in ts_tests:
274                    print('  %s' % (test.getFullName(),))
275
276        # Exit.
277        sys.exit(0)
278
279    # Select and order the tests.
280    numTotalTests = len(run.tests)
281
282    # First, select based on the filter expression if given.
283    if opts.filter:
284        try:
285            rex = re.compile(opts.filter)
286        except:
287            parser.error("invalid regular expression for --filter: %r" % (
288                    opts.filter))
289        run.tests = [t for t in run.tests
290                     if rex.search(t.getFullName())]
291
292    # Then select the order.
293    if opts.shuffle:
294        random.shuffle(run.tests)
295    else:
296        run.tests.sort(key = lambda t: t.getFullName())
297
298    # Finally limit the number of tests, if desired.
299    if opts.maxTests is not None:
300        run.tests = run.tests[:opts.maxTests]
301
302    # Don't create more threads than tests.
303    opts.numThreads = min(len(run.tests), opts.numThreads)
304
305    extra = ''
306    if len(run.tests) != numTotalTests:
307        extra = ' of %d' % numTotalTests
308    header = '-- Testing: %d%s tests, %d threads --'%(len(run.tests), extra,
309                                                      opts.numThreads)
310
311    progressBar = None
312    if not opts.quiet:
313        if opts.succinct and opts.useProgressBar:
314            try:
315                tc = lit.ProgressBar.TerminalController()
316                progressBar = lit.ProgressBar.ProgressBar(tc, header)
317            except ValueError:
318                print(header)
319                progressBar = lit.ProgressBar.SimpleProgressBar('Testing: ')
320        else:
321            print(header)
322
323    startTime = time.time()
324    display = TestingProgressDisplay(opts, len(run.tests), progressBar)
325    try:
326        run.execute_tests(display, opts.numThreads, opts.maxTime,
327                          opts.useProcesses)
328    except KeyboardInterrupt:
329        sys.exit(2)
330    display.finish()
331
332    testing_time = time.time() - startTime
333    if not opts.quiet:
334        print('Testing Time: %.2fs' % (testing_time,))
335
336    # Write out the test data, if requested.
337    if opts.output_path is not None:
338        write_test_results(run, litConfig, testing_time, opts.output_path)
339
340    # List test results organized by kind.
341    hasFailures = False
342    byCode = {}
343    for test in run.tests:
344        if test.result.code not in byCode:
345            byCode[test.result.code] = []
346        byCode[test.result.code].append(test)
347        if test.result.code.isFailure:
348            hasFailures = True
349
350    # Print each test in any of the failing groups.
351    for title,code in (('Unexpected Passing Tests', lit.Test.XPASS),
352                       ('Failing Tests', lit.Test.FAIL),
353                       ('Unresolved Tests', lit.Test.UNRESOLVED)):
354        elts = byCode.get(code)
355        if not elts:
356            continue
357        print('*'*20)
358        print('%s (%d):' % (title, len(elts)))
359        for test in elts:
360            print('    %s' % test.getFullName())
361        sys.stdout.write('\n')
362
363    if opts.timeTests and run.tests:
364        # Order by time.
365        test_times = [(test.getFullName(), test.result.elapsed)
366                      for test in run.tests]
367        lit.util.printHistogram(test_times, title='Tests')
368
369    for name,code in (('Expected Passes    ', lit.Test.PASS),
370                      ('Expected Failures  ', lit.Test.XFAIL),
371                      ('Unsupported Tests  ', lit.Test.UNSUPPORTED),
372                      ('Unresolved Tests   ', lit.Test.UNRESOLVED),
373                      ('Unexpected Passes  ', lit.Test.XPASS),
374                      ('Unexpected Failures', lit.Test.FAIL),):
375        if opts.quiet and not code.isFailure:
376            continue
377        N = len(byCode.get(code,[]))
378        if N:
379            print('  %s: %d' % (name,N))
380
381    # If we encountered any additional errors, exit abnormally.
382    if litConfig.numErrors:
383        sys.stderr.write('\n%d error(s), exiting.\n' % litConfig.numErrors)
384        sys.exit(2)
385
386    # Warn about warnings.
387    if litConfig.numWarnings:
388        sys.stderr.write('\n%d warning(s) in tests.\n' % litConfig.numWarnings)
389
390    if hasFailures:
391        sys.exit(1)
392    sys.exit(0)
393
394if __name__=='__main__':
395    main()
396