diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index b4848c48aba..8336a0401ec 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -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 @@ -1074,33 +1075,43 @@ 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", []) @@ -1108,24 +1119,27 @@ def show_skipped(lines: List[str]) -> None: 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] = [] @@ -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) @@ -1249,7 +1263,7 @@ 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 @@ -1280,13 +1294,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: @@ -1295,7 +1310,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 diff --git a/testing/test_skipping.py b/testing/test_skipping.py index 3010943607c..3b5c91ced8d 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -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*", ] ) @@ -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( @@ -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*"]) @@ -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( """ @@ -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( @@ -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]) @@ -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: diff --git a/testing/test_terminal.py b/testing/test_terminal.py index f0e58e5b4c4..5b464d68bfb 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -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 @@ -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}":