Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test that a custom excepthook is installed correctly?

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?

like image 769
wim Avatar asked Sep 19 '17 21:09

wim


1 Answers

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')
like image 118
user2357112 supports Monica Avatar answered Sep 28 '22 22:09

user2357112 supports Monica