Merge pull request #1526 from The-Compiler/tracebackhide
Filter selectively with __tracebackhide__
This commit is contained in:
commit
b220c96bf8
|
@ -23,6 +23,10 @@
|
||||||
Thanks `@omarkohl`_ for the complete PR (`#1502`_) and `@nicoddemus`_ for the
|
Thanks `@omarkohl`_ for the complete PR (`#1502`_) and `@nicoddemus`_ for the
|
||||||
implementation tips.
|
implementation tips.
|
||||||
|
|
||||||
|
* ``__tracebackhide__`` can now also be set to a callable which then can decide
|
||||||
|
whether to filter the traceback based on the ``ExceptionInfo`` object passed
|
||||||
|
to it.
|
||||||
|
|
||||||
*
|
*
|
||||||
|
|
||||||
**Changes**
|
**Changes**
|
||||||
|
|
|
@ -140,7 +140,8 @@ class TracebackEntry(object):
|
||||||
_repr_style = None
|
_repr_style = None
|
||||||
exprinfo = None
|
exprinfo = None
|
||||||
|
|
||||||
def __init__(self, rawentry):
|
def __init__(self, rawentry, excinfo=None):
|
||||||
|
self._excinfo = excinfo
|
||||||
self._rawentry = rawentry
|
self._rawentry = rawentry
|
||||||
self.lineno = rawentry.tb_lineno - 1
|
self.lineno = rawentry.tb_lineno - 1
|
||||||
|
|
||||||
|
@ -221,16 +222,24 @@ class TracebackEntry(object):
|
||||||
""" return True if the current frame has a var __tracebackhide__
|
""" return True if the current frame has a var __tracebackhide__
|
||||||
resolving to True
|
resolving to True
|
||||||
|
|
||||||
|
If __tracebackhide__ is a callable, it gets called with the
|
||||||
|
ExceptionInfo instance and can decide whether to hide the traceback.
|
||||||
|
|
||||||
mostly for internal use
|
mostly for internal use
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self.frame.f_locals['__tracebackhide__']
|
tbh = self.frame.f_locals['__tracebackhide__']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
try:
|
try:
|
||||||
return self.frame.f_globals['__tracebackhide__']
|
tbh = self.frame.f_globals['__tracebackhide__']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
if py.builtin.callable(tbh):
|
||||||
|
return tbh(self._excinfo)
|
||||||
|
else:
|
||||||
|
return tbh
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
try:
|
try:
|
||||||
fn = str(self.path)
|
fn = str(self.path)
|
||||||
|
@ -254,12 +263,13 @@ class Traceback(list):
|
||||||
access to Traceback entries.
|
access to Traceback entries.
|
||||||
"""
|
"""
|
||||||
Entry = TracebackEntry
|
Entry = TracebackEntry
|
||||||
def __init__(self, tb):
|
def __init__(self, tb, excinfo=None):
|
||||||
""" initialize from given python traceback object. """
|
""" initialize from given python traceback object and ExceptionInfo """
|
||||||
|
self._excinfo = excinfo
|
||||||
if hasattr(tb, 'tb_next'):
|
if hasattr(tb, 'tb_next'):
|
||||||
def f(cur):
|
def f(cur):
|
||||||
while cur is not None:
|
while cur is not None:
|
||||||
yield self.Entry(cur)
|
yield self.Entry(cur, excinfo=excinfo)
|
||||||
cur = cur.tb_next
|
cur = cur.tb_next
|
||||||
list.__init__(self, f(tb))
|
list.__init__(self, f(tb))
|
||||||
else:
|
else:
|
||||||
|
@ -283,7 +293,7 @@ class Traceback(list):
|
||||||
not codepath.relto(excludepath)) and
|
not codepath.relto(excludepath)) and
|
||||||
(lineno is None or x.lineno == lineno) and
|
(lineno is None or x.lineno == lineno) and
|
||||||
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
|
(firstlineno is None or x.frame.code.firstlineno == firstlineno)):
|
||||||
return Traceback(x._rawentry)
|
return Traceback(x._rawentry, self._excinfo)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
|
@ -302,7 +312,7 @@ class Traceback(list):
|
||||||
by default this removes all the TracebackItems which are hidden
|
by default this removes all the TracebackItems which are hidden
|
||||||
(see ishidden() above)
|
(see ishidden() above)
|
||||||
"""
|
"""
|
||||||
return Traceback(filter(fn, self))
|
return Traceback(filter(fn, self), self._excinfo)
|
||||||
|
|
||||||
def getcrashentry(self):
|
def getcrashentry(self):
|
||||||
""" return last non-hidden traceback entry that lead
|
""" return last non-hidden traceback entry that lead
|
||||||
|
@ -366,7 +376,7 @@ class ExceptionInfo(object):
|
||||||
#: the exception type name
|
#: the exception type name
|
||||||
self.typename = self.type.__name__
|
self.typename = self.type.__name__
|
||||||
#: the exception traceback (_pytest._code.Traceback instance)
|
#: the exception traceback (_pytest._code.Traceback instance)
|
||||||
self.traceback = _pytest._code.Traceback(self.tb)
|
self.traceback = _pytest._code.Traceback(self.tb, excinfo=self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||||
|
|
|
@ -216,6 +216,28 @@ Let's run our little function::
|
||||||
test_checkconfig.py:8: Failed
|
test_checkconfig.py:8: Failed
|
||||||
1 failed in 0.12 seconds
|
1 failed in 0.12 seconds
|
||||||
|
|
||||||
|
If you only want to hide certain exceptions, you can set ``__tracebackhide__``
|
||||||
|
to a callable which gets the ``ExceptionInfo`` object. You can for example use
|
||||||
|
this to make sure unexpected exception types aren't hidden::
|
||||||
|
|
||||||
|
import operator
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class ConfigException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def checkconfig(x):
|
||||||
|
__tracebackhide__ = operator.methodcaller('errisinstance', ConfigException)
|
||||||
|
if not hasattr(x, "config"):
|
||||||
|
raise ConfigException("not configured: %s" %(x,))
|
||||||
|
|
||||||
|
def test_something():
|
||||||
|
checkconfig(42)
|
||||||
|
|
||||||
|
This will avoid hiding the exception traceback on unrelated exceptions (i.e.
|
||||||
|
bugs in assertion helpers).
|
||||||
|
|
||||||
|
|
||||||
Detect if running from within a pytest run
|
Detect if running from within a pytest run
|
||||||
--------------------------------------------------------------
|
--------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
import operator
|
||||||
import _pytest
|
import _pytest
|
||||||
import py
|
import py
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -144,6 +145,39 @@ class TestTraceback_f_g_h:
|
||||||
ntraceback = traceback.filter()
|
ntraceback = traceback.filter()
|
||||||
assert len(ntraceback) == len(traceback) - 1
|
assert len(ntraceback) == len(traceback) - 1
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('tracebackhide, matching', [
|
||||||
|
(lambda info: True, True),
|
||||||
|
(lambda info: False, False),
|
||||||
|
(operator.methodcaller('errisinstance', ValueError), True),
|
||||||
|
(operator.methodcaller('errisinstance', IndexError), False),
|
||||||
|
])
|
||||||
|
def test_traceback_filter_selective(self, tracebackhide, matching):
|
||||||
|
def f():
|
||||||
|
#
|
||||||
|
raise ValueError
|
||||||
|
#
|
||||||
|
def g():
|
||||||
|
#
|
||||||
|
__tracebackhide__ = tracebackhide
|
||||||
|
f()
|
||||||
|
#
|
||||||
|
def h():
|
||||||
|
#
|
||||||
|
g()
|
||||||
|
#
|
||||||
|
|
||||||
|
excinfo = pytest.raises(ValueError, h)
|
||||||
|
traceback = excinfo.traceback
|
||||||
|
ntraceback = traceback.filter()
|
||||||
|
print('old: {0!r}'.format(traceback))
|
||||||
|
print('new: {0!r}'.format(ntraceback))
|
||||||
|
|
||||||
|
if matching:
|
||||||
|
assert len(ntraceback) == len(traceback) - 2
|
||||||
|
else:
|
||||||
|
# -1 because of the __tracebackhide__ in pytest.raises
|
||||||
|
assert len(ntraceback) == len(traceback) - 1
|
||||||
|
|
||||||
def test_traceback_recursion_index(self):
|
def test_traceback_recursion_index(self):
|
||||||
def f(n):
|
def f(n):
|
||||||
if n < 10:
|
if n < 10:
|
||||||
|
@ -442,7 +476,7 @@ raise ValueError()
|
||||||
f_globals = {}
|
f_globals = {}
|
||||||
|
|
||||||
class FakeTracebackEntry(_pytest._code.Traceback.Entry):
|
class FakeTracebackEntry(_pytest._code.Traceback.Entry):
|
||||||
def __init__(self, tb):
|
def __init__(self, tb, excinfo=None):
|
||||||
self.lineno = 5+3
|
self.lineno = 5+3
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
Loading…
Reference in New Issue