My app logs unhandled exceptions.
# app.py
import logging
import sys
logger = logging.getLogger(__name__)
def excepthook(exc_type, exc_value, traceback):
exc_info = exc_type, exc_value, traceback
if not issubclass(exc_type, (KeyboardInterrupt, SystemExit)):
logger.error('Unhandled exception', exc_info=exc_info)
sys.__excepthook__(*exc_info)
sys.excepthook = excepthook
def potato():
logger.warning('about to die...')
errorerrorerror
if __name__ == '__main__':
potato()
These tests pass OK:
# test_app.py
import app
import pytest
import sys
from logging import WARNING, ERROR
def test_potato_raises():
with pytest.raises(NameError):
app.potato()
def test_excepthook_is_set():
assert sys.excepthook is app.excepthook
# for caplog plugin: pip install pytest-catchlog
def test_excepthook_logs(caplog):
try:
whatever
except NameError as err:
exc_info = type(err), err, err.__traceback__
app.excepthook(*exc_info)
assert caplog.record_tuples == [('app', ERROR, 'Unhandled exception')]
[record] = caplog.records
assert record.exc_info == exc_info
But I couldn't get a test of unhandled exceptions logging working:
def test_unhandled_exceptions_logged(caplog):
try:
app.potato()
finally:
assert caplog.record_tuples == [
('app', WARNING, 'about to die...'),
('app', ERROR, 'Unhandled exception'),
]
return # return eats exception
What's wrong here? How can we actually trigger the app.excepthook
from within a test?
Python won't call sys.excepthook
until an exception actually propagates all the way through the whole stack and no more code has an opportunity to catch it. It's one of the very last things that happen before Python shuts down in response to the exception.
As long as your test code is still on the stack, sys.excepthook
won't fire. What little code actually can run after sys.excepthook
probably isn't going to play well with your testing framework. For example, atexit
handlers can still run, but the test is over by then. Also, your test framework is probably going to catch the exception itself if you don't, so sys.excepthook
won't fire anyway.
If you don't want to call sys.excepthook
yourself, your best bet may be to launch an entire subprocess with your excepthook
installed and verify the subprocess's behavior.
from subprocess import Popen, PIPE
def test_app():
proc = Popen([sys.executable, 'app.py'], stdout=PIPE, stderr=PIPE)
stdout, stderr = proc.communicate()
assert proc.returncode == 1
assert stdout == b''
assert stderr.startswith(b'about to die...\nUnhandled exception')
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With