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
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?
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.