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, useQEMU, qemuArgs, useValgrind, valgrindLeakCheck, valgrindArgs, noExecute, debug, isWindows, - params, config_prefix = None): + params, config_prefix = None, timeout = None): # The name of the test runner. self.progname = progname # The items to add to the PATH environment variable. @@ -63,6 +63,7 @@ self.valgrindArgs.append('--leak-check=no') self.valgrindArgs.extend(self.valgrindUserArgs) + self.timeout = timeout def load_config(self, config, path): """load_config(config, path) - Load a config object from an alternate 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.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.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 @@ -214,6 +214,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("", "--timeout", dest="timeout", 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) @@ -289,7 +292,8 @@ debug = opts.debug, isWindows = isWindows, params = userParams, - config_prefix = opts.configPrefix) + config_prefix = opts.configPrefix, + timeout = opts.timeout) # Perform test discovery. run = lit.run.Run(litConfig, 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,15 +146,46 @@ # 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, stderr=subprocess.PIPE, env=env, close_fds=kUseCloseFDs) + + class Watchdog(object): + def __init__(self, popen, timeout): + self.timeout_tripped = False + self.timer = None + if timeout is not None: + def timeout_handler(): + try: + popen.kill() + except OSError, e: + # The Popen already terminated before the watchdog + # got acqauinted with it. Too bad they couldn't be + # friends + pass + self.timeout_tripped = True + timer = threading.Timer(timeout, timeout_handler) + timer.start() + + def cancel(self): + if self.timer is not None: + self.timer.cancel() + + def timed_out(self): + return self.timeout_tripped + + wd = Watchdog(p, timeout) + out,err = p.communicate() exitCode = p.wait() + if wd.timed_out(): + err += 'Test timed out after %d seconds\n' % (timeout) + wd.cancel() + # Detect Ctrl-C in subprocess. if exitCode == -signal.SIGINT: raise KeyboardInterrupt