Index: test/dosep.py =================================================================== --- test/dosep.py +++ test/dosep.py @@ -7,15 +7,49 @@ import multiprocessing import os import platform +import shlex +import subprocess import sys from optparse import OptionParser -# Command template of the invocation of the test driver. -template = '%s %s/dotest.py %s -p %s %s' +def get_timeout_command(): + if sys.platform.startswith("win32"): + return None + try: + subprocess.call("timeout") + return "timeout" + except OSError: + pass + try: + subprocess.call("gtimeout") + return "gtimeout" + except OSError: + pass + return None + +timeout_command = get_timeout_command() + +# Status codes for running command with timeout. +eTimedOut, ePassed, eFailed = 124, 0, 1 + +def call_with_timeout(command, timeout): + """Timeout after 5 minutes by default. + Set timeout limit for all tests with LLDB_TEST_TIMEOUT. + E.g., LLDB_TEST_TIMEOUT=1h + Set timeout limit for individual tests with LLDB_[TEST NAME]_TIMEOUT. + E.g., LLDB_TESTCONCURRENTEVENTS_TIMEOUT=2m + Set to "0" to run without any limit.""" + if timeout_command is not None: + return subprocess.call([timeout_command, timeout] + command, + stdin=subprocess.PIPE) + else: + return (ePassed if subprocess.call(command, stdin=subprocess.PIPE) == 0 + else eFailed) def process_dir(root, files, test_root, dotest_options): """Examine a directory for tests, and invoke any found within it.""" + timed_out = [] failed = [] passed = [] for name in files: @@ -29,12 +63,22 @@ if os.path.islink(path): continue - command = template % (sys.executable, test_root, dotest_options if dotest_options else "", name, root) - if 0 != os.system(command): - failed.append(name) - else: + command = ([sys.executable, "%s/dotest.py" % test_root] + + (shlex.split(dotest_options) if dotest_options else []) + + ["-p", name, root]) + + exit_status = call_with_timeout( + command, + os.getenv("LLDB_" + name[:-3].upper() + "_TIMEOUT") or + os.getenv("LLDB_TEST_TIMEOUT") or "5m") + + if ePassed == exit_status: passed.append(name) - return (failed, passed) + else: + if eTimedOut == exit_status: + timed_out.append(name) + failed.append(name) + return (timed_out, failed, passed) in_q = None out_q = None @@ -66,15 +110,17 @@ for work_item in test_work_items: test_results.append(process_dir_worker(work_item)) + timed_out = [] failed = [] passed = [] for test_result in test_results: - (dir_failed, dir_passed) = test_result + (dir_timed_out, dir_failed, dir_passed) = test_result + timed_out += dir_timed_out failed += dir_failed passed += dir_passed - return (failed, passed) + return (timed_out, failed, passed) def main(): test_root = sys.path[0] @@ -107,7 +153,8 @@ num_threads = 1 system_info = " ".join(platform.uname()) - (failed, passed) = walk_and_invoke(test_root, dotest_options, num_threads) + (timed_out, failed, passed) = walk_and_invoke(test_root, dotest_options, + num_threads) num_tests = len(failed) + len(passed) print "Ran %d tests." % num_tests @@ -115,7 +162,9 @@ failed.sort() print "Failing Tests (%d)" % len(failed) for f in failed: - print "FAIL: LLDB (suite) :: %s (%s)" % (f, system_info) + print "%s: LLDB (suite) :: %s (%s)" % ( + "TIMEOUT" if f in timed_out else "FAIL", f, system_info + ) sys.exit(1) sys.exit(0)