Index: utils/lit/lit/run.py =================================================================== --- utils/lit/lit/run.py +++ utils/lit/lit/run.py @@ -1,4 +1,5 @@ import os +import math import sys import threading import time @@ -93,6 +94,17 @@ for a in async_results: if deadline: a.wait(deadline - time.time()) + if not a.ready(): + # We reached the deadline + assert time.time() >= deadline + self.notify_max_total_time_reached() + # FIXME: We should find a way to find the currently + # running tests and mark them as TIMEOUT. Right now + # because they don't get proceessed they get marked as + # UNRESOLVED which is wrong. + pool.terminate() + pool.join() + break else: # Python condition variables cannot be interrupted unless # they have a timeout. This can make lit unresponsive to @@ -111,6 +123,52 @@ finally: pool.join() + def execute_tests_sequentially_in_process(self, max_time): + deadline = None + if max_time: + deadline = time.time() + max_time + + # `worker_run_one_test()` currently requires the `child_lit_config` + # global to be set up. + global child_lit_config + child_lit_config = self.lit_config + + # Execute tests sequentially. + for test_index, test in enumerate(self.tests): + if max_time is not None: + # FIXME: This is a hack. We set the per test timeout to be the + # minimum of the remaining time to the deadline and the current + # timeout value. This is only safe to do because we are + # running tests sequentially, and so modifying the global + # lit_config object is fine. + # + # Using `maxIndividualTestTime` means we inherit its dependencies. + # This means that we need Python's `psutil` in order for this to work + # right now. + remaining_time = int(math.ceil(deadline - time.time())) + if remaining_time <= 0: + self.notify_max_total_time_reached() + break + if self.lit_config.maxIndividualTestTime > 0: + self.lit_config.maxIndividualTestTime = min( + self.lit_config.maxIndividualTestTime, + remaining_time + ) + else: + self.lit_config.maxIndividualTestTime = remaining_time + result = worker_run_one_test(test_index, test) + self.consume_test_result(result) + if max_time is not None: + if time.time() >= deadline: + self.notify_max_total_time_reached() + break + + def notify_max_total_time_reached(self): + self.lit_config.warning( + 'Used up max time. ' + 'Not running any more tests' + ) + def execute_tests(self, display, jobs, max_time=None): """ execute_tests(display, jobs, [max_time]) @@ -143,11 +201,7 @@ self.failure_count = 0 self.hit_max_failures = False if self.lit_config.singleProcess: - global child_lit_config - child_lit_config = self.lit_config - for test_index, test in enumerate(self.tests): - result = worker_run_one_test(test_index, test) - self.consume_test_result(result) + self.execute_tests_sequentially_in_process(max_time) else: self.execute_tests_in_pool(jobs, max_time) Index: utils/lit/tests/Inputs/shtest-max-global-time/0_short.py =================================================================== --- /dev/null +++ utils/lit/tests/Inputs/shtest-max-global-time/0_short.py @@ -0,0 +1,4 @@ +# RUN: %{python} %s +from __future__ import print_function + +print("short program") Index: utils/lit/tests/Inputs/shtest-max-global-time/1_infinite_loop.py =================================================================== --- /dev/null +++ utils/lit/tests/Inputs/shtest-max-global-time/1_infinite_loop.py @@ -0,0 +1,9 @@ +# RUN: %{python} %s +from __future__ import print_function + +import sys + +print("Running infinite loop") +sys.stdout.flush() # Make sure the print gets flushed so it appears in lit output. +while True: + pass Index: utils/lit/tests/Inputs/shtest-max-global-time/lit.cfg =================================================================== --- /dev/null +++ utils/lit/tests/Inputs/shtest-max-global-time/lit.cfg @@ -0,0 +1,17 @@ +# -*- Python -*- +import os +import sys + +import lit.formats + +config.name = 'max_global_time' + +config.test_format = lit.formats.ShTest(execute_external=True) +config.suffixes = ['.py'] + +config.test_source_root = os.path.dirname(__file__) +config.test_exec_root = config.test_source_root +config.target_triple = '(unused)' +src_root = os.path.join(config.test_source_root, '..') +config.environment['PYTHONPATH'] = src_root +config.substitutions.append(('%{python}', sys.executable)) Index: utils/lit/tests/shtest-max-global-time-multiprocess.py =================================================================== --- /dev/null +++ utils/lit/tests/shtest-max-global-time-multiprocess.py @@ -0,0 +1,17 @@ +# RUN: not %{lit} \ +# RUN: %{inputs}/shtest-max-global-time/0_short.py \ +# RUN: %{inputs}/shtest-max-global-time/1_infinite_loop.py \ +# RUN: -j1 -v --max-time 5 > %t.out 2> %t.err +# RUN: FileCheck --check-prefix=CHECK-OUT < %t.out %s +# RUN: FileCheck --check-prefix=CHECK-ERR < %t.err %s + +# CHECK-OUT: PASS: max_global_time :: 0_short.py + +# FIXME: This test should really be a TIMEOUT because we started running +# it and had to kill it. +# CHECK-OUT: Unresolved Tests (1): +# CHECK-OUT-NEXT: max_global_time :: 1_infinite_loop.py + +# CHECK-OUT: Expected Passes : 1 +# CHECK-OUT: Unresolved Tests : 1 +# CHECK-ERR: warning: Used up max time. Not running any more tests Index: utils/lit/tests/shtest-max-global-time-single-process.py =================================================================== --- /dev/null +++ utils/lit/tests/shtest-max-global-time-single-process.py @@ -0,0 +1,22 @@ +# FIXME: The current implementation of `--max-time` when using +# a single process uses the per test timeout feature which +# requires the psutil python module. + +# REQUIRES: python-psutil +# PR33944 +# XFAIL: windows + +# RUN: not %{lit} \ +# RUN: %{inputs}/shtest-max-global-time/0_short.py \ +# RUN: %{inputs}/shtest-max-global-time/1_infinite_loop.py \ +# RUN: -j1 -v --max-time 5 --single-process > %t.out 2> %t.err +# RUN: FileCheck --check-prefix=CHECK-OUT < %t.out %s +# RUN: FileCheck --check-prefix=CHECK-ERR < %t.err %s + +# CHECK-OUT: PASS: max_global_time :: 0_short.py + +# CHECK-OUT: TIMEOUT: max_global_time :: 1_infinite_loop.py + +# CHECK-OUT: Expected Passes : 1 +# CHECK-OUT: Individual Timeouts: 1 +# CHECK-ERR: warning: Used up max time. Not running any more tests