This option allows to set timeout for run_test. If timeout expires before run_test completes, the test will be killed (test fails). If no timeout parameter is present or if it is None, no timeout will be enforced.
Example: job.run_test('sleeptest', timeout=5, seconds=60) Signed-off-by: Jan Stancek <jstan...@redhat.com> --- client/bin/job.py | 16 +++++++++++++--- client/bin/job_unittest.py | 35 +++++++++++++++++++++++++++++++++-- client/bin/parallel.py | 29 ++++++++++++++++++++++++++++- 3 files changed, 74 insertions(+), 6 deletions(-) diff --git a/client/bin/job.py b/client/bin/job.py index 8b8997e..3731e23 100644 --- a/client/bin/job.py +++ b/client/bin/job.py @@ -515,11 +515,17 @@ class base_client_job(base_job.base_job): logging.info('Dependency %s successfuly built', dep) - def _runtest(self, url, tag, args, dargs): + def _runtest(self, url, tag, timeout, args, dargs): try: l = lambda : test.runtest(self, url, tag, args, dargs) pid = parallel.fork_start(self.resultdir, l) - parallel.fork_waitfor(self.resultdir, pid) + + if timeout: + logging.debug('Waiting for pid %d for %d seconds', pid, timeout) + parallel.fork_waitfor_timed(self.resultdir, pid, timeout) + else: + parallel.fork_waitfor(self.resultdir, pid) + except error.TestBaseException: # These are already classified with an error type (exit_status) raise @@ -549,12 +555,16 @@ class base_client_job(base_job.base_job): testname, subdir, tag = self._build_tagged_test_name(testname, dargs) outputdir = self._make_test_outputdir(subdir) + timeout = dargs.pop('timeout', None) + if timeout: + logging.debug('Test has timeout: %d sec.', timeout) + def log_warning(reason): self.record("WARN", subdir, testname, reason) @disk_usage_monitor.watch(log_warning, "/", self._max_disk_usage_rate) def group_func(): try: - self._runtest(url, tag, args, dargs) + self._runtest(url, tag, timeout, args, dargs) except error.TestBaseException, detail: # The error is already classified, record it properly. self.record(detail.exit_status, subdir, testname, str(detail)) diff --git a/client/bin/job_unittest.py b/client/bin/job_unittest.py index 88fa272..6b50ed0 100755 --- a/client/bin/job_unittest.py +++ b/client/bin/job_unittest.py @@ -505,7 +505,7 @@ class test_base_job(unittest.TestCase): testname, 'test').and_return(("", testname)) os.path.exists.expect_call(outputdir).and_return(False) self.job.record.expect_call("START", testname, testname) - self.job._runtest.expect_call(testname, "", (), {}).and_raises( + self.job._runtest.expect_call(testname, "", None, (), {}).and_raises( unhandled_error) self.job.record.expect_call("ERROR", testname, testname, first_line_comparator(str(real_error))) @@ -539,7 +539,7 @@ class test_base_job(unittest.TestCase): testname, 'test').and_return(("", testname)) os.path.exists.expect_call(outputdir).and_return(False) self.job.record.expect_call("START", testname, testname) - self.job._runtest.expect_call(testname, "", (), {}).and_raises( + self.job._runtest.expect_call(testname, "", None, (), {}).and_raises( unhandled_error) self.job.record.expect_call("ERROR", testname, testname, reason) self.job.record.expect_call("END ERROR", testname, testname) @@ -713,5 +713,36 @@ class test_base_job(unittest.TestCase): self.assertEqual(parsed_args, expected_args) + def test_run_test_timeout_parameter_is_propagated(self): + self.construct_job(True) + + # set up stubs + self.god.stub_function(self.job.pkgmgr, 'get_package_name') + self.god.stub_function(self.job, "_runtest") + + # create an unhandled error object + #class MyError(error.TestError): + # pass + #real_error = MyError("this is the real error message") + #unhandled_error = error.UnhandledTestError(real_error) + + # set up the recording + testname = "sleeptest" + outputdir = os.path.join(self.job.resultdir, testname) + self.job.pkgmgr.get_package_name.expect_call( + testname, 'test').and_return(("", testname)) + os.path.exists.expect_call(outputdir).and_return(False) + self.job.record.expect_call("START", testname, testname) + self.job._runtest.expect_call(testname, "", 60, (), {}) + self.job.record.expect_call("GOOD", testname, testname, 'completed successfully') + self.job.record.expect_call("END GOOD", testname, testname) + self.job.harness.run_test_complete.expect_call() + utils.drop_caches.expect_call() + + # run and check + self.job.run_test(testname, timeout=60) + self.god.check_playback() + + if __name__ == "__main__": unittest.main() diff --git a/client/bin/parallel.py b/client/bin/parallel.py index 47dd5cd..c426968 100644 --- a/client/bin/parallel.py +++ b/client/bin/parallel.py @@ -2,7 +2,7 @@ __author__ = """Copyright Andy Whitcroft 2006""" -import sys, logging, os, pickle, traceback, gc +import sys, logging, os, pickle, traceback, gc, time from autotest_lib.client.common_lib import error, utils def fork_start(tmp, l): @@ -76,6 +76,33 @@ def fork_waitfor(tmp, pid): if status: raise error.TestError("Test subprocess failed rc=%d" % (status)) +def fork_waitfor_timed(tmp, pid, timeout): + """ + Waits for pid until it terminates or timeout expires. + If timeout expires, test subprocess is killed. + """ + timer_expired = True + poll_time = 2 + time_passed = 0 + while time_passed < timeout: + time.sleep(poll_time) + (child_pid, status) = os.waitpid(pid, os.WNOHANG) + if (child_pid, status) == (0, 0): + time_passed = time_passed + poll_time + else: + timer_expired = False + break + + if timer_expired: + logging.info('Timer expired (%d sec.), nuking pid %d', timeout, pid) + utils.nuke_pid(pid) + (child_pid, status) = os.waitpid(pid, 0) + raise error.TestError("Test timeout expired, rc=%d" % (status)) + else: + _check_for_subprocess_exception(tmp, pid) + + if status: + raise error.TestError("Test subprocess failed rc=%d" % (status)) def fork_nuke_subprocess(tmp, pid): utils.nuke_pid(pid) -- 1.7.1 _______________________________________________ Autotest mailing list Autotest@test.kernel.org http://test.kernel.org/cgi-bin/mailman/listinfo/autotest