From 2e61f702c09899bf375dba0c59eec3760fdd8990 Mon Sep 17 00:00:00 2001 From: Jordan Moldow Date: Sat, 29 Jul 2017 02:39:17 -0700 Subject: [PATCH] Support PEP-415's Exception.__suppress_context__ PEP-415 states that `exception.__context__` should be suppressed in traceback outputs, if `exception.__suppress_context__` is `True`. Now if a ``raise exception from None`` is caught by pytest, pytest will no longer chain the context in the test report. The algorithm in `FormattedExcinfo` now better matches the one in `traceback.TracebackException`. `Exception.__suppress_context__` is available in all of the versions of Python 3 that are supported by pytest. Fixes #2631. --- AUTHORS | 1 + _pytest/_code/code.py | 2 +- changelog/2631.feature | 4 ++++ testing/code/test_excinfo.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 changelog/2631.feature diff --git a/AUTHORS b/AUTHORS index fdcb47690..1e2d34b30 100644 --- a/AUTHORS +++ b/AUTHORS @@ -84,6 +84,7 @@ John Towler Jon Sonesen Jonas Obrist Jordan Guymon +Jordan Moldow Joshua Bronson Jurko Gospodnetić Justyna Janczyszyn diff --git a/_pytest/_code/code.py b/_pytest/_code/code.py index 5750211f2..0230c5660 100644 --- a/_pytest/_code/code.py +++ b/_pytest/_code/code.py @@ -679,7 +679,7 @@ class FormattedExcinfo(object): e = e.__cause__ excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None descr = 'The above exception was the direct cause of the following exception:' - elif e.__context__ is not None: + elif (e.__context__ is not None and not e.__suppress_context__): e = e.__context__ excinfo = ExceptionInfo((type(e), e, e.__traceback__)) if e.__traceback__ else None descr = 'During handling of the above exception, another exception occurred:' diff --git a/changelog/2631.feature b/changelog/2631.feature new file mode 100644 index 000000000..91b903b17 --- /dev/null +++ b/changelog/2631.feature @@ -0,0 +1,4 @@ +Added support for `PEP-415's `_ +``Exception.__suppress_context__``. Now if a ``raise exception from None`` is +caught by pytest, pytest will no longer chain the context in the test report. +The behavior now matches Python's traceback behavior. diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 37ceeb423..f8f8a0365 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1095,6 +1095,36 @@ raise ValueError() assert line.endswith('mod.py') assert tw.lines[47] == ":15: AttributeError" + @pytest.mark.skipif("sys.version_info[0] < 3") + def test_exc_repr_with_raise_from_none_chain_suppression(self, importasmod): + mod = importasmod(""" + def f(): + try: + g() + except Exception: + raise AttributeError() from None + def g(): + raise ValueError() + """) + excinfo = pytest.raises(AttributeError, mod.f) + r = excinfo.getrepr(style="long") + tw = TWMock() + r.toterminal(tw) + for line in tw.lines: + print(line) + assert tw.lines[0] == "" + assert tw.lines[1] == " def f():" + assert tw.lines[2] == " try:" + assert tw.lines[3] == " g()" + assert tw.lines[4] == " except Exception:" + assert tw.lines[5] == "> raise AttributeError() from None" + assert tw.lines[6] == "E AttributeError" + assert tw.lines[7] == "" + line = tw.get_write_msg(8) + assert line.endswith('mod.py') + assert tw.lines[9] == ":6: AttributeError" + assert len(tw.lines) == 10 + @pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.parametrize('reason, description', [ ('cause', 'The above exception was the direct cause of the following exception:'),