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