Skip to content

Commit 612f157

Browse files
CarycaKatarzynabluetech
authored andcommitted
Show reason for skipped test in verbose mode
1 parent 810b878 commit 612f157

File tree

3 files changed

+117
-21
lines changed

3 files changed

+117
-21
lines changed

changelog/2044.improvement.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS".

src/_pytest/terminal.py

Lines changed: 61 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from pathlib import Path
1414
from typing import Any
1515
from typing import Callable
16+
from typing import cast
1617
from typing import Dict
1718
from typing import Generator
1819
from typing import List
@@ -545,6 +546,16 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
545546
line = self._locationline(rep.nodeid, *rep.location)
546547
if not running_xdist:
547548
self.write_ensure_prefix(line, word, **markup)
549+
if rep.skipped or hasattr(report, "wasxfail"):
550+
available_width = (
551+
(self._tw.fullwidth - self._tw.width_of_current_line)
552+
- len(" [100%]")
553+
- 1
554+
)
555+
reason = _get_raw_skip_reason(rep)
556+
reason_ = _format_trimmed(" ({})", reason, available_width)
557+
if reason_ is not None:
558+
self._tw.write(reason_)
548559
if self._show_progress_info:
549560
self._write_progress_information_filling_space()
550561
else:
@@ -1249,6 +1260,31 @@ def _get_pos(config: Config, rep: BaseReport):
12491260
return nodeid
12501261

12511262

1263+
def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]:
1264+
"""Format msg into format, ellipsizing it if doesn't fit in available_width.
1265+
1266+
Returns None if even the ellipsis can't fit.
1267+
"""
1268+
# Only use the first line.
1269+
i = msg.find("\n")
1270+
if i != -1:
1271+
msg = msg[:i]
1272+
1273+
ellipsis = "..."
1274+
format_width = wcswidth(format.format(""))
1275+
if format_width + len(ellipsis) > available_width:
1276+
return None
1277+
1278+
if format_width + wcswidth(msg) > available_width:
1279+
available_width -= len(ellipsis)
1280+
msg = msg[:available_width]
1281+
while format_width + wcswidth(msg) > available_width:
1282+
msg = msg[:-1]
1283+
msg += ellipsis
1284+
1285+
return format.format(msg)
1286+
1287+
12521288
def _get_line_with_reprcrash_message(
12531289
config: Config, rep: BaseReport, termwidth: int
12541290
) -> str:
@@ -1257,34 +1293,19 @@ def _get_line_with_reprcrash_message(
12571293
pos = _get_pos(config, rep)
12581294

12591295
line = f"{verbose_word} {pos}"
1260-
len_line = wcswidth(line)
1261-
ellipsis, len_ellipsis = "...", 3
1262-
if len_line > termwidth - len_ellipsis:
1263-
# No space for an additional message.
1264-
return line
1296+
line_width = wcswidth(line)
12651297

12661298
try:
12671299
# Type ignored intentionally -- possible AttributeError expected.
12681300
msg = rep.longrepr.reprcrash.message # type: ignore[union-attr]
12691301
except AttributeError:
12701302
pass
12711303
else:
1272-
# Only use the first line.
1273-
i = msg.find("\n")
1274-
if i != -1:
1275-
msg = msg[:i]
1276-
len_msg = wcswidth(msg)
1277-
1278-
sep, len_sep = " - ", 3
1279-
max_len_msg = termwidth - len_line - len_sep
1280-
if max_len_msg >= len_ellipsis:
1281-
if len_msg > max_len_msg:
1282-
max_len_msg -= len_ellipsis
1283-
msg = msg[:max_len_msg]
1284-
while wcswidth(msg) > max_len_msg:
1285-
msg = msg[:-1]
1286-
msg += ellipsis
1287-
line += sep + msg
1304+
available_width = termwidth - line_width
1305+
msg = _format_trimmed(" - {}", msg, available_width)
1306+
if msg is not None:
1307+
line += msg
1308+
12881309
return line
12891310

12901311

@@ -1361,3 +1382,22 @@ def format_session_duration(seconds: float) -> str:
13611382
else:
13621383
dt = datetime.timedelta(seconds=int(seconds))
13631384
return f"{seconds:.2f}s ({dt})"
1385+
1386+
1387+
def _get_raw_skip_reason(report: TestReport) -> str:
1388+
"""Get the reason string of a skip/xfail/xpass test report.
1389+
1390+
The string is just the part given by the user.
1391+
"""
1392+
if hasattr(report, "wasxfail"):
1393+
reason = cast(str, report.wasxfail)
1394+
if reason.startswith("reason: "):
1395+
reason = reason[len("reason: ") :]
1396+
return reason
1397+
else:
1398+
assert report.skipped
1399+
assert isinstance(report.longrepr, tuple)
1400+
_, _, reason = report.longrepr
1401+
if reason.startswith("Skipped: "):
1402+
reason = reason[len("Skipped: ") :]
1403+
return reason

testing/test_terminal.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import textwrap
66
from io import StringIO
77
from pathlib import Path
8+
from types import SimpleNamespace
89
from typing import cast
910
from typing import Dict
1011
from typing import List
@@ -23,8 +24,11 @@
2324
from _pytest.pytester import Pytester
2425
from _pytest.reports import BaseReport
2526
from _pytest.reports import CollectReport
27+
from _pytest.reports import TestReport
2628
from _pytest.terminal import _folded_skips
29+
from _pytest.terminal import _format_trimmed
2730
from _pytest.terminal import _get_line_with_reprcrash_message
31+
from _pytest.terminal import _get_raw_skip_reason
2832
from _pytest.terminal import _plugin_nameversions
2933
from _pytest.terminal import getreportopt
3034
from _pytest.terminal import TerminalReporter
@@ -342,6 +346,33 @@ def test_foobar():
342346
color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"])
343347
)
344348

349+
def test_verbose_skip_reason(self, pytester: Pytester) -> None:
350+
pytester.makepyfile(
351+
"""
352+
import pytest
353+
354+
@pytest.mark.skip(reason="123")
355+
def test_1():
356+
pass
357+
358+
@pytest.mark.xfail(reason="456")
359+
def test_2():
360+
pass
361+
362+
@pytest.mark.xfail(reason="789")
363+
def test_3():
364+
assert False
365+
"""
366+
)
367+
result = pytester.runpytest("-v")
368+
result.stdout.fnmatch_lines(
369+
[
370+
"test_verbose_skip_reason.py::test_1 SKIPPED (123) *",
371+
"test_verbose_skip_reason.py::test_2 XPASS (456) *",
372+
"test_verbose_skip_reason.py::test_3 XFAIL (789) *",
373+
]
374+
)
375+
345376

346377
class TestCollectonly:
347378
def test_collectonly_basic(self, pytester: Pytester) -> None:
@@ -2345,3 +2376,27 @@ def test_foo():
23452376
]
23462377
)
23472378
)
2379+
2380+
2381+
def test_raw_skip_reason_skipped() -> None:
2382+
report = SimpleNamespace()
2383+
report.skipped = True
2384+
report.longrepr = ("xyz", 3, "Skipped: Just so")
2385+
2386+
reason = _get_raw_skip_reason(cast(TestReport, report))
2387+
assert reason == "Just so"
2388+
2389+
2390+
def test_raw_skip_reason_xfail() -> None:
2391+
report = SimpleNamespace()
2392+
report.wasxfail = "reason: To everything there is a season"
2393+
2394+
reason = _get_raw_skip_reason(cast(TestReport, report))
2395+
assert reason == "To everything there is a season"
2396+
2397+
2398+
def test_format_trimmed() -> None:
2399+
msg = "unconditional skip"
2400+
2401+
assert _format_trimmed(" ({}) ", msg, len(msg) + 4) == " (unconditional skip) "
2402+
assert _format_trimmed(" ({}) ", msg, len(msg) + 3) == " (unconditional ...) "

0 commit comments

Comments
 (0)