main.py revision ff2dcd3ce94d2a982950e6c61a0eb1ae39c885e0
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 15from TestingConfig import TestingConfig 16import LitConfig 17import Test 18 19# Configuration files to look for when discovering test suites. These can be 20# overridden with --config-prefix. 21# 22# FIXME: Rename to 'config.lit', 'site.lit', and 'local.lit' ? 23gConfigName = 'lit.cfg' 24gSiteConfigName = 'lit.site.cfg' 25 26kLocalConfigName = 'lit.local.cfg' 27 28class TestingProgressDisplay: 29 def __init__(self, opts, numTests, progressBar=None): 30 self.opts = opts 31 self.numTests = numTests 32 self.current = None 33 self.lock = threading.Lock() 34 self.progressBar = progressBar 35 self.completed = 0 36 37 def update(self, test): 38 # Avoid locking overhead in quiet mode 39 if self.opts.quiet and not test.result.isFailure: 40 self.completed += 1 41 return 42 43 # Output lock. 44 self.lock.acquire() 45 try: 46 self.handleUpdate(test) 47 finally: 48 self.lock.release() 49 50 def finish(self): 51 if self.progressBar: 52 self.progressBar.clear() 53 elif self.opts.quiet: 54 pass 55 elif self.opts.succinct: 56 sys.stdout.write('\n') 57 58 def handleUpdate(self, test): 59 self.completed += 1 60 if self.progressBar: 61 self.progressBar.update(float(self.completed)/self.numTests, 62 test.getFullName()) 63 64 if self.opts.succinct and not test.result.isFailure: 65 return 66 67 if self.progressBar: 68 self.progressBar.clear() 69 70 print '%s: %s (%d of %d)' % (test.result.name, test.getFullName(), 71 self.completed, self.numTests) 72 73 if test.result.isFailure and self.opts.showOutput: 74 print "%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(), 75 '*'*20) 76 print test.output 77 print "*" * 20 78 79 sys.stdout.flush() 80 81class TestProvider: 82 def __init__(self, tests, maxTime): 83 self.maxTime = maxTime 84 self.iter = iter(tests) 85 self.lock = threading.Lock() 86 self.startTime = time.time() 87 88 def get(self): 89 # Check if we have run out of time. 90 if self.maxTime is not None: 91 if time.time() - self.startTime > self.maxTime: 92 return None 93 94 # Otherwise take the next test. 95 self.lock.acquire() 96 try: 97 item = self.iter.next() 98 except StopIteration: 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 = 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 dirContainsTestSuite(path): 141 cfgpath = os.path.join(path, gSiteConfigName) 142 if os.path.exists(cfgpath): 143 return cfgpath 144 cfgpath = os.path.join(path, gConfigName) 145 if os.path.exists(cfgpath): 146 return cfgpath 147 148def getTestSuite(item, litConfig, cache): 149 """getTestSuite(item, litConfig, cache) -> (suite, relative_path) 150 151 Find the test suite containing @arg item. 152 153 @retval (None, ...) - Indicates no test suite contains @arg item. 154 @retval (suite, relative_path) - The suite that @arg item is in, and its 155 relative path inside that suite. 156 """ 157 def search1(path): 158 # Check for a site config or a lit config. 159 cfgpath = dirContainsTestSuite(path) 160 161 # If we didn't find a config file, keep looking. 162 if not cfgpath: 163 parent,base = os.path.split(path) 164 if parent == path: 165 return (None, ()) 166 167 ts, relative = search(parent) 168 return (ts, relative + (base,)) 169 170 # We found a config file, load it. 171 if litConfig.debug: 172 litConfig.note('loading suite config %r' % cfgpath) 173 174 cfg = TestingConfig.frompath(cfgpath, None, litConfig, mustExist = True) 175 source_root = os.path.realpath(cfg.test_source_root or path) 176 exec_root = os.path.realpath(cfg.test_exec_root or path) 177 return Test.TestSuite(cfg.name, source_root, exec_root, cfg), () 178 179 def search(path): 180 # Check for an already instantiated test suite. 181 res = cache.get(path) 182 if res is None: 183 cache[path] = res = search1(path) 184 return res 185 186 # Canonicalize the path. 187 item = os.path.realpath(item) 188 189 # Skip files and virtual components. 190 components = [] 191 while not os.path.isdir(item): 192 parent,base = os.path.split(item) 193 if parent == item: 194 return (None, ()) 195 components.append(base) 196 item = parent 197 components.reverse() 198 199 ts, relative = search(item) 200 return ts, tuple(relative + tuple(components)) 201 202def getLocalConfig(ts, path_in_suite, litConfig, cache): 203 def search1(path_in_suite): 204 # Get the parent config. 205 if not path_in_suite: 206 parent = ts.config 207 else: 208 parent = search(path_in_suite[:-1]) 209 210 # Load the local configuration. 211 source_path = ts.getSourcePath(path_in_suite) 212 cfgpath = os.path.join(source_path, kLocalConfigName) 213 if litConfig.debug: 214 litConfig.note('loading local config %r' % cfgpath) 215 return TestingConfig.frompath(cfgpath, parent, litConfig, 216 mustExist = False, 217 config = parent.clone(cfgpath)) 218 219 def search(path_in_suite): 220 key = (ts, path_in_suite) 221 res = cache.get(key) 222 if res is None: 223 cache[key] = res = search1(path_in_suite) 224 return res 225 226 return search(path_in_suite) 227 228def getTests(path, litConfig, testSuiteCache, localConfigCache): 229 # Find the test suite for this input and its relative path. 230 ts,path_in_suite = getTestSuite(path, litConfig, testSuiteCache) 231 if ts is None: 232 litConfig.warning('unable to find test suite for %r' % path) 233 return (),() 234 235 if litConfig.debug: 236 litConfig.note('resolved input %r to %r::%r' % (path, ts.name, 237 path_in_suite)) 238 239 return ts, getTestsInSuite(ts, path_in_suite, litConfig, 240 testSuiteCache, localConfigCache) 241 242def getTestsInSuite(ts, path_in_suite, litConfig, 243 testSuiteCache, localConfigCache): 244 # Check that the source path exists (errors here are reported by the 245 # caller). 246 source_path = ts.getSourcePath(path_in_suite) 247 if not os.path.exists(source_path): 248 return 249 250 # Check if the user named a test directly. 251 if not os.path.isdir(source_path): 252 lc = getLocalConfig(ts, path_in_suite[:-1], litConfig, localConfigCache) 253 yield Test.Test(ts, path_in_suite, lc) 254 return 255 256 # Otherwise we have a directory to search for tests, start by getting the 257 # local configuration. 258 lc = getLocalConfig(ts, path_in_suite, litConfig, localConfigCache) 259 260 # Search for tests. 261 if lc.test_format is not None: 262 for res in lc.test_format.getTestsInDirectory(ts, path_in_suite, 263 litConfig, lc): 264 yield res 265 266 # Search subdirectories. 267 for filename in os.listdir(source_path): 268 # FIXME: This doesn't belong here? 269 if filename in ('Output', '.svn') or filename in lc.excludes: 270 continue 271 272 # Ignore non-directories. 273 file_sourcepath = os.path.join(source_path, filename) 274 if not os.path.isdir(file_sourcepath): 275 continue 276 277 # Check for nested test suites, first in the execpath in case there is a 278 # site configuration and then in the source path. 279 file_execpath = ts.getExecPath(path_in_suite + (filename,)) 280 if dirContainsTestSuite(file_execpath): 281 sub_ts, subiter = getTests(file_execpath, litConfig, 282 testSuiteCache, localConfigCache) 283 elif dirContainsTestSuite(file_sourcepath): 284 sub_ts, subiter = getTests(file_sourcepath, litConfig, 285 testSuiteCache, localConfigCache) 286 else: 287 # Otherwise, continue loading from inside this test suite. 288 subiter = getTestsInSuite(ts, path_in_suite + (filename,), 289 litConfig, testSuiteCache, 290 localConfigCache) 291 sub_ts = None 292 293 N = 0 294 for res in subiter: 295 N += 1 296 yield res 297 if sub_ts and not N: 298 litConfig.warning('test suite %r contained no tests' % sub_ts.name) 299 300def runTests(numThreads, litConfig, provider, display): 301 # If only using one testing thread, don't use threads at all; this lets us 302 # profile, among other things. 303 if numThreads == 1: 304 t = Tester(litConfig, provider, display) 305 t.run() 306 return 307 308 # Otherwise spin up the testing threads and wait for them to finish. 309 testers = [Tester(litConfig, provider, display) 310 for i in range(numThreads)] 311 for t in testers: 312 t.start() 313 try: 314 for t in testers: 315 t.join() 316 except KeyboardInterrupt: 317 sys.exit(2) 318 319def load_test_suite(inputs): 320 import unittest 321 322 # Create the global config object. 323 litConfig = LitConfig.LitConfig(progname = 'lit', 324 path = [], 325 quiet = False, 326 useValgrind = False, 327 valgrindLeakCheck = False, 328 valgrindArgs = [], 329 useTclAsSh = False, 330 noExecute = False, 331 debug = False, 332 isWindows = (platform.system()=='Windows'), 333 params = {}) 334 335 # Load the tests from the inputs. 336 tests = [] 337 testSuiteCache = {} 338 localConfigCache = {} 339 for input in inputs: 340 prev = len(tests) 341 tests.extend(getTests(input, litConfig, 342 testSuiteCache, localConfigCache)[1]) 343 if prev == len(tests): 344 litConfig.warning('input %r contained no tests' % input) 345 346 # If there were any errors during test discovery, exit now. 347 if litConfig.numErrors: 348 print >>sys.stderr, '%d errors, exiting.' % litConfig.numErrors 349 sys.exit(2) 350 351 # Return a unittest test suite which just runs the tests in order. 352 def get_test_fn(test): 353 return unittest.FunctionTestCase( 354 lambda: test.config.test_format.execute( 355 test, litConfig), 356 description = test.getFullName()) 357 358 from LitTestCase import LitTestCase 359 return unittest.TestSuite([LitTestCase(test, litConfig) for test in tests]) 360 361def main(builtinParameters = {}): # Bump the GIL check interval, its more important to get any one thread to a 362 # blocking operation (hopefully exec) than to try and unblock other threads. 363 # 364 # FIXME: This is a hack. 365 import sys 366 sys.setcheckinterval(1000) 367 368 global options 369 from optparse import OptionParser, OptionGroup 370 parser = OptionParser("usage: %prog [options] {file-or-path}") 371 372 parser.add_option("-j", "--threads", dest="numThreads", metavar="N", 373 help="Number of testing threads", 374 type=int, action="store", default=None) 375 parser.add_option("", "--config-prefix", dest="configPrefix", 376 metavar="NAME", help="Prefix for 'lit' config files", 377 action="store", default=None) 378 parser.add_option("", "--param", dest="userParameters", 379 metavar="NAME=VAL", 380 help="Add 'NAME' = 'VAL' to the user defined parameters", 381 type=str, action="append", default=[]) 382 383 group = OptionGroup(parser, "Output Format") 384 # FIXME: I find these names very confusing, although I like the 385 # functionality. 386 group.add_option("-q", "--quiet", dest="quiet", 387 help="Suppress no error output", 388 action="store_true", default=False) 389 group.add_option("-s", "--succinct", dest="succinct", 390 help="Reduce amount of output", 391 action="store_true", default=False) 392 group.add_option("-v", "--verbose", dest="showOutput", 393 help="Show all test output", 394 action="store_true", default=False) 395 group.add_option("", "--no-progress-bar", dest="useProgressBar", 396 help="Do not use curses based progress bar", 397 action="store_false", default=True) 398 parser.add_option_group(group) 399 400 group = OptionGroup(parser, "Test Execution") 401 group.add_option("", "--path", dest="path", 402 help="Additional paths to add to testing environment", 403 action="append", type=str, default=[]) 404 group.add_option("", "--vg", dest="useValgrind", 405 help="Run tests under valgrind", 406 action="store_true", default=False) 407 group.add_option("", "--vg-leak", dest="valgrindLeakCheck", 408 help="Check for memory leaks under valgrind", 409 action="store_true", default=False) 410 group.add_option("", "--vg-arg", dest="valgrindArgs", metavar="ARG", 411 help="Specify an extra argument for valgrind", 412 type=str, action="append", default=[]) 413 group.add_option("", "--time-tests", dest="timeTests", 414 help="Track elapsed wall time for each test", 415 action="store_true", default=False) 416 group.add_option("", "--no-execute", dest="noExecute", 417 help="Don't execute any tests (assume PASS)", 418 action="store_true", default=False) 419 parser.add_option_group(group) 420 421 group = OptionGroup(parser, "Test Selection") 422 group.add_option("", "--max-tests", dest="maxTests", metavar="N", 423 help="Maximum number of tests to run", 424 action="store", type=int, default=None) 425 group.add_option("", "--max-time", dest="maxTime", metavar="N", 426 help="Maximum time to spend testing (in seconds)", 427 action="store", type=float, default=None) 428 group.add_option("", "--shuffle", dest="shuffle", 429 help="Run tests in random order", 430 action="store_true", default=False) 431 parser.add_option_group(group) 432 433 group = OptionGroup(parser, "Debug and Experimental Options") 434 group.add_option("", "--debug", dest="debug", 435 help="Enable debugging (for 'lit' development)", 436 action="store_true", default=False) 437 group.add_option("", "--show-suites", dest="showSuites", 438 help="Show discovered test suites", 439 action="store_true", default=False) 440 group.add_option("", "--no-tcl-as-sh", dest="useTclAsSh", 441 help="Don't run Tcl scripts using 'sh'", 442 action="store_false", default=True) 443 group.add_option("", "--repeat", dest="repeatTests", metavar="N", 444 help="Repeat tests N times (for timing)", 445 action="store", default=None, type=int) 446 parser.add_option_group(group) 447 448 (opts, args) = parser.parse_args() 449 450 if not args: 451 parser.error('No inputs specified') 452 453 if opts.configPrefix is not None: 454 global gConfigName, gSiteConfigName 455 gConfigName = '%s.cfg' % opts.configPrefix 456 gSiteConfigName = '%s.site.cfg' % opts.configPrefix 457 458 if opts.numThreads is None: 459# Python <2.5 has a race condition causing lit to always fail with numThreads>1 460# http://bugs.python.org/issue1731717 461# I haven't seen this bug occur with 2.5.2 and later, so only enable multiple 462# threads by default there. 463 if sys.hexversion >= 0x2050200: 464 opts.numThreads = Util.detectCPUs() 465 else: 466 opts.numThreads = 1 467 468 inputs = args 469 470 # Create the user defined parameters. 471 userParams = dict(builtinParameters) 472 for entry in opts.userParameters: 473 if '=' not in entry: 474 name,val = entry,'' 475 else: 476 name,val = entry.split('=', 1) 477 userParams[name] = val 478 479 # Create the global config object. 480 litConfig = LitConfig.LitConfig(progname = os.path.basename(sys.argv[0]), 481 path = opts.path, 482 quiet = opts.quiet, 483 useValgrind = opts.useValgrind, 484 valgrindLeakCheck = opts.valgrindLeakCheck, 485 valgrindArgs = opts.valgrindArgs, 486 useTclAsSh = opts.useTclAsSh, 487 noExecute = opts.noExecute, 488 debug = opts.debug, 489 isWindows = (platform.system()=='Windows'), 490 params = userParams) 491 492 # Expand '@...' form in inputs. 493 actual_inputs = [] 494 for input in inputs: 495 if os.path.exists(input) or not input.startswith('@'): 496 actual_inputs.append(input) 497 else: 498 f = open(input[1:]) 499 try: 500 for ln in f: 501 ln = ln.strip() 502 if ln: 503 actual_inputs.append(ln) 504 finally: 505 f.close() 506 507 508 # Load the tests from the inputs. 509 tests = [] 510 testSuiteCache = {} 511 localConfigCache = {} 512 for input in actual_inputs: 513 prev = len(tests) 514 tests.extend(getTests(input, litConfig, 515 testSuiteCache, localConfigCache)[1]) 516 if prev == len(tests): 517 litConfig.warning('input %r contained no tests' % input) 518 519 # If there were any errors during test discovery, exit now. 520 if litConfig.numErrors: 521 print >>sys.stderr, '%d errors, exiting.' % litConfig.numErrors 522 sys.exit(2) 523 524 if opts.showSuites: 525 suitesAndTests = dict([(ts,[]) 526 for ts,_ in testSuiteCache.values() 527 if ts]) 528 for t in tests: 529 suitesAndTests[t.suite].append(t) 530 531 print '-- Test Suites --' 532 suitesAndTests = suitesAndTests.items() 533 suitesAndTests.sort(key = lambda (ts,_): ts.name) 534 for ts,ts_tests in suitesAndTests: 535 print ' %s - %d tests' %(ts.name, len(ts_tests)) 536 print ' Source Root: %s' % ts.source_root 537 print ' Exec Root : %s' % ts.exec_root 538 539 # Select and order the tests. 540 numTotalTests = len(tests) 541 if opts.shuffle: 542 random.shuffle(tests) 543 else: 544 tests.sort(key = lambda t: t.getFullName()) 545 if opts.maxTests is not None: 546 tests = tests[:opts.maxTests] 547 548 extra = '' 549 if len(tests) != numTotalTests: 550 extra = ' of %d' % numTotalTests 551 header = '-- Testing: %d%s tests, %d threads --'%(len(tests),extra, 552 opts.numThreads) 553 554 if opts.repeatTests: 555 tests = [t.copyWithIndex(i) 556 for t in tests 557 for i in range(opts.repeatTests)] 558 559 progressBar = None 560 if not opts.quiet: 561 if opts.succinct and opts.useProgressBar: 562 try: 563 tc = ProgressBar.TerminalController() 564 progressBar = ProgressBar.ProgressBar(tc, header) 565 except ValueError: 566 print header 567 progressBar = ProgressBar.SimpleProgressBar('Testing: ') 568 else: 569 print header 570 571 # Don't create more threads than tests. 572 opts.numThreads = min(len(tests), opts.numThreads) 573 574 startTime = time.time() 575 display = TestingProgressDisplay(opts, len(tests), progressBar) 576 provider = TestProvider(tests, opts.maxTime) 577 runTests(opts.numThreads, litConfig, provider, display) 578 display.finish() 579 580 if not opts.quiet: 581 print 'Testing Time: %.2fs'%(time.time() - startTime) 582 583 # Update results for any tests which weren't run. 584 for t in tests: 585 if t.result is None: 586 t.setResult(Test.UNRESOLVED, '', 0.0) 587 588 # List test results organized by kind. 589 hasFailures = False 590 byCode = {} 591 for t in tests: 592 if t.result not in byCode: 593 byCode[t.result] = [] 594 byCode[t.result].append(t) 595 if t.result.isFailure: 596 hasFailures = True 597 598 # FIXME: Show unresolved and (optionally) unsupported tests. 599 for title,code in (('Unexpected Passing Tests', Test.XPASS), 600 ('Failing Tests', Test.FAIL)): 601 elts = byCode.get(code) 602 if not elts: 603 continue 604 print '*'*20 605 print '%s (%d):' % (title, len(elts)) 606 for t in elts: 607 print ' %s' % t.getFullName() 608 print 609 610 if opts.timeTests: 611 # Collate, in case we repeated tests. 612 times = {} 613 for t in tests: 614 key = t.getFullName() 615 times[key] = times.get(key, 0.) + t.elapsed 616 617 byTime = list(times.items()) 618 byTime.sort(key = lambda (name,elapsed): elapsed) 619 if byTime: 620 Util.printHistogram(byTime, title='Tests') 621 622 for name,code in (('Expected Passes ', Test.PASS), 623 ('Expected Failures ', Test.XFAIL), 624 ('Unsupported Tests ', Test.UNSUPPORTED), 625 ('Unresolved Tests ', Test.UNRESOLVED), 626 ('Unexpected Passes ', Test.XPASS), 627 ('Unexpected Failures', Test.FAIL),): 628 if opts.quiet and not code.isFailure: 629 continue 630 N = len(byCode.get(code,[])) 631 if N: 632 print ' %s: %d' % (name,N) 633 634 # If we encountered any additional errors, exit abnormally. 635 if litConfig.numErrors: 636 print >>sys.stderr, '\n%d error(s), exiting.' % litConfig.numErrors 637 sys.exit(2) 638 639 # Warn about warnings. 640 if litConfig.numWarnings: 641 print >>sys.stderr, '\n%d warning(s) in tests.' % litConfig.numWarnings 642 643 if hasFailures: 644 sys.exit(1) 645 sys.exit(0) 646 647if __name__=='__main__': 648 main() 649