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, test_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.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 @@ -31,6 +31,7 @@ XPASS = ResultCode('XPASS', True) UNRESOLVED = ResultCode('UNRESOLVED', True) UNSUPPORTED = ResultCode('UNSUPPORTED', False) +TIMEOUT = ResultCode('TIMEOUT', True) # Test metric values. @@ -155,7 +156,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) @@ -208,4 +209,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,10 @@ group.add_option("", "--show-xfail", dest="show_xfail", help="Show tests that were expected to fail", action="store_true", default=False) + group.add_option("", "--show-timeout", dest="show_timeout", + help="Show timed out tests", + action="store_true", default=False) + parser.add_option_group(group) group = OptionGroup(parser, "Test Execution") @@ -214,6 +218,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) @@ -289,7 +296,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, @@ -402,9 +410,11 @@ ('Failing Tests', lit.Test.FAIL), ('Unresolved Tests', lit.Test.UNRESOLVED), ('Unsupported Tests', lit.Test.UNSUPPORTED), + ('Timeout Exceeded Tests', lit.Test.TIMEOUT), ('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): + (lit.Test.UNSUPPORTED == code and not opts.show_unsupported) or\ + (lit.Test.TIMEOUT == code and not opts.show_timeout): continue elts = byCode.get(code) if not elts: @@ -425,6 +435,7 @@ ('Expected Failures ', lit.Test.XFAIL), ('Unsupported Tests ', lit.Test.UNSUPPORTED), ('Unresolved Tests ', lit.Test.UNRESOLVED), + ('Timeout Exceeded ', lit.Test.TIMEOUT), ('Unexpected Passes ', lit.Test.XPASS), ('Unexpected Failures', lit.Test.FAIL)): if opts.quiet and not code.isFailure: 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.TIMEOUT, 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,15 +146,37 @@ # 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): +timed_out = {} +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) + + global timed_out + timed_out[p.pid] = False + timeout_timer = None + if timeout is not None: + def timeout_handler(p): + try: + p.kill() + except OSError, e: + # The Popen was already terminated + pass + timed_out[p.pid] = True + timeout_timer = threading.Timer(timeout, timeout_handler, [p]) + timeout_timer.start() + out,err = p.communicate() exitCode = p.wait() + if timeout is not None and timed_out[p.pid]: + raise Test.TimeoutException(timeout) + + if timeout_timer is not None: + timeout_timer.cancel() + # Detect Ctrl-C in subprocess. if exitCode == -signal.SIGINT: raise KeyboardInterrupt