Skip to content

Commit bcaff45

Browse files
committed
Implement showing summary words colored
Fix #9873
1 parent 28e8c85 commit bcaff45

File tree

3 files changed

+53
-36
lines changed

3 files changed

+53
-36
lines changed

src/_pytest/terminal.py

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
from _pytest import timing
3636
from _pytest._code import ExceptionInfo
3737
from _pytest._code.code import ExceptionRepr
38+
from _pytest._io import TerminalWriter
3839
from _pytest._io.wcwidth import wcswidth
3940
from _pytest.compat import final
4041
from _pytest.config import _PluggyPlugin
@@ -1074,58 +1075,71 @@ def short_test_summary(self) -> None:
10741075
if not self.reportchars:
10751076
return
10761077

1077-
def show_simple(stat, lines: List[str]) -> None:
1078+
def show_simple(lines: List[str], *, stat: str) -> None:
10781079
failed = self.stats.get(stat, [])
10791080
if not failed:
10801081
return
1081-
termwidth = self._tw.fullwidth
10821082
config = self.config
10831083
for rep in failed:
1084-
line = _get_line_with_reprcrash_message(config, rep, termwidth)
1084+
color = _color_for_type.get(stat, _color_for_type_default)
1085+
line = _get_line_with_reprcrash_message(
1086+
config, rep, self._tw, {color: True}
1087+
)
10851088
lines.append(line)
10861089

10871090
def show_xfailed(lines: List[str]) -> None:
10881091
xfailed = self.stats.get("xfailed", [])
10891092
for rep in xfailed:
10901093
verbose_word = rep._get_verbose_word(self.config)
1091-
pos = _get_pos(self.config, rep)
1092-
lines.append(f"{verbose_word} {pos}")
1094+
markup_word = self._tw.markup(
1095+
verbose_word, **{_color_for_type["warnings"]: True}
1096+
)
1097+
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
1098+
line = f"{markup_word} {nodeid}"
10931099
reason = rep.wasxfail
10941100
if reason:
1095-
lines.append(" " + str(reason))
1101+
line += " - " + str(reason)
1102+
1103+
lines.append(line)
10961104

10971105
def show_xpassed(lines: List[str]) -> None:
10981106
xpassed = self.stats.get("xpassed", [])
10991107
for rep in xpassed:
11001108
verbose_word = rep._get_verbose_word(self.config)
1101-
pos = _get_pos(self.config, rep)
1109+
markup_word = self._tw.markup(
1110+
verbose_word, **{_color_for_type["warnings"]: True}
1111+
)
1112+
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
11021113
reason = rep.wasxfail
1103-
lines.append(f"{verbose_word} {pos} {reason}")
1114+
lines.append(f"{markup_word} {nodeid} {reason}")
11041115

11051116
def show_skipped(lines: List[str]) -> None:
11061117
skipped: List[CollectReport] = self.stats.get("skipped", [])
11071118
fskips = _folded_skips(self.startpath, skipped) if skipped else []
11081119
if not fskips:
11091120
return
11101121
verbose_word = skipped[0]._get_verbose_word(self.config)
1122+
markup_word = self._tw.markup(
1123+
verbose_word, **{_color_for_type["warnings"]: True}
1124+
)
1125+
prefix = "Skipped: "
11111126
for num, fspath, lineno, reason in fskips:
1112-
if reason.startswith("Skipped: "):
1113-
reason = reason[9:]
1127+
if reason.startswith(prefix):
1128+
reason = reason[len(prefix) :]
11141129
if lineno is not None:
11151130
lines.append(
1116-
"%s [%d] %s:%d: %s"
1117-
% (verbose_word, num, fspath, lineno, reason)
1131+
"%s [%d] %s:%d: %s" % (markup_word, num, fspath, lineno, reason)
11181132
)
11191133
else:
1120-
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
1134+
lines.append("%s [%d] %s: %s" % (markup_word, num, fspath, reason))
11211135

11221136
REPORTCHAR_ACTIONS: Mapping[str, Callable[[List[str]], None]] = {
11231137
"x": show_xfailed,
11241138
"X": show_xpassed,
1125-
"f": partial(show_simple, "failed"),
1139+
"f": partial(show_simple, stat="failed"),
11261140
"s": show_skipped,
1127-
"p": partial(show_simple, "passed"),
1128-
"E": partial(show_simple, "error"),
1141+
"p": partial(show_simple, stat="passed"),
1142+
"E": partial(show_simple, stat="error"),
11291143
}
11301144

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

11371151
if lines:
1138-
self.write_sep("=", "short test summary info")
1152+
self.write_sep("=", "short test summary info", cyan=True, bold=True)
11391153
for line in lines:
11401154
self.write_line(line)
11411155

@@ -1249,7 +1263,7 @@ def _build_collect_only_summary_stats_line(
12491263
return parts, main_color
12501264

12511265

1252-
def _get_pos(config: Config, rep: BaseReport):
1266+
def _get_node_id_with_markup(tw: TerminalWriter, config: Config, rep: BaseReport):
12531267
nodeid = config.cwd_relative_nodeid(rep.nodeid)
12541268
return nodeid
12551269

@@ -1280,13 +1294,14 @@ def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str
12801294

12811295

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

1289-
line = f"{verbose_word} {pos}"
1304+
line = f"{word} {node}"
12901305
line_width = wcswidth(line)
12911306

12921307
try:
@@ -1295,7 +1310,7 @@ def _get_line_with_reprcrash_message(
12951310
except AttributeError:
12961311
pass
12971312
else:
1298-
available_width = termwidth - line_width
1313+
available_width = tw.fullwidth - line_width
12991314
msg = _format_trimmed(" - {}", msg, available_width)
13001315
if msg is not None:
13011316
line += msg

testing/test_skipping.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -441,10 +441,8 @@ def test_this_false():
441441
result = pytester.runpytest(p, "-rx")
442442
result.stdout.fnmatch_lines(
443443
[
444-
"*test_one*test_this*",
445-
"*NOTRUN*noway",
446-
"*test_one*test_this_true*",
447-
"*NOTRUN*condition:*True*",
444+
"*test_one*test_this - reason: *NOTRUN* noway",
445+
"*test_one*test_this_true - reason: *NOTRUN* condition: True",
448446
"*1 passed*",
449447
]
450448
)
@@ -461,9 +459,7 @@ def setup_module(mod):
461459
"""
462460
)
463461
result = pytester.runpytest(p, "-rx")
464-
result.stdout.fnmatch_lines(
465-
["*test_one*test_this*", "*NOTRUN*hello", "*1 xfailed*"]
466-
)
462+
result.stdout.fnmatch_lines(["*test_one*test_this*NOTRUN*hello", "*1 xfailed*"])
467463

468464
def test_xfail_xpass(self, pytester: Pytester) -> None:
469465
p = pytester.makepyfile(
@@ -489,7 +485,7 @@ def test_this():
489485
result = pytester.runpytest(p)
490486
result.stdout.fnmatch_lines(["*1 xfailed*"])
491487
result = pytester.runpytest(p, "-rx")
492-
result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"])
488+
result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"])
493489
result = pytester.runpytest(p, "--runxfail")
494490
result.stdout.fnmatch_lines(["*1 pass*"])
495491

@@ -507,7 +503,7 @@ def test_this():
507503
result = pytester.runpytest(p)
508504
result.stdout.fnmatch_lines(["*1 xfailed*"])
509505
result = pytester.runpytest(p, "-rx")
510-
result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*reason:*hello*"])
506+
result.stdout.fnmatch_lines(["*XFAIL*test_this*reason:*hello*"])
511507
result = pytester.runpytest(p, "--runxfail")
512508
result.stdout.fnmatch_lines(
513509
"""
@@ -543,7 +539,7 @@ def test_this(arg):
543539
"""
544540
)
545541
result = pytester.runpytest(p, "-rxX")
546-
result.stdout.fnmatch_lines(["*XFAIL*test_this*", "*NOTRUN*"])
542+
result.stdout.fnmatch_lines(["*XFAIL*test_this*NOTRUN*"])
547543

548544
def test_dynamic_xfail_set_during_funcarg_setup(self, pytester: Pytester) -> None:
549545
p = pytester.makepyfile(
@@ -622,7 +618,7 @@ def test_foo():
622618
"""
623619
)
624620
result = pytester.runpytest(p, "-rxX")
625-
result.stdout.fnmatch_lines(["*XFAIL*", "*unsupported feature*"])
621+
result.stdout.fnmatch_lines(["*XFAIL*unsupported feature*"])
626622
assert result.ret == 0
627623

628624
@pytest.mark.parametrize("strict", [True, False])
@@ -1185,7 +1181,7 @@ def test_boolean():
11851181
"""
11861182
)
11871183
result = pytester.runpytest("-rsx")
1188-
result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"])
1184+
result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*x == 3*"])
11891185

11901186

11911187
def test_default_markers(pytester: Pytester) -> None:

testing/test_terminal.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2319,7 +2319,7 @@ def test_line_with_reprcrash(monkeypatch: MonkeyPatch) -> None:
23192319
def mock_get_pos(*args):
23202320
return mocked_pos
23212321

2322-
monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos)
2322+
monkeypatch.setattr(_pytest.terminal, "_get_node_id_with_markup", mock_get_pos)
23232323

23242324
class config:
23252325
pass
@@ -2333,10 +2333,16 @@ class reprcrash:
23332333
pass
23342334

23352335
def check(msg, width, expected):
2336+
class DummyTerminalWriter:
2337+
fullwidth = width
2338+
2339+
def markup(self, word: str, **markup: str):
2340+
return word
2341+
23362342
__tracebackhide__ = True
23372343
if msg:
23382344
rep.longrepr.reprcrash.message = msg # type: ignore
2339-
actual = _get_line_with_reprcrash_message(config, rep(), width) # type: ignore
2345+
actual = _get_line_with_reprcrash_message(config, rep(), DummyTerminalWriter(), {}) # type: ignore
23402346

23412347
assert actual == expected
23422348
if actual != f"{mocked_verbose_word} {mocked_pos}":

0 commit comments

Comments
 (0)