Skip to content

Commit 18ca7c2

Browse files
Merge pull request #12472 from pbrezina/testresult-markup (#12555)
(cherry picked from commit 90459a8) Co-authored-by: Sviatoslav Sydorenko (Святослав Сидоренко) <[email protected]>
1 parent 353bc7f commit 18ca7c2

File tree

4 files changed

+41
-18
lines changed

4 files changed

+41
-18
lines changed

Diff for: changelog/12472.bugfix.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a crash when returning category ``"error"`` or ``"failed"`` with a custom test status from :hook:`pytest_report_teststatus` hook -- :user:`pbrezina`.

Diff for: src/_pytest/reports.py

+19-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from typing import Literal
1414
from typing import Mapping
1515
from typing import NoReturn
16+
from typing import Sequence
1617
from typing import TYPE_CHECKING
1718

1819
from _pytest._code.code import ExceptionChainRepr
@@ -30,6 +31,7 @@
3031
from _pytest.config import Config
3132
from _pytest.nodes import Collector
3233
from _pytest.nodes import Item
34+
from _pytest.outcomes import fail
3335
from _pytest.outcomes import skip
3436

3537

@@ -190,11 +192,26 @@ def head_line(self) -> str | None:
190192
return domain
191193
return None
192194

193-
def _get_verbose_word(self, config: Config):
195+
def _get_verbose_word_with_markup(
196+
self, config: Config, default_markup: Mapping[str, bool]
197+
) -> tuple[str, Mapping[str, bool]]:
194198
_category, _short, verbose = config.hook.pytest_report_teststatus(
195199
report=self, config=config
196200
)
197-
return verbose
201+
202+
if isinstance(verbose, str):
203+
return verbose, default_markup
204+
205+
if isinstance(verbose, Sequence) and len(verbose) == 2:
206+
word, markup = verbose
207+
if isinstance(word, str) and isinstance(markup, Mapping):
208+
return word, markup
209+
210+
fail( # pragma: no cover
211+
"pytest_report_teststatus() hook (from a plugin) returned "
212+
f"an invalid verbose value: {verbose!r}.\nExpected either a string "
213+
"or a tuple of (word, markup)."
214+
)
198215

199216
def _to_json(self) -> dict[str, Any]:
200217
"""Return the contents of this report as a dict of builtin entries,

Diff for: src/_pytest/terminal.py

+13-11
Original file line numberDiff line numberDiff line change
@@ -1179,10 +1179,10 @@ def show_simple(lines: list[str], *, stat: str) -> None:
11791179
def show_xfailed(lines: list[str]) -> None:
11801180
xfailed = self.stats.get("xfailed", [])
11811181
for rep in xfailed:
1182-
verbose_word = rep._get_verbose_word(self.config)
1183-
markup_word = self._tw.markup(
1184-
verbose_word, **{_color_for_type["warnings"]: True}
1182+
verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1183+
self.config, {_color_for_type["warnings"]: True}
11851184
)
1185+
markup_word = self._tw.markup(verbose_word, **verbose_markup)
11861186
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
11871187
line = f"{markup_word} {nodeid}"
11881188
reason = rep.wasxfail
@@ -1194,10 +1194,10 @@ def show_xfailed(lines: list[str]) -> None:
11941194
def show_xpassed(lines: list[str]) -> None:
11951195
xpassed = self.stats.get("xpassed", [])
11961196
for rep in xpassed:
1197-
verbose_word = rep._get_verbose_word(self.config)
1198-
markup_word = self._tw.markup(
1199-
verbose_word, **{_color_for_type["warnings"]: True}
1197+
verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1198+
self.config, {_color_for_type["warnings"]: True}
12001199
)
1200+
markup_word = self._tw.markup(verbose_word, **verbose_markup)
12011201
nodeid = _get_node_id_with_markup(self._tw, self.config, rep)
12021202
line = f"{markup_word} {nodeid}"
12031203
reason = rep.wasxfail
@@ -1210,10 +1210,10 @@ def show_skipped(lines: list[str]) -> None:
12101210
fskips = _folded_skips(self.startpath, skipped) if skipped else []
12111211
if not fskips:
12121212
return
1213-
verbose_word = skipped[0]._get_verbose_word(self.config)
1214-
markup_word = self._tw.markup(
1215-
verbose_word, **{_color_for_type["warnings"]: True}
1213+
verbose_word, verbose_markup = skipped[0]._get_verbose_word_with_markup(
1214+
self.config, {_color_for_type["warnings"]: True}
12161215
)
1216+
markup_word = self._tw.markup(verbose_word, **verbose_markup)
12171217
prefix = "Skipped: "
12181218
for num, fspath, lineno, reason in fskips:
12191219
if reason.startswith(prefix):
@@ -1394,8 +1394,10 @@ def _get_line_with_reprcrash_message(
13941394
config: Config, rep: BaseReport, tw: TerminalWriter, word_markup: dict[str, bool]
13951395
) -> str:
13961396
"""Get summary line for a report, trying to add reprcrash message."""
1397-
verbose_word = rep._get_verbose_word(config)
1398-
word = tw.markup(verbose_word, **word_markup)
1397+
verbose_word, verbose_markup = rep._get_verbose_word_with_markup(
1398+
config, word_markup
1399+
)
1400+
word = tw.markup(verbose_word, **verbose_markup)
13991401
node = _get_node_id_with_markup(tw, config, rep)
14001402

14011403
line = f"{word} {node}"

Diff for: testing/test_terminal.py

+8-5
Original file line numberDiff line numberDiff line change
@@ -326,16 +326,17 @@ def test_rewrite(self, pytester: Pytester, monkeypatch) -> None:
326326
tr.rewrite("hey", erase=True)
327327
assert f.getvalue() == "hello" + "\r" + "hey" + (6 * " ")
328328

329+
@pytest.mark.parametrize("category", ["foo", "failed", "error", "passed"])
329330
def test_report_teststatus_explicit_markup(
330-
self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping
331+
self, monkeypatch: MonkeyPatch, pytester: Pytester, color_mapping, category: str
331332
) -> None:
332333
"""Test that TerminalReporter handles markup explicitly provided by
333334
a pytest_report_teststatus hook."""
334335
monkeypatch.setenv("PY_COLORS", "1")
335336
pytester.makeconftest(
336-
"""
337+
f"""
337338
def pytest_report_teststatus(report):
338-
return 'foo', 'F', ('FOO', {'red': True})
339+
return {category !r}, 'F', ('FOO', {{'red': True}})
339340
"""
340341
)
341342
pytester.makepyfile(
@@ -344,7 +345,9 @@ def test_foobar():
344345
pass
345346
"""
346347
)
348+
347349
result = pytester.runpytest("-v")
350+
assert not result.stderr.lines
348351
result.stdout.fnmatch_lines(
349352
color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"])
350353
)
@@ -2385,8 +2388,8 @@ def __init__(self):
23852388
self.option = Namespace(verbose=0)
23862389

23872390
class rep:
2388-
def _get_verbose_word(self, *args):
2389-
return mocked_verbose_word
2391+
def _get_verbose_word_with_markup(self, *args):
2392+
return mocked_verbose_word, {}
23902393

23912394
class longrepr:
23922395
class reprcrash:

0 commit comments

Comments
 (0)