[lit] Refactor test execution logic into lit.run.Run.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@189554 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Daniel Dunbar 2013-08-29 00:54:15 +00:00
parent b11b690d3f
commit 07f0f16bfd
2 changed files with 158 additions and 123 deletions

View File

@ -7,17 +7,16 @@ See lit.pod for more information.
""" """
from __future__ import absolute_import 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.ProgressBar
import lit.LitConfig import lit.LitConfig
import lit.Test import lit.Test
import lit.run import lit.run
import lit.util import lit.util
import lit.discovery import lit.discovery
class TestingProgressDisplay: class TestingProgressDisplay(object):
def __init__(self, opts, numTests, progressBar=None): def __init__(self, opts, numTests, progressBar=None):
self.opts = opts self.opts = opts
self.numTests = numTests self.numTests = numTests
@ -57,107 +56,6 @@ class TestingProgressDisplay:
sys.stdout.flush() 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 = {}): def main(builtinParameters = {}):
# Bump the GIL check interval, its more important to get any one thread to a # 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. # blocking operation (hopefully exec) than to try and unblock other threads.
@ -365,19 +263,8 @@ def main(builtinParameters = {}):
startTime = time.time() startTime = time.time()
display = TestingProgressDisplay(opts, len(run.tests), progressBar) display = TestingProgressDisplay(opts, len(run.tests), progressBar)
provider = TestProvider(run.tests, opts.maxTime)
try: try:
import win32api run.execute_tests(display, opts.numThreads, opts.maxTime)
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)
except KeyboardInterrupt: except KeyboardInterrupt:
sys.exit(2) sys.exit(2)
display.finish() display.finish()
@ -385,11 +272,6 @@ def main(builtinParameters = {}):
if not opts.quiet: if not opts.quiet:
print('Testing Time: %.2fs'%(time.time() - startTime)) 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. # List test results organized by kind.
hasFailures = False hasFailures = False
byCode = {} byCode = {}

View File

@ -1,8 +1,98 @@
import os
import threading
import time import time
import traceback import traceback
try:
import win32api
except ImportError:
win32api = None
import lit.Test 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): class Run(object):
""" """
This class represents a concrete, configured testing run. This class represents a concrete, configured testing run.
@ -14,7 +104,7 @@ class Run(object):
def execute_test(self, test): def execute_test(self, test):
result = None result = None
startTime = time.time() start_time = time.time()
try: try:
result = test.config.test_format.execute(test, self.lit_config) result = test.config.test_format.execute(test, self.lit_config)
@ -34,6 +124,69 @@ class Run(object):
output += traceback.format_exc() output += traceback.format_exc()
output += '\n' output += '\n'
result = lit.Test.Result(lit.Test.UNRESOLVED, output) result = lit.Test.Result(lit.Test.UNRESOLVED, output)
result.elapsed = time.time() - startTime result.elapsed = time.time() - start_time
test.setResult(result) 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()