main.py revision 59c6b1073c48befe021de024a693bed94147120c
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, threading, traceback
11
12import lit.ProgressBar
13import lit.LitConfig
14import lit.Test
15import lit.Util
16
17import lit.discovery
18
19class TestingProgressDisplay:
20    def __init__(self, opts, numTests, progressBar=None):
21        self.opts = opts
22        self.numTests = numTests
23        self.current = None
24        self.lock = threading.Lock()
25        self.progressBar = progressBar
26        self.completed = 0
27
28    def update(self, test):
29        # Avoid locking overhead in quiet mode
30        if self.opts.quiet and not test.result.isFailure:
31            self.completed += 1
32            return
33
34        # Output lock.
35        self.lock.acquire()
36        try:
37            self.handleUpdate(test)
38        finally:
39            self.lock.release()
40
41    def finish(self):
42        if self.progressBar:
43            self.progressBar.clear()
44        elif self.opts.quiet:
45            pass
46        elif self.opts.succinct:
47            sys.stdout.write('\n')
48
49    def handleUpdate(self, test):
50        self.completed += 1
51        if self.progressBar:
52            self.progressBar.update(float(self.completed)/self.numTests,
53                                    test.getFullName())
54
55        if self.opts.succinct and not test.result.isFailure:
56            return
57
58        if self.progressBar:
59            self.progressBar.clear()
60
61        print('%s: %s (%d of %d)' % (test.result.name, test.getFullName(),
62                                     self.completed, self.numTests))
63
64        if test.result.isFailure and self.opts.showOutput:
65            print("%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(),
66                                              '*'*20))
67            print(test.output)
68            print("*" * 20)
69
70        sys.stdout.flush()
71
72class TestProvider:
73    def __init__(self, tests, maxTime):
74        self.maxTime = maxTime
75        self.iter = iter(tests)
76        self.lock = threading.Lock()
77        self.startTime = time.time()
78        self.canceled = False
79
80    def cancel(self):
81        self.lock.acquire()
82        self.canceled = True
83        self.lock.release()
84
85    def get(self):
86        # Check if we have run out of time.
87        if self.maxTime is not None:
88            if time.time() - self.startTime > self.maxTime:
89                return None
90
91        # Otherwise take the next test.
92        self.lock.acquire()
93        if self.canceled:
94          self.lock.release()
95          return None
96        for item in self.iter:
97            break
98        else:
99            item = None
100        self.lock.release()
101        return item
102
103class Tester(threading.Thread):
104    def __init__(self, litConfig, provider, display):
105        threading.Thread.__init__(self)
106        self.litConfig = litConfig
107        self.provider = provider
108        self.display = display
109
110    def run(self):
111        while 1:
112            item = self.provider.get()
113            if item is None:
114                break
115            self.runTest(item)
116
117    def runTest(self, test):
118        result = None
119        startTime = time.time()
120        try:
121            result, output = test.config.test_format.execute(test,
122                                                             self.litConfig)
123        except KeyboardInterrupt:
124            # This is a sad hack. Unfortunately subprocess goes
125            # bonkers with ctrl-c and we start forking merrily.
126            print('\nCtrl-C detected, goodbye.')
127            os.kill(0,9)
128        except:
129            if self.litConfig.debug:
130                raise
131            result = lit.Test.UNRESOLVED
132            output = 'Exception during script execution:\n'
133            output += traceback.format_exc()
134            output += '\n'
135        elapsed = time.time() - startTime
136
137        test.setResult(result, output, elapsed)
138        self.display.update(test)
139
140def runTests(numThreads, litConfig, provider, display):
141    # If only using one testing thread, don't use threads at all; this lets us
142    # profile, among other things.
143    if numThreads == 1:
144        t = Tester(litConfig, provider, display)
145        t.run()
146        return
147
148    # Otherwise spin up the testing threads and wait for them to finish.
149    testers = [Tester(litConfig, provider, display)
150               for i in range(numThreads)]
151    for t in testers:
152        t.start()
153    try:
154        for t in testers:
155            t.join()
156    except KeyboardInterrupt:
157        sys.exit(2)
158
159def main(builtinParameters = {}):
160    # Bump the GIL check interval, its more important to get any one thread to a
161    # blocking operation (hopefully exec) than to try and unblock other threads.
162    #
163    # FIXME: This is a hack.
164    sys.setcheckinterval(1000)
165
166    global options
167    from optparse import OptionParser, OptionGroup
168    parser = OptionParser("usage: %prog [options] {file-or-path}")
169
170    parser.add_option("-j", "--threads", dest="numThreads", metavar="N",
171                      help="Number of testing threads",
172                      type=int, action="store", default=None)
173    parser.add_option("", "--config-prefix", dest="configPrefix",
174                      metavar="NAME", help="Prefix for 'lit' config files",
175                      action="store", default=None)
176    parser.add_option("", "--param", dest="userParameters",
177                      metavar="NAME=VAL",
178                      help="Add 'NAME' = 'VAL' to the user defined parameters",
179                      type=str, action="append", default=[])
180
181    group = OptionGroup(parser, "Output Format")
182    # FIXME: I find these names very confusing, although I like the
183    # functionality.
184    group.add_option("-q", "--quiet", dest="quiet",
185                     help="Suppress no error output",
186                     action="store_true", default=False)
187    group.add_option("-s", "--succinct", dest="succinct",
188                     help="Reduce amount of output",
189                     action="store_true", default=False)
190    group.add_option("-v", "--verbose", dest="showOutput",
191                     help="Show all test output",
192                     action="store_true", default=False)
193    group.add_option("", "--no-progress-bar", dest="useProgressBar",
194                     help="Do not use curses based progress bar",
195                     action="store_false", default=True)
196    parser.add_option_group(group)
197
198    group = OptionGroup(parser, "Test Execution")
199    group.add_option("", "--path", dest="path",
200                     help="Additional paths to add to testing environment",
201                     action="append", type=str, default=[])
202    group.add_option("", "--vg", dest="useValgrind",
203                     help="Run tests under valgrind",
204                     action="store_true", default=False)
205    group.add_option("", "--vg-leak", dest="valgrindLeakCheck",
206                     help="Check for memory leaks under valgrind",
207                     action="store_true", default=False)
208    group.add_option("", "--vg-arg", dest="valgrindArgs", metavar="ARG",
209                     help="Specify an extra argument for valgrind",
210                     type=str, action="append", default=[])
211    group.add_option("", "--time-tests", dest="timeTests",
212                     help="Track elapsed wall time for each test",
213                     action="store_true", default=False)
214    group.add_option("", "--no-execute", dest="noExecute",
215                     help="Don't execute any tests (assume PASS)",
216                     action="store_true", default=False)
217    parser.add_option_group(group)
218
219    group = OptionGroup(parser, "Test Selection")
220    group.add_option("", "--max-tests", dest="maxTests", metavar="N",
221                     help="Maximum number of tests to run",
222                     action="store", type=int, default=None)
223    group.add_option("", "--max-time", dest="maxTime", metavar="N",
224                     help="Maximum time to spend testing (in seconds)",
225                     action="store", type=float, default=None)
226    group.add_option("", "--shuffle", dest="shuffle",
227                     help="Run tests in random order",
228                     action="store_true", default=False)
229    group.add_option("", "--filter", dest="filter", metavar="REGEX",
230                     help=("Only run tests with paths matching the given "
231                           "regular expression"),
232                     action="store", default=None)
233    parser.add_option_group(group)
234
235    group = OptionGroup(parser, "Debug and Experimental Options")
236    group.add_option("", "--debug", dest="debug",
237                      help="Enable debugging (for 'lit' development)",
238                      action="store_true", default=False)
239    group.add_option("", "--show-suites", dest="showSuites",
240                      help="Show discovered test suites",
241                      action="store_true", default=False)
242    group.add_option("", "--show-tests", dest="showTests",
243                      help="Show all discovered tests",
244                      action="store_true", default=False)
245    parser.add_option_group(group)
246
247    (opts, args) = parser.parse_args()
248
249    if not args:
250        parser.error('No inputs specified')
251
252    if opts.numThreads is None:
253# Python <2.5 has a race condition causing lit to always fail with numThreads>1
254# http://bugs.python.org/issue1731717
255# I haven't seen this bug occur with 2.5.2 and later, so only enable multiple
256# threads by default there.
257       if sys.hexversion >= 0x2050200:
258               opts.numThreads = lit.Util.detectCPUs()
259       else:
260               opts.numThreads = 1
261
262    inputs = args
263
264    # Create the user defined parameters.
265    userParams = dict(builtinParameters)
266    for entry in opts.userParameters:
267        if '=' not in entry:
268            name,val = entry,''
269        else:
270            name,val = entry.split('=', 1)
271        userParams[name] = val
272
273    # Create the global config object.
274    litConfig = lit.LitConfig.LitConfig(
275        progname = os.path.basename(sys.argv[0]),
276        path = opts.path,
277        quiet = opts.quiet,
278        useValgrind = opts.useValgrind,
279        valgrindLeakCheck = opts.valgrindLeakCheck,
280        valgrindArgs = opts.valgrindArgs,
281        noExecute = opts.noExecute,
282        debug = opts.debug,
283        isWindows = (platform.system()=='Windows'),
284        params = userParams,
285        config_prefix = opts.configPrefix)
286
287    tests = lit.discovery.find_tests_for_inputs(litConfig, inputs)
288
289    if opts.showSuites or opts.showTests:
290        # Aggregate the tests by suite.
291        suitesAndTests = {}
292        for t in tests:
293            if t.suite not in suitesAndTests:
294                suitesAndTests[t.suite] = []
295            suitesAndTests[t.suite].append(t)
296        suitesAndTests = list(suitesAndTests.items())
297        suitesAndTests.sort(key = lambda item: item[0].name)
298
299        # Show the suites, if requested.
300        if opts.showSuites:
301            print('-- Test Suites --')
302            for ts,ts_tests in suitesAndTests:
303                print('  %s - %d tests' %(ts.name, len(ts_tests)))
304                print('    Source Root: %s' % ts.source_root)
305                print('    Exec Root  : %s' % ts.exec_root)
306
307        # Show the tests, if requested.
308        if opts.showTests:
309            print('-- Available Tests --')
310            for ts,ts_tests in suitesAndTests:
311                ts_tests.sort(key = lambda test: test.path_in_suite)
312                for test in ts_tests:
313                    print('  %s' % (test.getFullName(),))
314
315    # Select and order the tests.
316    numTotalTests = len(tests)
317
318    # First, select based on the filter expression if given.
319    if opts.filter:
320        try:
321            rex = re.compile(opts.filter)
322        except:
323            parser.error("invalid regular expression for --filter: %r" % (
324                    opts.filter))
325        tests = [t for t in tests
326                 if rex.search(t.getFullName())]
327
328    # Then select the order.
329    if opts.shuffle:
330        random.shuffle(tests)
331    else:
332        tests.sort(key = lambda t: t.getFullName())
333
334    # Finally limit the number of tests, if desired.
335    if opts.maxTests is not None:
336        tests = tests[:opts.maxTests]
337
338    # Don't create more threads than tests.
339    opts.numThreads = min(len(tests), opts.numThreads)
340
341    extra = ''
342    if len(tests) != numTotalTests:
343        extra = ' of %d' % numTotalTests
344    header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra,
345                                                      opts.numThreads)
346
347    progressBar = None
348    if not opts.quiet:
349        if opts.succinct and opts.useProgressBar:
350            try:
351                tc = lit.ProgressBar.TerminalController()
352                progressBar = lit.ProgressBar.ProgressBar(tc, header)
353            except ValueError:
354                print(header)
355                progressBar = lit.ProgressBar.SimpleProgressBar('Testing: ')
356        else:
357            print(header)
358
359    startTime = time.time()
360    display = TestingProgressDisplay(opts, len(tests), progressBar)
361    provider = TestProvider(tests, opts.maxTime)
362
363    try:
364      import win32api
365    except ImportError:
366      pass
367    else:
368      def console_ctrl_handler(type):
369        provider.cancel()
370        return True
371      win32api.SetConsoleCtrlHandler(console_ctrl_handler, True)
372
373    runTests(opts.numThreads, litConfig, provider, display)
374    display.finish()
375
376    if not opts.quiet:
377        print('Testing Time: %.2fs'%(time.time() - startTime))
378
379    # Update results for any tests which weren't run.
380    for t in tests:
381        if t.result is None:
382            t.setResult(lit.Test.UNRESOLVED, '', 0.0)
383
384    # List test results organized by kind.
385    hasFailures = False
386    byCode = {}
387    for t in tests:
388        if t.result not in byCode:
389            byCode[t.result] = []
390        byCode[t.result].append(t)
391        if t.result.isFailure:
392            hasFailures = True
393
394    # Print each test in any of the failing groups.
395    for title,code in (('Unexpected Passing Tests', lit.Test.XPASS),
396                       ('Failing Tests', lit.Test.FAIL),
397                       ('Unresolved Tests', lit.Test.UNRESOLVED)):
398        elts = byCode.get(code)
399        if not elts:
400            continue
401        print('*'*20)
402        print('%s (%d):' % (title, len(elts)))
403        for t in elts:
404            print('    %s' % t.getFullName())
405        sys.stdout.write('\n')
406
407    if opts.timeTests:
408        # Collate, in case we repeated tests.
409        times = {}
410        for t in tests:
411            key = t.getFullName()
412            times[key] = times.get(key, 0.) + t.elapsed
413
414        byTime = list(times.items())
415        byTime.sort(key = lambda item: item[1])
416        if byTime:
417            lit.Util.printHistogram(byTime, title='Tests')
418
419    for name,code in (('Expected Passes    ', lit.Test.PASS),
420                      ('Expected Failures  ', lit.Test.XFAIL),
421                      ('Unsupported Tests  ', lit.Test.UNSUPPORTED),
422                      ('Unresolved Tests   ', lit.Test.UNRESOLVED),
423                      ('Unexpected Passes  ', lit.Test.XPASS),
424                      ('Unexpected Failures', lit.Test.FAIL),):
425        if opts.quiet and not code.isFailure:
426            continue
427        N = len(byCode.get(code,[]))
428        if N:
429            print('  %s: %d' % (name,N))
430
431    # If we encountered any additional errors, exit abnormally.
432    if litConfig.numErrors:
433        sys.stderr.write('\n%d error(s), exiting.\n' % litConfig.numErrors)
434        sys.exit(2)
435
436    # Warn about warnings.
437    if litConfig.numWarnings:
438        sys.stderr.write('\n%d warning(s) in tests.\n' % litConfig.numWarnings)
439
440    if hasFailures:
441        sys.exit(1)
442    sys.exit(0)
443
444if __name__=='__main__':
445    main()
446