[Piglit] [PATCH] framework: add a generic timeout mechanism
Thomas Wood
thomas.wood at intel.com
Tue Sep 9 08:15:12 PDT 2014
The timeout mechanism within igt.py can no longer be used since
get_command_result was renamed and made private in commit 5e9dd69
(exectest.py: rename get_command_result to __run_command). Therefore,
move the timeout mechanism into exectest.py, allowing all test profiles
to use it if needed and also avoiding code duplication between igt.py
and exectest.py.
Cc: Dylan Baker <baker.dylan.c at gmail.com>
Cc: Chad Versace <chad.versace at linux.intel.com>
Signed-off-by: Thomas Wood <thomas.wood at intel.com>
---
framework/exectest.py | 67 +++++++++++++++++++++++++++++++++++--
framework/profile.py | 3 +-
tests/igt.py | 93 +--------------------------------------------------
3 files changed, 68 insertions(+), 95 deletions(-)
diff --git a/framework/exectest.py b/framework/exectest.py
index 3ed9b6f..90920c6 100644
--- a/framework/exectest.py
+++ b/framework/exectest.py
@@ -29,6 +29,9 @@ import shlex
import time
import sys
import traceback
+from datetime import datetime
+import threading
+import signal
import itertools
import abc
try:
@@ -50,6 +53,45 @@ else:
TEST_BIN_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__),
'../bin'))
+# This class is for timing out tests that hang. Create an instance by passing
+# it a timeout in seconds and the Popen object that is running your test. Then
+# call the start method (inherited from Thread) to start the timer. The Popen
+# object is killed if the timeout is reached and it has not completed. Wait for
+# the outcome by calling the join() method from the parent.
+
+class ProcessTimeout(threading.Thread):
+ def __init__(self, timeout, proc):
+ threading.Thread.__init__(self)
+ self.proc = proc
+ self.timeout = timeout
+ self.status = 0
+
+ def run(self):
+ start_time = datetime.now()
+ delta = 0
+ while (delta < self.timeout) and (self.proc.poll() is None):
+ time.sleep(1)
+ delta = (datetime.now() - start_time).total_seconds()
+
+ # if the test is not finished after timeout, first try to terminate it
+ # and if that fails, send SIGKILL to all processes in the test's
+ # process group
+
+ if self.proc.poll() is None:
+ self.status = 1
+ self.proc.terminate()
+ time.sleep(5)
+
+ if self.proc.poll() is None:
+ self.status = 2
+ if hasattr(os, 'killpg'):
+ os.killpg(self.proc.pid, signal.SIGKILL)
+ self.proc.wait()
+
+
+ def join(self):
+ threading.Thread.join(self)
+ return self.status
class Test(object):
""" Abstract base class for Test classes
@@ -82,12 +124,14 @@ class Test(object):
self.env = {}
self.result = TestResult({'result': 'fail'})
self.cwd = None
+ self.timeout = 0
+ self.__proc_timeout = None
# This is a hook for doing some testing on execute right before
# self.run is called.
self._test_hook_execute_run = lambda: None
- def execute(self, path, log, dmesg):
+ def execute(self, path, log, dmesg, timeout):
""" Run a test
Run a test, but with features. This times the test, uses dmesg checking
@@ -99,6 +143,7 @@ class Test(object):
dmesg -- a dmesg.BaseDmesg derived class
"""
+ self.timeout = timeout
log_current = log.pre_log(path if self.OPTS.verbose else None)
# Run the test
@@ -199,6 +244,11 @@ class Test(object):
# Test passed but has valgrind errors.
self.result['result'] = 'fail'
+ if self.timeout > 0:
+ # check for timeout
+ if self.__proc_timeout.join() > 0:
+ self.result['result'] = 'timeout'
+
def is_skip(self):
""" Application specific check for skip
@@ -208,6 +258,10 @@ class Test(object):
"""
return False
+ def __set_process_group(self):
+ if hasattr(os, 'setpgrp'):
+ os.setpgrp()
+
def __run_command(self):
""" Run the test command and get the result
@@ -240,7 +294,16 @@ class Test(object):
stderr=subprocess.PIPE,
cwd=self.cwd,
env=fullenv,
- universal_newlines=True)
+ universal_newlines=True,
+ preexec_fn=self.__set_process_group)
+ # create a ProcessTimeout object to watch out for test hang if the
+ # process is still going after the timeout, then it will be killed
+ # forcing the communicate function (which is a blocking call) to
+ # return
+ if self.timeout > 0:
+ self.__proc_timeout = ProcessTimeout(self.timeout, proc)
+ self.__proc_timeout.start()
+
out, err = proc.communicate()
returncode = proc.returncode
except OSError as e:
diff --git a/framework/profile.py b/framework/profile.py
index b801147..f810096 100644
--- a/framework/profile.py
+++ b/framework/profile.py
@@ -71,6 +71,7 @@ class TestProfile(object):
self._dmesg = None
self.dmesg = False
self.results_dir = None
+ self.timeout = 0
@property
def dmesg(self):
@@ -202,7 +203,7 @@ class TestProfile(object):
"""
name, test = pair
- test.execute(name, log, self.dmesg)
+ test.execute(name, log, self.dmesg, self.timeout)
backend.write_test(name, test.result)
def run_threads(pool, testlist):
diff --git a/tests/igt.py b/tests/igt.py
index 22250ce..e7ee518 100644
--- a/tests/igt.py
+++ b/tests/igt.py
@@ -84,47 +84,6 @@ igtEnvironmentOk = checkEnvironment()
profile = TestProfile()
-# This class is for timing out tests that hang. Create an instance by passing
-# it a timeout in seconds and the Popen object that is running your test. Then
-# call the start method (inherited from Thread) to start the timer. The Popen
-# object is killed if the timeout is reached and it has not completed. Wait for
-# the outcome by calling the join() method from the parent.
-
-class ProcessTimeout(threading.Thread):
- def __init__(self, timeout, proc):
- threading.Thread.__init__(self)
- self.proc = proc
- self.timeout = timeout
- self.status = 0
-
- def run(self):
- start_time = datetime.now()
- delta = 0
- while (delta < self.timeout) and (self.proc.poll() is None):
- time.sleep(1)
- delta = (datetime.now() - start_time).total_seconds()
-
- # if the test is not finished after timeout, first try to terminate it
- # and if that fails, send SIGKILL to all processes in the test's
- # process group
-
- if self.proc.poll() is None:
- self.status = 1
- self.proc.terminate()
- time.sleep(5)
-
- if self.proc.poll() is None:
- self.status = 2
- os.killpg(self.proc.pid, signal.SIGKILL)
- self.proc.wait()
-
-
- def join(self):
- threading.Thread.join(self)
- return self.status
-
-
-
class IGTTest(Test):
def __init__(self, binary, arguments=None):
if arguments is None:
@@ -153,57 +112,6 @@ class IGTTest(Test):
super(IGTTest, self).run()
-
- def get_command_result(self):
- out = ""
- err = ""
- returncode = 0
- fullenv = os.environ.copy()
- for key, value in self.env.iteritems():
- fullenv[key] = str(value)
-
- try:
- proc = subprocess.Popen(self.command,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- env=fullenv,
- universal_newlines=True,
- preexec_fn=os.setpgrp)
-
- # create a ProcessTimeout object to watch out for test hang if the
- # process is still going after the timeout, then it will be killed
- # forcing the communicate function (which is a blocking call) to
- # return
- timeout = ProcessTimeout(600, proc)
- timeout.start()
- out, err = proc.communicate()
-
- # check for pass or skip first
- if proc.returncode in (0, 77):
- returncode = proc.returncode
- elif timeout.join() > 0:
- returncode = 78
- else:
- returncode = proc.returncode
-
- except OSError as e:
- # Different sets of tests get built under
- # different build configurations. If
- # a developer chooses to not build a test,
- # Piglit should not report that test as having
- # failed.
- if e.errno == errno.ENOENT:
- out = "PIGLIT: {'result': 'skip'}\n" \
- + "Test executable not found.\n"
- err = ""
- returncode = None
- else:
- raise e
- self.result['out'] = out.decode('utf-8', 'replace')
- self.result['err'] = err.decode('utf-8', 'replace')
- self.result['returncode'] = returncode
-
-
def listTests(listname):
with open(path.join(igtTestRoot, listname + '.txt'), 'r') as f:
lines = (line.rstrip() for line in f.readlines())
@@ -255,6 +163,7 @@ for test in tests:
addSubTestCases(test)
profile.dmesg = True
+profile.timeout = 600
# the dmesg property of TestProfile returns a Dmesg object
profile.dmesg.regex = re.compile(r"(\[drm:|drm_|intel_|i915_)")
--
1.9.3
More information about the Piglit
mailing list