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

Reply via email to