From 57ab2df070ff2de83d31075689fcde3d2a77feff Mon Sep 17 00:00:00 2001 From: Daniel Dunbar Date: Thu, 29 Aug 2013 00:54:15 +0000 Subject: [PATCH] [lit] Refactor test execution logic into lit.run.Run. llvm-svn: 189554 --- llvm/utils/lit/lit/main.py | 124 +---------------------------- llvm/utils/lit/lit/run.py | 157 ++++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 123 deletions(-) diff --git a/llvm/utils/lit/lit/main.py b/llvm/utils/lit/lit/main.py index 29275ac34a70..5c40f1ca5337 100755 --- a/llvm/utils/lit/lit/main.py +++ b/llvm/utils/lit/lit/main.py @@ -7,17 +7,16 @@ See lit.pod for more information. """ from __future__ import absolute_import -import math, os, platform, random, re, sys, time, threading, traceback +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: +class TestingProgressDisplay(object): def __init__(self, opts, numTests, progressBar=None): self.opts = opts self.numTests = numTests @@ -57,107 +56,6 @@ class TestingProgressDisplay: sys.stdout.flush() -class TestProvider: - def __init__(self, tests, maxTime): - self.maxTime = maxTime - self.iter = iter(range(len(tests))) - self.lock = threading.Lock() - self.startTime = time.time() - self.canceled = False - - def cancel(self): - self.lock.acquire() - self.canceled = True - self.lock.release() - - def get(self): - # Check if we have run out of time. - if self.maxTime is not None: - if time.time() - self.startTime > self.maxTime: - return None - - # Otherwise take the next test. - self.lock.acquire() - if self.canceled: - self.lock.release() - return None - for item in self.iter: - break - else: - item = None - self.lock.release() - return item - -class Tester(object): - def __init__(self, run_instance, provider, consumer): - self.run_instance = run_instance - self.provider = provider - self.consumer = consumer - - def run(self): - while 1: - item = self.provider.get() - if item is None: - break - self.runTest(item) - self.consumer.taskFinished() - - def runTest(self, test_index): - test = self.run_instance.tests[test_index] - try: - self.run_instance.execute_test(test) - except KeyboardInterrupt: - # This is a sad hack. Unfortunately subprocess goes - # bonkers with ctrl-c and we start forking merrily. - print('\nCtrl-C detected, goodbye.') - os.kill(0,9) - self.consumer.update(test_index, test) - -class ThreadResultsConsumer(object): - def __init__(self, display): - self.display = display - self.lock = threading.Lock() - - def update(self, test_index, test): - self.lock.acquire() - try: - self.display.update(test) - finally: - self.lock.release() - - def taskFinished(self): - pass - - def handleResults(self): - pass - -def run_one_tester(run, provider, display): - tester = Tester(run, provider, display) - tester.run() - -def runTests(numThreads, run, provider, display): - consumer = ThreadResultsConsumer(display) - - # If only using one testing thread, don't use tasks at all; this lets us - # profile, among other things. - if numThreads == 1: - run_one_tester(run, provider, consumer) - return - - # Start all of the tasks. - tasks = [threading.Thread(target=run_one_tester, - args=(run, provider, consumer)) - for i in range(numThreads)] - for t in tasks: - t.start() - - # Allow the consumer to handle results, if necessary. - consumer.handleResults() - - # Wait for all the tasks to complete. - for t in tasks: - t.join() - def main(builtinParameters = {}): # Bump the GIL check interval, its more important to get any one thread to a # blocking operation (hopefully exec) than to try and unblock other threads. @@ -365,19 +263,8 @@ def main(builtinParameters = {}): startTime = time.time() display = TestingProgressDisplay(opts, len(run.tests), progressBar) - provider = TestProvider(run.tests, opts.maxTime) - try: - import win32api - except ImportError: - pass - else: - def console_ctrl_handler(type): - provider.cancel() - return True - win32api.SetConsoleCtrlHandler(console_ctrl_handler, True) - try: - runTests(opts.numThreads, run, provider, display) + run.execute_tests(display, opts.numThreads, opts.maxTime) except KeyboardInterrupt: sys.exit(2) display.finish() @@ -385,11 +272,6 @@ def main(builtinParameters = {}): if not opts.quiet: print('Testing Time: %.2fs'%(time.time() - startTime)) - # Update results for any tests which weren't run. - for test in run.tests: - if test.result is None: - test.setResult(lit.Test.Result(lit.Test.UNRESOLVED, '', 0.0)) - # List test results organized by kind. hasFailures = False byCode = {} diff --git a/llvm/utils/lit/lit/run.py b/llvm/utils/lit/lit/run.py index 44666679a60b..2ebce855ff9f 100644 --- a/llvm/utils/lit/lit/run.py +++ b/llvm/utils/lit/lit/run.py @@ -1,8 +1,98 @@ +import os +import threading import time import traceback +try: + import win32api +except ImportError: + win32api = None + import lit.Test +### +# Test Execution Implementation + +class TestProvider(object): + def __init__(self, tests, max_time): + self.max_time = max_time + self.iter = iter(range(len(tests))) + self.lock = threading.Lock() + self.start_time = time.time() + self.canceled = False + + def cancel(self): + self.lock.acquire() + self.canceled = True + self.lock.release() + + def get(self): + # Check if we have run out of time. + if self.max_time is not None: + if time.time() - self.start_time > self.max_time: + return None + + # Otherwise take the next test. + self.lock.acquire() + if self.canceled: + self.lock.release() + return None + for item in self.iter: + break + else: + item = None + self.lock.release() + return item + +class Tester(object): + def __init__(self, run_instance, provider, consumer): + self.run_instance = run_instance + self.provider = provider + self.consumer = consumer + + def run(self): + while 1: + item = self.provider.get() + if item is None: + break + self.run_test(item) + self.consumer.task_finished() + + def run_test(self, test_index): + test = self.run_instance.tests[test_index] + try: + self.run_instance.execute_test(test) + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print('\nCtrl-C detected, goodbye.') + os.kill(0,9) + self.consumer.update(test_index, test) + +class ThreadResultsConsumer(object): + def __init__(self, display): + self.display = display + self.lock = threading.Lock() + + def update(self, test_index, test): + self.lock.acquire() + try: + self.display.update(test) + finally: + self.lock.release() + + def task_finished(self): + pass + + def handle_results(self): + pass + +def run_one_tester(run, provider, display): + tester = Tester(run, provider, display) + tester.run() + +### + class Run(object): """ This class represents a concrete, configured testing run. @@ -14,7 +104,7 @@ class Run(object): def execute_test(self, test): result = None - startTime = time.time() + start_time = time.time() try: result = test.config.test_format.execute(test, self.lit_config) @@ -34,6 +124,69 @@ class Run(object): output += traceback.format_exc() output += '\n' result = lit.Test.Result(lit.Test.UNRESOLVED, output) - result.elapsed = time.time() - startTime + result.elapsed = time.time() - start_time test.setResult(result) + + def execute_tests(self, display, jobs, max_time=None): + """ + execute_tests(display, jobs, [max_time]) + + Execute each of the tests in the run, using up to jobs number of + parallel tasks, and inform the display of each individual result. The + provided tests should be a subset of the tests available in this run + object. + + If max_time is non-None, it should be a time in seconds after which to + stop executing tests. + + The display object will have its update method called with each test as + it is completed. The calls are guaranteed to be locked with respect to + one another, but are *not* guaranteed to be called on the same thread as + this method was invoked on. + + Upon completion, each test in the run will have its result + computed. Tests which were not actually executed (for any reason) will + be given an UNRESOLVED result. + """ + + # Create the test provider object. + provider = TestProvider(self.tests, max_time) + + # Install a console-control signal handler on Windows. + if win32api is not None: + def console_ctrl_handler(type): + provider.cancel() + return True + win32api.SetConsoleCtrlHandler(console_ctrl_handler, True) + + # Actually execute the tests. + self._execute_tests_with_provider(provider, display, jobs) + + # Update results for any tests which weren't run. + for test in self.tests: + if test.result is None: + test.setResult(lit.Test.Result(lit.Test.UNRESOLVED, '', 0.0)) + + def _execute_tests_with_provider(self, provider, display, jobs): + consumer = ThreadResultsConsumer(display) + + # If only using one testing thread, don't use tasks at all; this lets us + # profile, among other things. + if jobs == 1: + run_one_tester(self, provider, consumer) + return + + # Start all of the tasks. + tasks = [threading.Thread(target=run_one_tester, + args=(self, provider, consumer)) + for i in range(jobs)] + for t in tasks: + t.start() + + # Allow the consumer to handle results, if necessary. + consumer.handle_results() + + # Wait for all the tasks to complete. + for t in tasks: + t.join()