The code in this notebook helps with handling errors. Normally, an error in notebook code causes the execution of the code to stop; while an infinite loop in notebook code causes the notebook to run without end. This notebook provides two classes to help address these concerns.
Prerequisites
with
statementTo use the code provided in this chapter, write
>>> from fuzzingbook.ExpectError import <identifier>
and then make use of the following features.
The ExpectError
class allows you to catch and report exceptions, yet resume execution. This is useful in notebooks, as they would normally interrupt execution as soon as an exception is raised. Its typical usage is in conjunction with a with
clause:
>>> with ExpectError():
>>> x = 1 / 0
Traceback (most recent call last):
File "<ipython-input-13-264328004f25>", line 2, in <module>
x = 1 / 0
ZeroDivisionError: division by zero (expected)
The ExpectTimeout
class allows you to interrupt execution after the specified time. This is useful for interrupting code that might otherwise run forever.
>>> with ExpectTimeout(5):
>>> long_running_test()
Start
0 seconds have passed
1 seconds have passed
2 seconds have passed
3 seconds have passed
Traceback (most recent call last):
File "<ipython-input-14-7e5136e65261>", line 2, in <module>
long_running_test()
File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test
print(i, "seconds have passed")
File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test
print(i, "seconds have passed")
File "<ipython-input-9-a28a583f0630>", line 16, in check_time
raise TimeoutError
TimeoutError (expected)
The exception and the associated traceback are printed as error messages. If you do not want that, use these keyword options:
print_traceback
(default True) can be set to False
to avoid the traceback being printedmute
(default False) can be set to True
to completely avoid any output.The class ExpectError
allows to express that some code produces an exception. A typical usage looks as follows:
from ExpectError import ExpectError
with ExpectError():
function_that_is_supposed_to_fail()
If an exception occurs, it is printed on standard error; yet, execution continues.
import fuzzingbook_utils
import traceback
import sys
class ExpectError(object):
def __init__(self, print_traceback=True, mute=False):
self.print_traceback = print_traceback
self.mute = mute
# Begin of `with` block
def __enter__(self):
return self
# End of `with` block
def __exit__(self, exc_type, exc_value, tb):
if exc_type is None:
# No exception
return
# An exception occurred
if self.print_traceback:
lines = ''.join(
traceback.format_exception(
exc_type,
exc_value,
tb)).strip()
else:
lines = traceback.format_exception_only(
exc_type, exc_value)[-1].strip()
if not self.mute:
print(lines, "(expected)", file=sys.stderr)
return True # Ignore it
Here's an example:
def fail_test():
# Trigger an exception
x = 1 / 0
with ExpectError():
fail_test()
Traceback (most recent call last): File "<ipython-input-5-67c629a2a842>", line 2, in <module> fail_test() File "<ipython-input-4-2e8a6dbc7b2c>", line 3, in fail_test x = 1 / 0 ZeroDivisionError: division by zero (expected)
with ExpectError(print_traceback=False):
fail_test()
ZeroDivisionError: division by zero (expected)
The class ExpectTimeout(seconds)
allows to express that some code may run for a long or infinite time; execution is thus interrupted after seconds
seconds. A typical usage looks as follows:
from ExpectError import ExpectTimeout
with ExpectTimeout(2) as t:
function_that_is_supposed_to_hang()
If an exception occurs, it is printed on standard error (as with ExpectError
); yet, execution continues.
Should there be a need to cancel the timeout within the with
block, t.cancel()
will do the trick.
The implementation uses sys.settrace()
, as this seems to be the most portable way to implement timeouts. It is not very efficient, though. Also, it only works on individual lines of Python code and will not interrupt a long-running system function.
import sys
import time
try:
# Should be defined in Python 3
x = TimeoutError
except:
# For Python 2
class TimeoutError(Exception):
def __init__(self, value="Timeout"):
self.value = value
def __str__(self):
return repr(self.value)
class ExpectTimeout(object):
def __init__(self, seconds, print_traceback=True, mute=False):
self.seconds_before_timeout = seconds
self.original_trace_function = None
self.end_time = None
self.print_traceback = print_traceback
self.mute = mute
# Tracing function
def check_time(self, frame, event, arg):
if self.original_trace_function is not None:
self.original_trace_function(frame, event, arg)
current_time = time.time()
if current_time >= self.end_time:
raise TimeoutError
return self.check_time
# Begin of `with` block
def __enter__(self):
start_time = time.time()
self.end_time = start_time + self.seconds_before_timeout
self.original_trace_function = sys.gettrace()
sys.settrace(self.check_time)
return self
# End of `with` block
def __exit__(self, exc_type, exc_value, tb):
self.cancel()
if exc_type is None:
return
# An exception occurred
if self.print_traceback:
lines = ''.join(
traceback.format_exception(
exc_type,
exc_value,
tb)).strip()
else:
lines = traceback.format_exception_only(
exc_type, exc_value)[-1].strip()
if not self.mute:
print(lines, "(expected)", file=sys.stderr)
return True # Ignore it
def cancel(self):
sys.settrace(self.original_trace_function)
Here's an example:
def long_running_test():
print("Start")
for i in range(10):
time.sleep(1)
print(i, "seconds have passed")
print("End")
with ExpectTimeout(5, print_traceback=False):
long_running_test()
Start 0 seconds have passed 1 seconds have passed 2 seconds have passed 3 seconds have passed
TimeoutError (expected)
Note that it is possible to nest multiple timeouts.
with ExpectTimeout(5):
with ExpectTimeout(3):
long_running_test()
long_running_test()
Start 0 seconds have passed 1 seconds have passed
Traceback (most recent call last): File "<ipython-input-12-d87d5d50b60e>", line 3, in <module> long_running_test() File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test print(i, "seconds have passed") File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test print(i, "seconds have passed") File "<ipython-input-9-a28a583f0630>", line 16, in check_time raise TimeoutError TimeoutError (expected)
Start 0 seconds have passed
Traceback (most recent call last): File "<ipython-input-12-d87d5d50b60e>", line 4, in <module> long_running_test() File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test print(i, "seconds have passed") File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test print(i, "seconds have passed") File "<ipython-input-9-a28a583f0630>", line 16, in check_time raise TimeoutError TimeoutError (expected)
That's it, folks – enjoy!
The ExpectError
class allows you to catch and report exceptions, yet resume execution. This is useful in notebooks, as they would normally interrupt execution as soon as an exception is raised. Its typical usage is in conjunction with a with
clause:
with ExpectError():
x = 1 / 0
Traceback (most recent call last): File "<ipython-input-13-264328004f25>", line 2, in <module> x = 1 / 0 ZeroDivisionError: division by zero (expected)
The ExpectTimeout
class allows you to interrupt execution after the specified time. This is useful for interrupting code that might otherwise run forever.
with ExpectTimeout(5):
long_running_test()
Start 0 seconds have passed 1 seconds have passed 2 seconds have passed 3 seconds have passed
Traceback (most recent call last): File "<ipython-input-14-7e5136e65261>", line 2, in <module> long_running_test() File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test print(i, "seconds have passed") File "<ipython-input-10-8d0f8e53f106>", line 5, in long_running_test print(i, "seconds have passed") File "<ipython-input-9-a28a583f0630>", line 16, in check_time raise TimeoutError TimeoutError (expected)
The exception and the associated traceback are printed as error messages. If you do not want that, use these keyword options:
print_traceback
(default True) can be set to False
to avoid the traceback being printedmute
(default False) can be set to True
to completely avoid any output.