a8655eaccc
Ensure process terminate is only called after checking whether the process is still running so as not to accidentally set an exitcode. Additionally include improved message exchange and capturing to allow for easier debug should the expected exceptions not appear on socket timeouts. Change-Id: Ic51745ffa67570e9a3ca4574d2bfc54d0cd6724b
76 lines
2.4 KiB
Python
76 lines
2.4 KiB
Python
import functools
|
|
from multiprocessing import Process
|
|
from multiprocessing import Queue
|
|
import traceback
|
|
|
|
from six.moves import socketserver
|
|
|
|
|
|
class TestsTimeoutException(Exception):
|
|
pass
|
|
|
|
|
|
def time_limit(seconds, fp, func, *args, **kwargs):
|
|
|
|
if fp:
|
|
if not hasattr(fp, 'write'):
|
|
raise TypeError("Expected 'file-like' object, got '%s'" % fp)
|
|
else:
|
|
def record(msg):
|
|
fp.write(msg)
|
|
else:
|
|
def record(msg):
|
|
return
|
|
|
|
def capture_results(msg_queue, func, *args, **kwargs):
|
|
try:
|
|
result = func(*args, **kwargs)
|
|
except Exception as e:
|
|
msg_queue.put(
|
|
"Running function '%s' resulted in exception '%s' with "
|
|
"message: '%s'\n" % (func.__name__, e.__class__.__name__, e))
|
|
# no point re-raising an exception from the subprocess, instead
|
|
# return False
|
|
return False
|
|
else:
|
|
msg_queue.put(
|
|
"Running function '%s' finished with result '%s', and"
|
|
"stack:\n%s\n" % (func.__name__, result,
|
|
traceback.format_stack()))
|
|
return result
|
|
|
|
messages = Queue()
|
|
# although creating a separate process is expensive it's the only way to
|
|
# ensure cross platform that we can cleanly terminate after timeout
|
|
p = Process(target=functools.partial(capture_results, messages, func),
|
|
args=args, kwargs=kwargs)
|
|
p.start()
|
|
p.join(seconds)
|
|
if p.is_alive():
|
|
p.terminate()
|
|
while not messages.empty():
|
|
record(messages.get())
|
|
record("Running function '%s' did not finish\n" % func.__name__)
|
|
|
|
raise TestsTimeoutException
|
|
else:
|
|
while not messages.empty():
|
|
record(messages.get())
|
|
record("Running function '%s' finished with exit code '%s'\n"
|
|
% (func.__name__, p.exitcode))
|
|
|
|
|
|
class NullServer(socketserver.TCPServer):
|
|
|
|
request_queue_size = 1
|
|
|
|
def __init__(self, server_address, *args, **kwargs):
|
|
# TCPServer is old style in python 2.x so cannot use
|
|
# super() correctly, explicitly call __init__.
|
|
|
|
# simply init'ing is sufficient to open the port, which
|
|
# with the server not started creates a black hole server
|
|
socketserver.TCPServer.__init__(
|
|
self, server_address, socketserver.BaseRequestHandler,
|
|
*args, **kwargs)
|