| Index: third_party/lit/lit/main.py
|
| diff --git a/third_party/lit/lit/main.py b/third_party/lit/lit/main.py
|
| new file mode 100755
|
| index 0000000000000000000000000000000000000000..630cb54f96d5f162f5187921e1ed5547f1d98d5f
|
| --- /dev/null
|
| +++ b/third_party/lit/lit/main.py
|
| @@ -0,0 +1,474 @@
|
| +#!/usr/bin/env python
|
| +
|
| +"""
|
| +lit - LLVM Integrated Tester.
|
| +
|
| +See lit.pod for more information.
|
| +"""
|
| +
|
| +from __future__ import absolute_import
|
| +import math, os, platform, random, re, sys, time
|
| +
|
| +import lit.ProgressBar
|
| +import lit.LitConfig
|
| +import lit.Test
|
| +import lit.run
|
| +import lit.util
|
| +import lit.discovery
|
| +
|
| +class TestingProgressDisplay(object):
|
| + def __init__(self, opts, numTests, progressBar=None):
|
| + self.opts = opts
|
| + self.numTests = numTests
|
| + self.current = None
|
| + self.progressBar = progressBar
|
| + self.completed = 0
|
| +
|
| + def finish(self):
|
| + if self.progressBar:
|
| + self.progressBar.clear()
|
| + elif self.opts.quiet:
|
| + pass
|
| + elif self.opts.succinct:
|
| + sys.stdout.write('\n')
|
| +
|
| + def update(self, test):
|
| + self.completed += 1
|
| +
|
| + if self.opts.incremental:
|
| + update_incremental_cache(test)
|
| +
|
| + if self.progressBar:
|
| + self.progressBar.update(float(self.completed)/self.numTests,
|
| + test.getFullName())
|
| +
|
| + shouldShow = test.result.code.isFailure or \
|
| + (not self.opts.quiet and not self.opts.succinct)
|
| + if not shouldShow:
|
| + return
|
| +
|
| + if self.progressBar:
|
| + self.progressBar.clear()
|
| +
|
| + # Show the test result line.
|
| + test_name = test.getFullName()
|
| + print('%s: %s (%d of %d)' % (test.result.code.name, test_name,
|
| + self.completed, self.numTests))
|
| +
|
| + # Show the test failure output, if requested.
|
| + if test.result.code.isFailure and self.opts.showOutput:
|
| + print("%s TEST '%s' FAILED %s" % ('*'*20, test.getFullName(),
|
| + '*'*20))
|
| + print(test.result.output)
|
| + print("*" * 20)
|
| +
|
| + # Report test metrics, if present.
|
| + if test.result.metrics:
|
| + print("%s TEST '%s' RESULTS %s" % ('*'*10, test.getFullName(),
|
| + '*'*10))
|
| + items = sorted(test.result.metrics.items())
|
| + for metric_name, value in items:
|
| + print('%s: %s ' % (metric_name, value.format()))
|
| + print("*" * 10)
|
| +
|
| + # Ensure the output is flushed.
|
| + sys.stdout.flush()
|
| +
|
| +def write_test_results(run, lit_config, testing_time, output_path):
|
| + try:
|
| + import json
|
| + except ImportError:
|
| + lit_config.fatal('test output unsupported with Python 2.5')
|
| +
|
| + # Construct the data we will write.
|
| + data = {}
|
| + # Encode the current lit version as a schema version.
|
| + data['__version__'] = lit.__versioninfo__
|
| + data['elapsed'] = testing_time
|
| + # FIXME: Record some information on the lit configuration used?
|
| + # FIXME: Record information from the individual test suites?
|
| +
|
| + # Encode the tests.
|
| + data['tests'] = tests_data = []
|
| + for test in run.tests:
|
| + test_data = {
|
| + 'name' : test.getFullName(),
|
| + 'code' : test.result.code.name,
|
| + 'output' : test.result.output,
|
| + 'elapsed' : test.result.elapsed }
|
| +
|
| + # Add test metrics, if present.
|
| + if test.result.metrics:
|
| + test_data['metrics'] = metrics_data = {}
|
| + for key, value in test.result.metrics.items():
|
| + metrics_data[key] = value.todata()
|
| +
|
| + tests_data.append(test_data)
|
| +
|
| + # Write the output.
|
| + f = open(output_path, 'w')
|
| + try:
|
| + json.dump(data, f, indent=2, sort_keys=True)
|
| + f.write('\n')
|
| + finally:
|
| + f.close()
|
| +
|
| +def update_incremental_cache(test):
|
| + if not test.result.code.isFailure:
|
| + return
|
| + fname = test.getFilePath()
|
| + os.utime(fname, None)
|
| +
|
| +def sort_by_incremental_cache(run):
|
| + def sortIndex(test):
|
| + fname = test.getFilePath()
|
| + try:
|
| + return -os.path.getmtime(fname)
|
| + except:
|
| + return 0
|
| + run.tests.sort(key = lambda t: sortIndex(t))
|
| +
|
| +def main(builtinParameters = {}):
|
| + # Use processes by default on Unix platforms.
|
| + isWindows = platform.system() == 'Windows'
|
| + useProcessesIsDefault = not isWindows
|
| +
|
| + global options
|
| + from optparse import OptionParser, OptionGroup
|
| + parser = OptionParser("usage: %prog [options] {file-or-path}")
|
| +
|
| + parser.add_option("", "--version", dest="show_version",
|
| + help="Show version and exit",
|
| + action="store_true", default=False)
|
| + parser.add_option("-j", "--threads", dest="numThreads", metavar="N",
|
| + help="Number of testing threads",
|
| + type=int, action="store", default=None)
|
| + parser.add_option("", "--config-prefix", dest="configPrefix",
|
| + metavar="NAME", help="Prefix for 'lit' config files",
|
| + action="store", default=None)
|
| + parser.add_option("-D", "--param", dest="userParameters",
|
| + metavar="NAME=VAL",
|
| + help="Add 'NAME' = 'VAL' to the user defined parameters",
|
| + type=str, action="append", default=[])
|
| +
|
| + group = OptionGroup(parser, "Output Format")
|
| + # FIXME: I find these names very confusing, although I like the
|
| + # functionality.
|
| + group.add_option("-q", "--quiet", dest="quiet",
|
| + help="Suppress no error output",
|
| + action="store_true", default=False)
|
| + group.add_option("-s", "--succinct", dest="succinct",
|
| + help="Reduce amount of output",
|
| + action="store_true", default=False)
|
| + group.add_option("-v", "--verbose", dest="showOutput",
|
| + help="Show all test output",
|
| + action="store_true", default=False)
|
| + group.add_option("-o", "--output", dest="output_path",
|
| + help="Write test results to the provided path",
|
| + action="store", type=str, metavar="PATH")
|
| + group.add_option("", "--no-progress-bar", dest="useProgressBar",
|
| + help="Do not use curses based progress bar",
|
| + action="store_false", default=True)
|
| + group.add_option("", "--show-unsupported", dest="show_unsupported",
|
| + help="Show unsupported tests",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--show-xfail", dest="show_xfail",
|
| + help="Show tests that were expected to fail",
|
| + action="store_true", default=False)
|
| + parser.add_option_group(group)
|
| +
|
| + group = OptionGroup(parser, "Test Execution")
|
| + group.add_option("", "--path", dest="path",
|
| + help="Additional paths to add to testing environment",
|
| + action="append", type=str, default=[])
|
| + group.add_option("", "--vg", dest="useValgrind",
|
| + help="Run tests under valgrind",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--vg-leak", dest="valgrindLeakCheck",
|
| + help="Check for memory leaks under valgrind",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--vg-arg", dest="valgrindArgs", metavar="ARG",
|
| + help="Specify an extra argument for valgrind",
|
| + type=str, action="append", default=[])
|
| + group.add_option("", "--time-tests", dest="timeTests",
|
| + help="Track elapsed wall time for each test",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--no-execute", dest="noExecute",
|
| + help="Don't execute any tests (assume PASS)",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--xunit-xml-output", dest="xunit_output_file",
|
| + help=("Write XUnit-compatible XML test reports to the"
|
| + " specified file"), default=None)
|
| + parser.add_option_group(group)
|
| +
|
| + group = OptionGroup(parser, "Test Selection")
|
| + group.add_option("", "--max-tests", dest="maxTests", metavar="N",
|
| + help="Maximum number of tests to run",
|
| + action="store", type=int, default=None)
|
| + group.add_option("", "--max-time", dest="maxTime", metavar="N",
|
| + help="Maximum time to spend testing (in seconds)",
|
| + action="store", type=float, default=None)
|
| + group.add_option("", "--shuffle", dest="shuffle",
|
| + help="Run tests in random order",
|
| + action="store_true", default=False)
|
| + group.add_option("-i", "--incremental", dest="incremental",
|
| + help="Run modified and failing tests first (updates "
|
| + "mtimes)",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--filter", dest="filter", metavar="REGEX",
|
| + help=("Only run tests with paths matching the given "
|
| + "regular expression"),
|
| + action="store", default=None)
|
| + parser.add_option_group(group)
|
| +
|
| + group = OptionGroup(parser, "Debug and Experimental Options")
|
| + group.add_option("", "--debug", dest="debug",
|
| + help="Enable debugging (for 'lit' development)",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--show-suites", dest="showSuites",
|
| + help="Show discovered test suites",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--show-tests", dest="showTests",
|
| + help="Show all discovered tests",
|
| + action="store_true", default=False)
|
| + group.add_option("", "--use-processes", dest="useProcesses",
|
| + help="Run tests in parallel with processes (not threads)",
|
| + action="store_true", default=useProcessesIsDefault)
|
| + group.add_option("", "--use-threads", dest="useProcesses",
|
| + help="Run tests in parallel with threads (not processes)",
|
| + action="store_false", default=useProcessesIsDefault)
|
| + parser.add_option_group(group)
|
| +
|
| + (opts, args) = parser.parse_args()
|
| +
|
| + if opts.show_version:
|
| + print("lit %s" % (lit.__version__,))
|
| + return
|
| +
|
| + if not args:
|
| + parser.error('No inputs specified')
|
| +
|
| + if opts.numThreads is None:
|
| +# Python <2.5 has a race condition causing lit to always fail with numThreads>1
|
| +# http://bugs.python.org/issue1731717
|
| +# I haven't seen this bug occur with 2.5.2 and later, so only enable multiple
|
| +# threads by default there.
|
| + if sys.hexversion >= 0x2050200:
|
| + opts.numThreads = lit.util.detectCPUs()
|
| + else:
|
| + opts.numThreads = 1
|
| +
|
| + inputs = args
|
| +
|
| + # Create the user defined parameters.
|
| + userParams = dict(builtinParameters)
|
| + for entry in opts.userParameters:
|
| + if '=' not in entry:
|
| + name,val = entry,''
|
| + else:
|
| + name,val = entry.split('=', 1)
|
| + userParams[name] = val
|
| +
|
| + # Create the global config object.
|
| + litConfig = lit.LitConfig.LitConfig(
|
| + progname = os.path.basename(sys.argv[0]),
|
| + path = opts.path,
|
| + quiet = opts.quiet,
|
| + useValgrind = opts.useValgrind,
|
| + valgrindLeakCheck = opts.valgrindLeakCheck,
|
| + valgrindArgs = opts.valgrindArgs,
|
| + noExecute = opts.noExecute,
|
| + debug = opts.debug,
|
| + isWindows = isWindows,
|
| + params = userParams,
|
| + config_prefix = opts.configPrefix)
|
| +
|
| + # Perform test discovery.
|
| + run = lit.run.Run(litConfig,
|
| + lit.discovery.find_tests_for_inputs(litConfig, inputs))
|
| +
|
| + if opts.showSuites or opts.showTests:
|
| + # Aggregate the tests by suite.
|
| + suitesAndTests = {}
|
| + for result_test in run.tests:
|
| + if result_test.suite not in suitesAndTests:
|
| + suitesAndTests[result_test.suite] = []
|
| + suitesAndTests[result_test.suite].append(result_test)
|
| + suitesAndTests = list(suitesAndTests.items())
|
| + suitesAndTests.sort(key = lambda item: item[0].name)
|
| +
|
| + # Show the suites, if requested.
|
| + if opts.showSuites:
|
| + print('-- Test Suites --')
|
| + for ts,ts_tests in suitesAndTests:
|
| + print(' %s - %d tests' %(ts.name, len(ts_tests)))
|
| + print(' Source Root: %s' % ts.source_root)
|
| + print(' Exec Root : %s' % ts.exec_root)
|
| +
|
| + # Show the tests, if requested.
|
| + if opts.showTests:
|
| + print('-- Available Tests --')
|
| + for ts,ts_tests in suitesAndTests:
|
| + ts_tests.sort(key = lambda test: test.path_in_suite)
|
| + for test in ts_tests:
|
| + print(' %s' % (test.getFullName(),))
|
| +
|
| + # Exit.
|
| + sys.exit(0)
|
| +
|
| + # Select and order the tests.
|
| + numTotalTests = len(run.tests)
|
| +
|
| + # First, select based on the filter expression if given.
|
| + if opts.filter:
|
| + try:
|
| + rex = re.compile(opts.filter)
|
| + except:
|
| + parser.error("invalid regular expression for --filter: %r" % (
|
| + opts.filter))
|
| + run.tests = [result_test for result_test in run.tests
|
| + if rex.search(result_test.getFullName())]
|
| +
|
| + # Then select the order.
|
| + if opts.shuffle:
|
| + random.shuffle(run.tests)
|
| + elif opts.incremental:
|
| + sort_by_incremental_cache(run)
|
| + else:
|
| + run.tests.sort(key = lambda result_test: result_test.getFullName())
|
| +
|
| + # Finally limit the number of tests, if desired.
|
| + if opts.maxTests is not None:
|
| + run.tests = run.tests[:opts.maxTests]
|
| +
|
| + # Don't create more threads than tests.
|
| + opts.numThreads = min(len(run.tests), opts.numThreads)
|
| +
|
| + extra = ''
|
| + if len(run.tests) != numTotalTests:
|
| + extra = ' of %d' % numTotalTests
|
| + header = '-- Testing: %d%s tests, %d threads --'%(len(run.tests), extra,
|
| + opts.numThreads)
|
| +
|
| + progressBar = None
|
| + if not opts.quiet:
|
| + if opts.succinct and opts.useProgressBar:
|
| + try:
|
| + tc = lit.ProgressBar.TerminalController()
|
| + progressBar = lit.ProgressBar.ProgressBar(tc, header)
|
| + except ValueError:
|
| + print(header)
|
| + progressBar = lit.ProgressBar.SimpleProgressBar('Testing: ')
|
| + else:
|
| + print(header)
|
| +
|
| + startTime = time.time()
|
| + display = TestingProgressDisplay(opts, len(run.tests), progressBar)
|
| + try:
|
| + run.execute_tests(display, opts.numThreads, opts.maxTime,
|
| + opts.useProcesses)
|
| + except KeyboardInterrupt:
|
| + sys.exit(2)
|
| + display.finish()
|
| +
|
| + testing_time = time.time() - startTime
|
| + if not opts.quiet:
|
| + print('Testing Time: %.2fs' % (testing_time,))
|
| +
|
| + # Write out the test data, if requested.
|
| + if opts.output_path is not None:
|
| + write_test_results(run, litConfig, testing_time, opts.output_path)
|
| +
|
| + # List test results organized by kind.
|
| + hasFailures = False
|
| + byCode = {}
|
| + for test in run.tests:
|
| + if test.result.code not in byCode:
|
| + byCode[test.result.code] = []
|
| + byCode[test.result.code].append(test)
|
| + if test.result.code.isFailure:
|
| + hasFailures = True
|
| +
|
| + # Print each test in any of the failing groups.
|
| + for title,code in (('Unexpected Passing Tests', lit.Test.XPASS),
|
| + ('Failing Tests', lit.Test.FAIL),
|
| + ('Unresolved Tests', lit.Test.UNRESOLVED),
|
| + ('Unsupported Tests', lit.Test.UNSUPPORTED),
|
| + ('Expected Failing Tests', lit.Test.XFAIL)):
|
| + if (lit.Test.XFAIL == code and not opts.show_xfail) or \
|
| + (lit.Test.UNSUPPORTED == code and not opts.show_unsupported):
|
| + continue
|
| + elts = byCode.get(code)
|
| + if not elts:
|
| + continue
|
| + print('*'*20)
|
| + print('%s (%d):' % (title, len(elts)))
|
| + for test in elts:
|
| + print(' %s' % test.getFullName())
|
| + sys.stdout.write('\n')
|
| +
|
| + if opts.timeTests and run.tests:
|
| + # Order by time.
|
| + test_times = [(test.getFullName(), test.result.elapsed)
|
| + for test in run.tests]
|
| + lit.util.printHistogram(test_times, title='Tests')
|
| +
|
| + for name,code in (('Expected Passes ', lit.Test.PASS),
|
| + ('Passes With Retry ', lit.Test.FLAKYPASS),
|
| + ('Expected Failures ', lit.Test.XFAIL),
|
| + ('Unsupported Tests ', lit.Test.UNSUPPORTED),
|
| + ('Unresolved Tests ', lit.Test.UNRESOLVED),
|
| + ('Unexpected Passes ', lit.Test.XPASS),
|
| + ('Unexpected Failures', lit.Test.FAIL)):
|
| + if opts.quiet and not code.isFailure:
|
| + continue
|
| + N = len(byCode.get(code,[]))
|
| + if N:
|
| + print(' %s: %d' % (name,N))
|
| +
|
| + if opts.xunit_output_file:
|
| + # Collect the tests, indexed by test suite
|
| + by_suite = {}
|
| + for result_test in run.tests:
|
| + suite = result_test.suite.config.name
|
| + if suite not in by_suite:
|
| + by_suite[suite] = {
|
| + 'passes' : 0,
|
| + 'failures' : 0,
|
| + 'tests' : [] }
|
| + by_suite[suite]['tests'].append(result_test)
|
| + if result_test.result.code.isFailure:
|
| + by_suite[suite]['failures'] += 1
|
| + else:
|
| + by_suite[suite]['passes'] += 1
|
| + xunit_output_file = open(opts.xunit_output_file, "w")
|
| + xunit_output_file.write("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n")
|
| + xunit_output_file.write("<testsuites>\n")
|
| + for suite_name, suite in by_suite.items():
|
| + safe_suite_name = suite_name.replace(".", "-")
|
| + xunit_output_file.write("<testsuite name='" + safe_suite_name + "'")
|
| + xunit_output_file.write(" tests='" + str(suite['passes'] +
|
| + suite['failures']) + "'")
|
| + xunit_output_file.write(" failures='" + str(suite['failures']) +
|
| + "'>\n")
|
| + for result_test in suite['tests']:
|
| + xunit_output_file.write(result_test.getJUnitXML() + "\n")
|
| + xunit_output_file.write("</testsuite>\n")
|
| + xunit_output_file.write("</testsuites>")
|
| + xunit_output_file.close()
|
| +
|
| + # If we encountered any additional errors, exit abnormally.
|
| + if litConfig.numErrors:
|
| + sys.stderr.write('\n%d error(s), exiting.\n' % litConfig.numErrors)
|
| + sys.exit(2)
|
| +
|
| + # Warn about warnings.
|
| + if litConfig.numWarnings:
|
| + sys.stderr.write('\n%d warning(s) in tests.\n' % litConfig.numWarnings)
|
| +
|
| + if hasFailures:
|
| + sys.exit(1)
|
| + sys.exit(0)
|
| +
|
| +if __name__=='__main__':
|
| + main()
|
|
|