Skip to content

Commit

Permalink
Merge pull request #12472 from pbrezina/testresult-markup
Browse files Browse the repository at this point in the history
  • Loading branch information
webknjaz authored Jul 1, 2024
2 parents 1a8394e + f502f1d commit 90459a8
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 18 deletions.
1 change: 1 addition & 0 deletions changelog/12472.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed a crash when returning category ``"error"`` or ``"failed"`` with a custom test status from :hook:`pytest_report_teststatus` hook -- :user:`pbrezina`.
21 changes: 19 additions & 2 deletions src/_pytest/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import Literal
from typing import Mapping
from typing import NoReturn
from typing import Sequence
from typing import TYPE_CHECKING

from _pytest._code.code import ExceptionChainRepr
Expand All @@ -30,6 +31,7 @@
from _pytest.config import Config
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.outcomes import fail
from _pytest.outcomes import skip


Expand Down Expand Up @@ -190,11 +192,26 @@ def head_line(self) -> str | None:
return domain
return None

def _get_verbose_word(self, config: Config):
def _get_verbose_word_with_markup(
self, config: Config, default_markup: Mapping[str, bool]
) -> tuple[str, Mapping[str, bool]]:
_category, _short, verbose = config.hook.pytest_report_teststatus(
report=self, config=config
)
return verbose

if isinstance(verbose, str):
return verbose, default_markup

if isinstance(verbose, Sequence) and len(verbose) == 2:
word, markup = verbose
if isinstance(word, str) and isinstance(markup, Mapping):
return word, markup

fail( # pragma: no cover
"pytest_report_teststatus() hook (from a plugin) returned "
f"an invalid verbose value: {verbose!r}.\nExpected either a string "
"or a tuple of (word, markup)."
)

def _to_json(self) -> dict[str, Any]:
"""Return the contents of this report as a dict of builtin entries,
Expand Down
24 changes: 13 additions & 11 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1206,10 +1206,10 @@ def show_simple(lines: list[str], *, stat: str) -> None:
def show_xfailed(lines: list[str]) -> None:
xfailed = self.stats.get("xfailed", [])
for rep in xfailed:
verbose_word = rep._get_verbose_word(self.config)
markup_word = self._tw.markup(
verbose_word, **{_color_for_type["warnings"]: True}
verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
self.config, {_color_for_type["warnings"]: True}
)
markup_word = self._tw.markup(verbose_word, **verbose_markup)
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
line = f"{markup_word} {nodeid}"
reason = rep.wasxfail
Expand All @@ -1221,10 +1221,10 @@ def show_xfailed(lines: list[str]) -> None:
def show_xpassed(lines: list[str]) -> None:
xpassed = self.stats.get("xpassed", [])
for rep in xpassed:
verbose_word = rep._get_verbose_word(self.config)
markup_word = self._tw.markup(
verbose_word, **{_color_for_type["warnings"]: True}
verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
self.config, {_color_for_type["warnings"]: True}
)
markup_word = self._tw.markup(verbose_word, **verbose_markup)
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
line = f"{markup_word} {nodeid}"
reason = rep.wasxfail
Expand All @@ -1237,10 +1237,10 @@ def show_skipped(lines: list[str]) -> None:
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}
verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup(
self.config, {_color_for_type["warnings"]: True}
)
markup_word = self._tw.markup(verbose_word, **verbose_markup)
prefix = "Skipped: "
for num, fspath, lineno, reason in fskips:
if reason.startswith(prefix):
Expand Down Expand Up @@ -1421,8 +1421,10 @@ def _get_line_with_reprcrash_message(
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)
word = tw.markup(verbose_word, **word_markup)
verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
config, word_markup
)
word = tw.markup(verbose_word, **verbose_markup)
node = _get_node_id_with_markup(tw, config, rep)

line = f"{word} {node}"
Expand Down
13 changes: 8 additions & 5 deletions testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -326,16 +326,17 @@ def test_rewrite(self, pytester: Pytester, monkeypatch) -> None:
tr.rewrite("hey", erase=True)
assert f.getvalue() == "hello" + "\r" + "hey" + (6 * " ")

@pytest.mark.parametrize("category", ["foo", "failed", "error", "passed"])
def test_report_teststatus_explicit_markup(
self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping
self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping, category: str
) -> None:
"""Test that TerminalReporter handles markup explicitly provided by
a pytest_report_teststatus hook."""
monkeypatch.setenv("PY_COLORS", "1")
pytester.makeconftest(
"""
f"""
def pytest_report_teststatus(report):
return 'foo', 'F', ('FOO', {'red': True})
return {category !r}, 'F', ('FOO', {{'red': True}})
"""
)
pytester.makepyfile(
Expand All @@ -344,7 +345,9 @@ def test_foobar():
pass
"""
)

result = pytester.runpytest("-v")
assert not result.stderr.lines
result.stdout.fnmatch_lines(
color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"])
)
Expand Down Expand Up @@ -2385,8 +2388,8 @@ def __init__(self):
self.option = Namespace(verbose=0)

class rep:
def _get_verbose_word(self, *args):
return mocked_verbose_word
def _get_verbose_word_with_markup(self, *args):
return mocked_verbose_word, {}

class longrepr:
class reprcrash:
Expand Down

0 comments on commit 90459a8

Please sign in to comment.