diff --git a/CHANGELOG b/CHANGELOG index 6e9ad833e..fa078f553 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -62,6 +62,10 @@ - fix issue463: raise specific error for 'parameterize' misspelling (pfctdayelise). +- On failure, the ``sys.last_value``, ``sys.last_type`` and + ``sys.last_traceback`` are set, so that a user can inspect the error + via postmortem debugging (almarklein). + 2.6.4 ---------- diff --git a/_pytest/runner.py b/_pytest/runner.py index 2932f14c3..8accd3b0c 100644 --- a/_pytest/runner.py +++ b/_pytest/runner.py @@ -86,7 +86,17 @@ def pytest_runtest_setup(item): item.session._setupstate.prepare(item) def pytest_runtest_call(item): - item.runtest() + try: + item.runtest() + except Exception: + # Store trace info to allow postmortem debugging + type, value, tb = sys.exc_info() + tb = tb.tb_next # Skip *this* frame + sys.last_type = type + sys.last_value = value + sys.last_traceback = tb + del tb # Get rid of it in this namespace + raise def pytest_runtest_teardown(item, nextitem): item.session._setupstate.teardown_exact(item, nextitem) diff --git a/doc/en/usage.txt b/doc/en/usage.txt index b03b9b9d6..e774ebef6 100644 --- a/doc/en/usage.txt +++ b/doc/en/usage.txt @@ -87,6 +87,17 @@ failure situation:: py.test -x --pdb # drop to PDB on first failure, then end test session py.test --pdb --maxfail=3 # drop to PDB for first three failures +Note that on any failure the exception information is stored on +``sys.last_value``, ``sys.last_type`` and ``sys.last_traceback``. In +interactive use, this allows one to drop into postmortem debugging with +any debug tool. One can also manually access the exception information, +for example:: + + >> import sys + >> sys.last_traceback.tb_lineno + 42 + >> sys.last_value + AssertionError('assert result == "ok"',) Setting a breakpoint / aka ``set_trace()`` ---------------------------------------------------- diff --git a/testing/test_runner.py b/testing/test_runner.py index 9a13a6b47..e62aea9f7 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -525,3 +525,21 @@ def test_makereport_getsource(testdir): result = testdir.runpytest() assert 'INTERNALERROR' not in result.stdout.str() result.stdout.fnmatch_lines(['*else: assert False*']) + + +def test_store_except_info_on_eror(): + """ Test that upon test failure, the exception info is stored on + sys.last_traceback and friends. + """ + # Simulate item that raises a specific exception + class ItemThatRaises: + def runtest(self): + raise IndexError('TEST') + try: + runner.pytest_runtest_call(ItemThatRaises()) + except IndexError: + pass + # Check that exception info is stored on sys + assert sys.last_type is IndexError + assert sys.last_value.args[0] == 'TEST' + assert sys.last_traceback