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