From 36dc6718435c423f768df8334e93d7f06d752784 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Fri, 12 Oct 2018 09:37:54 -0300 Subject: [PATCH] New ExceptionInfo.getrepr 'chain' parameter to be able to suppress chained exceptions --- src/_pytest/_code/code.py | 42 ++++++++++++++++++++++++++++++------ testing/code/test_excinfo.py | 20 ++++++++++++----- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 2662e4320..b2321c02d 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -451,13 +451,35 @@ class ExceptionInfo(object): tbfilter=True, funcargs=False, truncate_locals=True, + chain=True, ): - """ return str()able representation of this exception info. - showlocals: show locals per traceback entry - style: long|short|no|native traceback style - tbfilter: hide entries (where __tracebackhide__ is true) + """ + Return str()able representation of this exception info. - in case of style==native, tbfilter and showlocals is ignored. + :param bool showlocals: + Show locals per traceback entry. + Ignored if ``style=="native"``. + + :param str style: long|short|no|native traceback style + + :param bool abspath: + If paths should be changed to absolute or left unchanged. + + :param bool tbfilter: + Hide entries that contain a local variable ``__tracebackhide__==True``. + Ignored if ``style=="native"``. + + :param bool funcargs: + Show fixtures ("funcargs" for legacy purposes) per traceback entry. + + :param bool truncate_locals: + With ``showlocals==True``, make sure locals can be safely represented as strings. + + :param bool chain: if chained exceptions in Python 3 should be shown. + + .. versionchanged:: 3.9 + + Added the ``chain`` parameter. """ if style == "native": return ReprExceptionInfo( @@ -476,6 +498,7 @@ class ExceptionInfo(object): tbfilter=tbfilter, funcargs=funcargs, truncate_locals=truncate_locals, + chain=chain, ) return fmt.repr_excinfo(self) @@ -516,6 +539,7 @@ class FormattedExcinfo(object): tbfilter = attr.ib(default=True) funcargs = attr.ib(default=False) truncate_locals = attr.ib(default=True) + chain = attr.ib(default=True) astcache = attr.ib(default=attr.Factory(dict), init=False, repr=False) def _getindent(self, source): @@ -735,7 +759,7 @@ class FormattedExcinfo(object): reprcrash = None repr_chain += [(reprtraceback, reprcrash, descr)] - if e.__cause__ is not None: + if e.__cause__ is not None and self.chain: e = e.__cause__ excinfo = ( ExceptionInfo((type(e), e, e.__traceback__)) @@ -743,7 +767,11 @@ class FormattedExcinfo(object): else None ) descr = "The above exception was the direct cause of the following exception:" - elif e.__context__ is not None and not e.__suppress_context__: + elif ( + e.__context__ is not None + and not e.__suppress_context__ + and self.chain + ): e = e.__context__ excinfo = ( ExceptionInfo((type(e), e, e.__traceback__)) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 72b1a78ab..92460cd29 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -1184,20 +1184,28 @@ raise ValueError() 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): + @pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"]) + def test_exc_repr_chain_suppression(self, importasmod, mode): + """Check that exc repr does not show chained exceptions in Python 3. + - When the exception is raised with "from None" + - Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr(). + """ + raise_suffix = " from None" if mode == "from_none" else "" mod = importasmod( """ def f(): try: g() except Exception: - raise AttributeError() from None + raise AttributeError(){raise_suffix} def g(): raise ValueError() - """ + """.format( + raise_suffix=raise_suffix + ) ) excinfo = pytest.raises(AttributeError, mod.f) - r = excinfo.getrepr(style="long") + r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress") tw = TWMock() r.toterminal(tw) for line in tw.lines: @@ -1207,7 +1215,9 @@ raise ValueError() 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[5] == "> raise AttributeError(){}".format( + raise_suffix + ) assert tw.lines[6] == "E AttributeError" assert tw.lines[7] == "" line = tw.get_write_msg(8)