fix issue364: shorten and enhance tracebacks representation by default.

The new "--tb=auto" option (default) will only display long tracebacks
for the first and last entry.  You can get the old behaviour of printing
all entries as long entries with "--tb=long".  Also short entries by
default are now printed very similarly to "--tb=native" ones.
This commit is contained in:
holger krekel 2014-06-29 13:32:53 +02:00
parent 76d5c9e4f4
commit 07e76cbef2
9 changed files with 52 additions and 15 deletions

View File

@ -1,6 +1,12 @@
NEXT (2.6)
-----------------------------------
- fix issue364: shorten and enhance tracebacks representation by default.
The new "--tb=auto" option (default) will only display long tracebacks
for the first and last entry. You can get the old behaviour of printing
all entries as long entries with "--tb=long". Also short entries by
default are now printed very similarly to "--tb=native" ones.
- fix issue514: teach assertion reinterpretation about private class attributes
- change -v output to include full node IDs of tests. Users can copy

View File

@ -1,2 +1,2 @@
#
__version__ = '2.6.0.dev1'
__version__ = '2.6.0.dev2'

View File

@ -390,20 +390,24 @@ class Node(object):
fm = self.session._fixturemanager
if excinfo.errisinstance(fm.FixtureLookupError):
return excinfo.value.formatrepr()
tbfilter = True
if self.config.option.fulltrace:
style="long"
else:
self._prunetraceback(excinfo)
# XXX should excinfo.getrepr record all data and toterminal()
# process it?
tbfilter = False # prunetraceback already does it
if style == "auto":
style = "long"
# XXX should excinfo.getrepr record all data and toterminal() process it?
if style is None:
if self.config.option.tbstyle == "short":
style = "short"
else:
style = "long"
return excinfo.getrepr(funcargs=True,
showlocals=self.config.option.showlocals,
style=style)
style=style, tbfilter=tbfilter)
repr_failure = _repr_failure_py

View File

@ -578,6 +578,12 @@ class FunctionMixin(PyobjMixin):
if ntraceback == traceback:
ntraceback = ntraceback.cut(excludepath=cutdir)
excinfo.traceback = ntraceback.filter()
# issue364: mark all but first and last frames to
# only show a single-line message for each frame
if self.config.option.tbstyle == "auto":
if len(excinfo.traceback) > 2:
for entry in excinfo.traceback[1:-1]:
entry.set_repr_style('short')
def _repr_failure_py(self, excinfo, style="long"):
if excinfo.errisinstance(pytest.fail.Exception):
@ -588,8 +594,10 @@ class FunctionMixin(PyobjMixin):
def repr_failure(self, excinfo, outerr=None):
assert outerr is None, "XXX outerr usage is deprecated"
return self._repr_failure_py(excinfo,
style=self.config.option.tbstyle)
style = self.config.option.tbstyle
if style == "auto":
style = "long"
return self._repr_failure_py(excinfo, style=style)
class Generator(FunctionMixin, PyCollector):

View File

@ -23,8 +23,8 @@ def pytest_addoption(parser):
action="store", dest="report", default=None, metavar="opts",
help="(deprecated, use -r)")
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
choices=['long', 'short', 'no', 'line', 'native'],
action="store", dest="tbstyle", default='auto',
choices=['auto', 'long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/native/no).")
group._addoption('--fulltrace', '--full-trace',
action="store_true", default=False,

View File

@ -17,7 +17,7 @@ long_description = open('README.rst').read()
def main():
install_requires = ['py>=1.4.20']
install_requires = ['py>=1.4.21.dev2']
if sys.version_info < (2, 7) or (3,) <= sys.version_info < (3, 2):
install_requires.append('argparse')
if sys.platform == 'win32':
@ -27,7 +27,7 @@ def main():
name='pytest',
description='pytest: simple powerful testing with Python',
long_description=long_description,
version='2.6.0.dev1',
version='2.6.0.dev2',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],

View File

@ -641,7 +641,7 @@ class TestTracebackCutting:
assert "x = 1" not in out
assert "x = 2" not in out
result.stdout.fnmatch_lines([
">*asd*",
" *asd*",
"E*NameError*",
])
result = testdir.runpytest("--fulltrace")

View File

@ -369,7 +369,7 @@ def test_traceback_failure(testdir):
def test_onefails():
f(3)
""")
result = testdir.runpytest(p1)
result = testdir.runpytest(p1, "--tb=long")
result.stdout.fnmatch_lines([
"*test_traceback_failure.py F",
"====* FAILURES *====",
@ -389,6 +389,25 @@ def test_traceback_failure(testdir):
"*test_traceback_failure.py:4: AssertionError"
])
result = testdir.runpytest(p1) # "auto"
result.stdout.fnmatch_lines([
"*test_traceback_failure.py F",
"====* FAILURES *====",
"____*____",
"",
" def test_onefails():",
"> f(3)",
"",
"*test_*.py:6: ",
"",
" def f(x):",
"> assert x == g()",
"E assert 3 == 2",
"E + where 2 = g()",
"",
"*test_traceback_failure.py:4: AssertionError"
])
@pytest.mark.skipif("sys.version_info < (2,5) or '__pypy__' in sys.builtin_module_names or sys.platform.startswith('java')" )
def test_warn_missing(testdir):
testdir.makepyfile("")

View File

@ -559,8 +559,8 @@ def test_tbstyle_short(testdir):
assert 'x = 0' not in s
result.stdout.fnmatch_lines([
"*%s:5*" % p.basename,
">*assert x",
"E*assert*",
" assert x",
"E assert*",
])
result = testdir.runpytest()
s = result.stdout.str()
@ -583,7 +583,7 @@ class TestGenericReporting:
testdir.makepyfile("import xyz\n")
result = testdir.runpytest(*option.args)
result.stdout.fnmatch_lines([
"> import xyz",
"? import xyz",
"E ImportError: No module named *xyz*",
"*1 error*",
])