Skip to content

Add colors to summary #9875

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 43 additions & 23 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
from _pytest import timing
from _pytest._code import ExceptionInfo
from _pytest._code.code import ExceptionRepr
from _pytest._io import TerminalWriter
from _pytest._io.wcwidth import wcswidth
from _pytest.compat import final
from _pytest.config import _PluggyPlugin
Expand Down Expand Up @@ -1074,58 +1075,71 @@ def short_test_summary(self) -> None:
if not self.reportchars:
return

def show_simple(stat, lines: List[str]) -> None:
def show_simple(lines: List[str], *, stat: str) -> None:
failed = self.stats.get(stat, [])
if not failed:
return
termwidth = self._tw.fullwidth
config = self.config
for rep in failed:
line = _get_line_with_reprcrash_message(config, rep, termwidth)
color = _color_for_type.get(stat, _color_for_type_default)
line = _get_line_with_reprcrash_message(
config, rep, self._tw, {color: True}
)
lines.append(line)

def show_xfailed(lines: List[str]) -> None:
xfailed = self.stats.get("xfailed", [])
for rep in xfailed:
verbose_word = rep._get_verbose_word(self.config)
pos = _get_pos(self.config, rep)
lines.append(f"{verbose_word} {pos}")
markup_word = self._tw.markup(
verbose_word, **{_color_for_type["warnings"]: True}
)
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
line = f"{markup_word} {nodeid}"
reason = rep.wasxfail
if reason:
lines.append(" " + str(reason))
line += " - " + str(reason)

lines.append(line)

def show_xpassed(lines: List[str]) -> None:
xpassed = self.stats.get("xpassed", [])
for rep in xpassed:
verbose_word = rep._get_verbose_word(self.config)
pos = _get_pos(self.config, rep)
markup_word = self._tw.markup(
verbose_word, **{_color_for_type["warnings"]: True}
)
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
reason = rep.wasxfail
lines.append(f"{verbose_word} {pos} {reason}")
lines.append(f"{markup_word} {nodeid} {reason}")

def show_skipped(lines: List[str]) -> None:
skipped: List[CollectReport] = self.stats.get("skipped", [])
fskips = _folded_skips(self.startpath, skipped) if skipped else []
if not fskips:
return
verbose_word = skipped[0]._get_verbose_word(self.config)
markup_word = self._tw.markup(
verbose_word, **{_color_for_type["warnings"]: True}
)
prefix = "Skipped: "
for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "):
reason = reason[9:]
if reason.startswith(prefix):
reason = reason[len(prefix) :]
if lineno is not None:
lines.append(
"%s [%d] %s:%d: %s"
% (verbose_word, num, fspath, lineno, reason)
"%s [%d] %s:%d: %s" % (markup_word, num, fspath, lineno, reason)
)
else:
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason))

REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = {
"x": show_xfailed,
"X": show_xpassed,
"f": partial(show_simple, "failed"),
"f": partial(show_simple, stat="failed"),
"s": show_skipped,
"p": partial(show_simple, "passed"),
"E": partial(show_simple, "error"),
"p": partial(show_simple, stat="passed"),
"E": partial(show_simple, stat="error"),
}

lines: List[str] = []
Expand All @@ -1135,7 +1149,7 @@ def show_skipped(lines: List[str]) -> None:
action(lines)

if lines:
self.write_sep("=", "short test summary info")
self.write_sep("=", "short test summary info", cyan=True, bold=True)
for line in lines:
self.write_line(line)

Expand Down Expand Up @@ -1249,9 +1263,14 @@ def _build_collect_only_summary_stats_line(
return parts, main_color


def _get_pos(config: Config, rep: BaseReport):
def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport):
nodeid = config.cwd_relative_nodeid(rep.nodeid)
return nodeid
path, *parts = nodeid.split("::")
if parts:
parts_markup = tw.markup("::".join(parts), bold=True)
return path + "::" + parts_markup
else:
return path


def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]:
Expand Down Expand Up @@ -1280,13 +1299,14 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str


def _get_line_with_reprcrash_message(
config: Config, rep: BaseReport, termwidth: int
config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: Dict[str, bool]
) -> str:
"""Get summary line for a report, trying to add reprcrash message."""
verbose_word = rep._get_verbose_word(config)
pos = _get_pos(config, rep)
word = tw.markup(verbose_word, **word_markup)
node = _get_node_id_with_markup(tw, config, rep)

line = f"{verbose_word} {pos}"
line = f"{word} {node}"
line_width = wcswidth(line)

try:
Expand All @@ -1295,7 +1315,7 @@ def _get_line_with_reprcrash_message(
except AttributeError:
pass
else:
available_width = termwidth - line_width
available_width = tw.fullwidth - line_width
msg = _format_trimmed(" - {}", msg, available_width)
if msg is not None:
line += msg
Expand Down
23 changes: 9 additions & 14 deletions testing/test_skipping.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,10 +441,8 @@ def test_this_false():
result = pytester.runpytest(p, "-rx")
result.stdout.fnmatch_lines(
[
"*test_one*test_this*",
"*NOTRUN*noway",
"*test_one*test_this_true*",
"*NOTRUN*condition:*True*",
"*test_one*test_this - reason: *NOTRUN* noway",
"*test_one*test_this_true - reason: *NOTRUN* condition: True",
"*1 passed*",
]
)
Expand All @@ -461,9 +459,7 @@ def setup_module(mod):
"""
)
result = pytester.runpytest(p, "-rx")
result.stdout.fnmatch_lines(
["*test_one*test_this*", "*NOTRUN*hello", "*1 xfailed*"]
)
result.stdout.fnmatch_lines(["*test_one*test_this*NOTRUN*hello", "*1 xfailed*"])

def test_xfail_xpass(self, pytester: Pytester) -> None:
p = pytester.makepyfile(
Expand All @@ -489,7 +485,7 @@ def test_this():
result = pytester.runpytest(p)
result.stdout.fnmatch_lines(["*1 xfailed*"])
result = pytester.runpytest(p, "-rx")
result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"])
result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"])
result = pytester.runpytest(p, "--runxfail")
result.stdout.fnmatch_lines(["*1 pass*"])

Expand All @@ -507,7 +503,7 @@ def test_this():
result = pytester.runpytest(p)
result.stdout.fnmatch_lines(["*1 xfailed*"])
result = pytester.runpytest(p, "-rx")
result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"])
result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"])
result = pytester.runpytest(p, "--runxfail")
result.stdout.fnmatch_lines(
"""
Expand Down Expand Up @@ -543,7 +539,7 @@ def test_this(arg):
"""
)
result = pytester.runpytest(p, "-rxX")
result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*NOTRUN*"])
result.stdout.fnmatch_lines(["*XFAIL*test_this*NOTRUN*"])

def test_dynamic_xfail_set_during_funcarg_setup(self, pytester: Pytester) -> None:
p = pytester.makepyfile(
Expand Down Expand Up @@ -622,7 +618,7 @@ def test_foo():
"""
)
result = pytester.runpytest(p, "-rxX")
result.stdout.fnmatch_lines(["*XFAIL*", "*unsupported feature*"])
result.stdout.fnmatch_lines(["*XFAIL*unsupported feature*"])
assert result.ret == 0

@pytest.mark.parametrize("strict", [True, False])
Expand Down Expand Up @@ -1185,7 +1181,7 @@ def test_boolean():
"""
)
result = pytester.runpytest("-rsx")
result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"])
result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*x == 3*"])


def test_default_markers(pytester: Pytester) -> None:
Expand Down Expand Up @@ -1297,8 +1293,7 @@ def test_func():
result = pytester.runpytest("-rxs")
result.stdout.fnmatch_lines(
"""
*XFAIL*
*True123*
*XFAIL*True123*
*1 xfail*
"""
)
Expand Down
10 changes: 8 additions & 2 deletions testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2319,7 +2319,7 @@ def test_line_with_reprcrash(monkeypatch: MonkeyPatch) -> None:
def mock_get_pos(*args):
return mocked_pos

monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos)
monkeypatch.setattr(_pytest.terminal, "_get_node_id_with_markup", mock_get_pos)

class config:
pass
Expand All @@ -2333,10 +2333,16 @@ class reprcrash:
pass

def check(msg, width, expected):
class DummyTerminalWriter:
fullwidth = width

def markup(self, word: str, **markup: str):
return word

__tracebackhide__ = True
if msg:
rep.longrepr.reprcrash.message = msg # type: ignore
actual = _get_line_with_reprcrash_message(config, rep(), width) # type: ignore
actual = _get_line_with_reprcrash_message(config, rep(), DummyTerminalWriter(), {}) # type: ignore

assert actual == expected
if actual != f"{mocked_verbose_word} {mocked_pos}":
Expand Down