I have this pytest-powered test function that seems functional :
def test_settrace_timeout():
TIMEOUT_DURATION = 3
@settrace_timeout(TIMEOUT_DURATION)
def function_to_timeout(n):
sleep(n)
with pytest.raises(TimeoutError):
function_to_timeout(TIMEOUT_DURATION + 1)
function_to_timeout(TIMEOUT_DURATION - 1)
It's meant to test whether the parametrized settrace_timeout
decorator makes decorated functions raise a TimeoutError
when it takes too long and doesn't make it raise it when it return soon enough.
In that test, I'm decorating a function that uses time.sleep
.
It looks functional as a test. The tested decorator works and fails as expected and "sabotaging" it makes the test fail.
So I also tried this out of curiosity (and the test still works) :
As far as I know it should be nearly equivalent.
def test_settrace_timeout():
TIMEOUT_DURATION = 3
function_to_timeout = settrace_timeout(TIMEOUT_DURATION)(lambda n: sleep(n))
with pytest.raises(TimeoutError):
function_to_timeout(TIMEOUT_DURATION + 1)
function_to_timeout(TIMEOUT_DURATION - 1)
And finally, I have this that should work (that's what I was expecting at least). It doesn't work though.
When I try to remove the lambda proxy function, and when I try to directly decorate the sleep
function, it suddenly fails (doesn't raise the function when expected to).
def test_settrace_timeout():
TIMEOUT_DURATION = 3
function_to_timeout = settrace_timeout(TIMEOUT_DURATION)(sleep) # Syntax it suddenly fails with (no more raised exception).
with pytest.raises(TimeoutError):
function_to_timeout(TIMEOUT_DURATION + 1)
function_to_timeout(TIMEOUT_DURATION - 1)
What is the reason why I have this different behaviour in this specific case? I'd like to understand the difference between the test functions.
Here is the decorator I'm trying to test :
def settrace_timeout(timeout_in_seconds):
"""
sys.settrace() based timeout.
Will raise TimeoutError.
It's meant to be used with requests based methods.
It's based on this SO answer :
https://stackoverflow.com/a/71453648/3156085
"""
def decorator(f):
exception_class = TimeoutError
f_name = f.__name__
def function(*args, **kwargs):
start = time.time()
def trace_function(frame, event, arg):
if time.time() - start > timeout_in_seconds:
log("[settrace_timeout] - Timeout occured in function {0}.".format(f_name))
raise exception_class("Timeout")
else:
return trace_function
try:
sys.settrace(trace_function)
log("[settrace_timeout] settrace enabled in function {0}.".format(f_name))
return_value = f(*args, **kwargs)
except exception_class as e:
log("[settrace_timeout] exception {0} raised in function {1}.".format(e, f_name))
raise
finally:
sys.settrace(None)
log("[settrace_timeout] settrace disabled in function {0}.".format(f_name))
return return_value
return function
return decorator