Index: utils/lit/lit/LitConfig.py =================================================================== --- utils/lit/lit/LitConfig.py +++ utils/lit/lit/LitConfig.py @@ -21,7 +21,7 @@ def __init__(self, progname, path, quiet, useValgrind, valgrindLeakCheck, valgrindArgs, noExecute, debug, isWindows, - params, config_prefix = None): + params, config_prefix = None, test_timeout = None): # The name of the test runner. self.progname = progname # The items to add to the PATH environment variable. @@ -57,6 +57,7 @@ self.valgrindArgs.append('--leak-check=no') self.valgrindArgs.extend(self.valgrindUserArgs) + self.test_timeout = test_timeout def load_config(self, config, path): """load_config(config, path) - Load a config object from an alternate Index: utils/lit/lit/Test.py =================================================================== --- utils/lit/lit/Test.py +++ utils/lit/lit/Test.py @@ -155,7 +155,7 @@ self.result.code = XPASS elif self.result.code == FAIL: self.result.code = XFAIL - + def getFullName(self): return self.suite.config.name + ' :: ' + '/'.join(self.path_in_suite) @@ -216,4 +216,9 @@ xml += "\n\t\n" else: xml += "/>" - return xml \ No newline at end of file + return xml + +class TimeoutException(Exception): + def __init__(self, duration): + self.duration = duration + Index: utils/lit/lit/TestRunner.py =================================================================== --- utils/lit/lit/TestRunner.py +++ utils/lit/lit/TestRunner.py @@ -305,7 +305,8 @@ command = litConfig.valgrindArgs + command return lit.util.executeCommand(command, cwd=cwd, - env=test.config.environment) + env=test.config.environment, + timeout=litConfig.test_timeout) def parseIntegratedTestScriptCommands(source_path): """ Index: utils/lit/lit/formats/base.py =================================================================== --- utils/lit/lit/formats/base.py +++ utils/lit/lit/formats/base.py @@ -101,7 +101,7 @@ else: cmd.append(test.getSourcePath()) - out, err, exitCode = lit.util.executeCommand(cmd) + out, err, exitCode = lit.util.executeCommand(cmd, timeout=litConfig.test_timeout) diags = out + err if not exitCode and not diags.strip(): Index: utils/lit/lit/main.py =================================================================== --- utils/lit/lit/main.py +++ utils/lit/lit/main.py @@ -175,6 +175,7 @@ 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") @@ -208,6 +209,9 @@ 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("", "--test-timeout", dest="testTimeout", metavar="N", + help="Maximum time to spend in any given test (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) @@ -281,7 +285,8 @@ debug = opts.debug, isWindows = isWindows, params = userParams, - config_prefix = opts.configPrefix) + config_prefix = opts.configPrefix, + test_timeout = opts.testTimeout) # Perform test discovery. run = lit.run.Run(litConfig, Index: utils/lit/lit/run.py =================================================================== --- utils/lit/lit/run.py +++ utils/lit/lit/run.py @@ -174,6 +174,9 @@ raise ValueError("unexpected result from test execution") except KeyboardInterrupt: raise + except lit.Test.TimeoutException, t: + output = 'Timeout after %d seconds\n' % (t.duration) + result = lit.Test.Result(lit.Test.FAIL, output) except: if self.lit_config.debug: raise Index: utils/lit/lit/util.py =================================================================== --- utils/lit/lit/util.py +++ utils/lit/lit/util.py @@ -6,7 +6,10 @@ import signal import subprocess import sys +import threading +import Test + def detectCPUs(): """ Detects the number of CPUs on a system. Cribbed from pp. @@ -143,7 +146,7 @@ # Close extra file handles on UNIX (on Windows this cannot be done while # also redirecting input). kUseCloseFDs = not (platform.system() == 'Windows') -def executeCommand(command, cwd=None, env=None): +def executeCommand(command, cwd=None, env=None, timeout=None): p = subprocess.Popen(command, cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, @@ -150,6 +153,23 @@ stderr=subprocess.PIPE, env=env, close_fds=kUseCloseFDs) out,err = p.communicate() + + # Use a list so it has reference semantics + timed_out = [False] + if timeout is not None: + def timeout_handler(p, timed_out): + try: + p.kill() + except OSError, e: + # The Popen was already terminated + pass + timed_out[0] = True + timeout_timer = threading.Timer(timeout, timeout_handler, [p, timed_out]) + timeout_timer.start() + + if timed_out[0]: + raise Test.TimeoutException(timeout) + exitCode = p.wait() # Detect Ctrl-C in subprocess.