Merge pull request #5013 from blueyed/short-summary-message
Display message from reprcrash in short test summary
This commit is contained in:
commit
5eeb5ee960
|
@ -0,0 +1 @@
|
|||
Messages from crash reports are displayed within test summaries now, truncated to the terminal width.
|
|
@ -0,0 +1 @@
|
|||
pytest now depends on `wcwidth <https://pypi.org/project/wcwidth>`__ to properly track unicode character sizes for more precise terminal output.
|
1
setup.py
1
setup.py
|
@ -14,6 +14,7 @@ INSTALL_REQUIRES = [
|
|||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
"pluggy>=0.9",
|
||||
"wcwidth",
|
||||
]
|
||||
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# coding=utf8
|
||||
""" support for skip/xfail functions and markers. """
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# encoding: utf-8
|
||||
""" terminal reporting of the full testing process.
|
||||
|
||||
This is a good source for looking at the various reporting hooks.
|
||||
|
@ -887,10 +888,13 @@ class TerminalReporter(object):
|
|||
|
||||
def show_simple(stat, lines):
|
||||
failed = self.stats.get(stat, [])
|
||||
if not failed:
|
||||
return
|
||||
termwidth = self.writer.fullwidth
|
||||
config = self.config
|
||||
for rep in failed:
|
||||
verbose_word = rep._get_verbose_word(self.config)
|
||||
pos = _get_pos(self.config, rep)
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
line = _get_line_with_reprcrash_message(config, rep, termwidth)
|
||||
lines.append(line)
|
||||
|
||||
def show_xfailed(lines):
|
||||
xfailed = self.stats.get("xfailed", [])
|
||||
|
@ -927,10 +931,6 @@ class TerminalReporter(object):
|
|||
else:
|
||||
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
|
||||
|
||||
def _get_pos(config, rep):
|
||||
nodeid = config.cwd_relative_nodeid(rep.nodeid)
|
||||
return nodeid
|
||||
|
||||
REPORTCHAR_ACTIONS = {
|
||||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
|
@ -954,6 +954,56 @@ class TerminalReporter(object):
|
|||
self.write_line(line)
|
||||
|
||||
|
||||
def _get_pos(config, rep):
|
||||
nodeid = config.cwd_relative_nodeid(rep.nodeid)
|
||||
return nodeid
|
||||
|
||||
|
||||
def _get_line_with_reprcrash_message(config, rep, termwidth):
|
||||
"""Get summary line for a report, trying to add reprcrash message."""
|
||||
from wcwidth import wcswidth
|
||||
|
||||
verbose_word = rep._get_verbose_word(config)
|
||||
pos = _get_pos(config, rep)
|
||||
|
||||
line = "%s %s" % (verbose_word, pos)
|
||||
len_line = wcswidth(line)
|
||||
ellipsis, len_ellipsis = "...", 3
|
||||
if len_line > termwidth - len_ellipsis:
|
||||
# No space for an additional message.
|
||||
return line
|
||||
|
||||
try:
|
||||
msg = rep.longrepr.reprcrash.message
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
# Only use the first line.
|
||||
i = msg.find("\n")
|
||||
if i != -1:
|
||||
msg = msg[:i]
|
||||
len_msg = wcswidth(msg)
|
||||
|
||||
sep, len_sep = " - ", 3
|
||||
max_len_msg = termwidth - len_line - len_sep
|
||||
if max_len_msg >= len_ellipsis:
|
||||
if len_msg > max_len_msg:
|
||||
max_len_msg -= len_ellipsis
|
||||
msg = msg[:max_len_msg]
|
||||
while wcswidth(msg) > max_len_msg:
|
||||
msg = msg[:-1]
|
||||
if six.PY2:
|
||||
# on python 2 systems with narrow unicode compilation, trying to
|
||||
# get a single character out of a multi-byte unicode character such as
|
||||
# u'😄' will result in a High Surrogate (U+D83D) character, which is
|
||||
# rendered as u'<27>'; in this case we just strip that character out as it
|
||||
# serves no purpose being rendered
|
||||
msg = msg.rstrip(u"\uD83D")
|
||||
msg += ellipsis
|
||||
line += sep + msg
|
||||
return line
|
||||
|
||||
|
||||
def _folded_skips(skipped):
|
||||
d = {}
|
||||
for event in skipped:
|
||||
|
|
|
@ -876,7 +876,9 @@ class TestInvocationVariants(object):
|
|||
_fail, _sep, testid = line.partition(" ")
|
||||
break
|
||||
result = testdir.runpytest(testid, "-rf")
|
||||
result.stdout.fnmatch_lines([line, "*1 failed*"])
|
||||
result.stdout.fnmatch_lines(
|
||||
["FAILED test_doctest_id.txt::test_doctest_id.txt", "*1 failed*"]
|
||||
)
|
||||
|
||||
def test_core_backward_compatibility(self):
|
||||
"""Test backward compatibility for get_plugin_manager function. See #787."""
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# coding=utf8
|
||||
from __future__ import absolute_import
|
||||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
@ -1173,6 +1174,6 @@ def test_summary_list_after_errors(testdir):
|
|||
[
|
||||
"=* FAILURES *=",
|
||||
"*= short test summary info =*",
|
||||
"FAILED test_summary_list_after_errors.py::test_fail",
|
||||
"FAILED test_summary_list_after_errors.py::test_fail - assert 0",
|
||||
]
|
||||
)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# encoding: utf-8
|
||||
"""
|
||||
terminal reporting of the full testing process.
|
||||
"""
|
||||
|
@ -17,6 +18,7 @@ import pytest
|
|||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.reports import BaseReport
|
||||
from _pytest.terminal import _folded_skips
|
||||
from _pytest.terminal import _get_line_with_reprcrash_message
|
||||
from _pytest.terminal import _plugin_nameversions
|
||||
from _pytest.terminal import build_summary_stats_line
|
||||
from _pytest.terminal import getreportopt
|
||||
|
@ -758,12 +760,18 @@ class TestTerminalFunctional(object):
|
|||
result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"])
|
||||
|
||||
|
||||
def test_fail_extra_reporting(testdir):
|
||||
testdir.makepyfile("def test_this(): assert 0")
|
||||
def test_fail_extra_reporting(testdir, monkeypatch):
|
||||
monkeypatch.setenv("COLUMNS", "80")
|
||||
testdir.makepyfile("def test_this(): assert 0, 'this_failed' * 100")
|
||||
result = testdir.runpytest()
|
||||
assert "short test summary" not in result.stdout.str()
|
||||
result = testdir.runpytest("-rf")
|
||||
result.stdout.fnmatch_lines(["*test summary*", "FAIL*test_fail_extra_reporting*"])
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*test summary*",
|
||||
"FAILED test_fail_extra_reporting.py::test_this - AssertionError: this_failedt...",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_fail_reporting_on_pass(testdir):
|
||||
|
@ -1607,3 +1615,72 @@ def test_skip_reasons_folding():
|
|||
assert fspath == path
|
||||
assert lineno == lineno
|
||||
assert reason == message
|
||||
|
||||
|
||||
def test_line_with_reprcrash(monkeypatch):
|
||||
import _pytest.terminal
|
||||
from wcwidth import wcswidth
|
||||
|
||||
mocked_verbose_word = "FAILED"
|
||||
|
||||
mocked_pos = "some::nodeid"
|
||||
|
||||
def mock_get_pos(*args):
|
||||
return mocked_pos
|
||||
|
||||
monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos)
|
||||
|
||||
class config(object):
|
||||
pass
|
||||
|
||||
class rep(object):
|
||||
def _get_verbose_word(self, *args):
|
||||
return mocked_verbose_word
|
||||
|
||||
class longrepr:
|
||||
class reprcrash:
|
||||
pass
|
||||
|
||||
def check(msg, width, expected):
|
||||
__tracebackhide__ = True
|
||||
if msg:
|
||||
rep.longrepr.reprcrash.message = msg
|
||||
actual = _get_line_with_reprcrash_message(config, rep(), width)
|
||||
|
||||
assert actual == expected
|
||||
if actual != "%s %s" % (mocked_verbose_word, mocked_pos):
|
||||
assert len(actual) <= width
|
||||
assert wcswidth(actual) <= width
|
||||
|
||||
# AttributeError with message
|
||||
check(None, 80, "FAILED some::nodeid")
|
||||
|
||||
check("msg", 80, "FAILED some::nodeid - msg")
|
||||
check("msg", 3, "FAILED some::nodeid")
|
||||
|
||||
check("msg", 24, "FAILED some::nodeid")
|
||||
check("msg", 25, "FAILED some::nodeid - msg")
|
||||
|
||||
check("some longer msg", 24, "FAILED some::nodeid")
|
||||
check("some longer msg", 25, "FAILED some::nodeid - ...")
|
||||
check("some longer msg", 26, "FAILED some::nodeid - s...")
|
||||
|
||||
check("some\nmessage", 25, "FAILED some::nodeid - ...")
|
||||
check("some\nmessage", 26, "FAILED some::nodeid - some")
|
||||
check("some\nmessage", 80, "FAILED some::nodeid - some")
|
||||
|
||||
# Test unicode safety.
|
||||
check(u"😄😄😄😄😄\n2nd line", 25, u"FAILED some::nodeid - ...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 26, u"FAILED some::nodeid - ...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 27, u"FAILED some::nodeid - 😄...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 28, u"FAILED some::nodeid - 😄...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 29, u"FAILED some::nodeid - 😄😄...")
|
||||
|
||||
# NOTE: constructed, not sure if this is supported.
|
||||
# It would fail if not using u"" in Python 2 for mocked_pos.
|
||||
mocked_pos = u"nodeid::😄::withunicode"
|
||||
check(u"😄😄😄😄😄\n2nd line", 29, u"FAILED nodeid::😄::withunicode")
|
||||
check(u"😄😄😄😄😄\n2nd line", 40, u"FAILED nodeid::😄::withunicode - 😄😄...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 41, u"FAILED nodeid::😄::withunicode - 😄😄...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 42, u"FAILED nodeid::😄::withunicode - 😄😄😄...")
|
||||
check(u"😄😄😄😄😄\n2nd line", 80, u"FAILED nodeid::😄::withunicode - 😄😄😄😄😄")
|
||||
|
|
Loading…
Reference in New Issue