Pytest: __tracebackhide__ does not work with chained exceptions

Created on 2 Sep 2016  路  5Comments  路  Source: pytest-dev/pytest

On Python 3, with this code:

def bar():
    __tracebackhide__ = True
    raise ValueError

def assert_foo():
    __tracebackhide__ = True
    bar()

def test_foo():
    assert_foo()

I get a nice cleaned up traceback as I'd expect:

_____________________ test_foo ______________________

    def test_foo():
>       assert_foo()
E       ValueError

tbh.py:10: ValueError

However, if I re-raise the exception:

def bar():
    __tracebackhide__ = True
    raise ValueError

def assert_foo():
    __tracebackhide__ = True
    try:
        bar()
    except ValueError:
        raise TypeError

def test_foo():
    assert_foo()

The traceback gets quite verbose:

_____________________ test_foo ______________________

    def assert_foo():
        __tracebackhide__ = True
        try:
>           bar()

tbh.py:8: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

    def bar():
        __tracebackhide__ = True
>       raise ValueError
E       ValueError

tbh.py:3: ValueError

During handling of the above exception, another exception occurred:

    def test_foo():
>       assert_foo()
E       TypeError

tbh.py:13: TypeError

cc @roolebo

tracebacks bug

Most helpful comment

@wimglenn Yes - the whole idea of __tracebackhide__ is to suppress a verbose traceback, I don't see why that should change when exception chaining comes into play.

All 5 comments

Thanks for the report. I'll look at it.

I'm running into this too, including on AssertionError, which is very problematic for test reporting.

I.e., this will also show the full stack trace

def bar():
    __tracebackhide__ = True
    raise ValueError

def assert_foo():
    __tracebackhide__ = True
    try:
        bar()
    except ValueError:
        assert False

def test_foo():
    assert_foo()

I would consider this a feature not a bug. It's good that pytest gives you all the context by default. To opt-out of the extra-context:

def assert_foo():
    __tracebackhide__ = True
    try:
        bar()
    except ValueError:
        raise TypeError from None

cross-compat version of the same, if you need to support 2.7 still:

from future.utils import raise_from

def assert_foo():
    __tracebackhide__ = True
    try:
        bar()
    except ValueError:
        raise_from(TypeError, None)

Similarly, assert statements can just be replaced with

raise AssertionError from None    # assert False
raise AssertionError("your message") from None    # assert False, "your message"

So, what is the bug exactly? Are you suggesting to check if __tracebackhide__ is set in the frame, and always disable exception chaining in that case?

@wimglenn Yes - the whole idea of __tracebackhide__ is to suppress a verbose traceback, I don't see why that should change when exception chaining comes into play.

For reference, here is a failing test:

def test_tbh_chained(testdir):
    p = testdir.makepyfile(
        """
        import pytest

        def f1():
            __tracebackhide__ = True
            try:
                return f1.meh
            except AttributeError:
                pytest.fail("fail")

        @pytest.fixture
        def fix():
            f1()


        def test(fix):
            pass
        """
    )
    result = testdir.runpytest(str(p))
    assert "'function' object has no attribute 'meh'" not in result.stdout.str()
    assert result.ret == 0

Output:

test_tbh_chained.py E                                                    [100%]

==================================== ERRORS ====================================
____________________________ ERROR at setup of test ____________________________

    def f1():
        __tracebackhide__ = True
        try:
>           return f1.meh
E           AttributeError: 'function' object has no attribute 'meh'

test_tbh_chained.py:6: AttributeError

During handling of the above exception, another exception occurred:

    @pytest.fixture
    def fix():
>       f1()
E       Failed: fail

test_tbh_chained.py:12: Failed
=========================== 1 error in 0.05 seconds ============================
=========================== short test summary info ============================
FAILED testing/test_tbhide_chained.py::test_tbh_chained

I guess in this case the whole chain should be removed?

Was this page helpful?
0 / 5 - 0 ratings