Skip to content

Commit

Permalink
Merge pull request pytest-dev#7208 from CarycaKatarzyna/issue2044
Browse files Browse the repository at this point in the history
Issue 2044 - show skipping reason in verbose mode
  • Loading branch information
bluetech committed Dec 9, 2020
2 parents 810b878 + 612f157 commit 902739c
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 21 deletions.
1 change: 1 addition & 0 deletions changelog/2044.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Verbose mode now shows the reason that a test was skipped in the test's terminal line after the "SKIPPED", "XFAIL" or "XPASS".
82 changes: 61 additions & 21 deletions src/_pytest/terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from pathlib import Path
from typing import Any
from typing import Callable
from typing import cast
from typing import Dict
from typing import Generator
from typing import List
Expand Down Expand Up @@ -545,6 +546,16 @@ def pytest_runtest_logreport(self, report: TestReport) -> None:
line = self._locationline(rep.nodeid, *rep.location)
if not running_xdist:
self.write_ensure_prefix(line, word, **markup)
if rep.skipped or hasattr(report, "wasxfail"):
available_width = (
(self._tw.fullwidth - self._tw.width_of_current_line)
- len(" [100%]")
- 1
)
reason = _get_raw_skip_reason(rep)
reason_ = _format_trimmed(" ({})", reason, available_width)
if reason_ is not None:
self._tw.write(reason_)
if self._show_progress_info:
self._write_progress_information_filling_space()
else:
Expand Down Expand Up @@ -1249,6 +1260,31 @@ def _get_pos(config: Config, rep: BaseReport):
return nodeid


def _format_trimmed(format: str, msg: str, available_width: int) -> Optional[str]:
"""Format msg into format, ellipsizing it if doesn't fit in available_width.
Returns None if even the ellipsis can't fit.
"""
# Only use the first line.
i = msg.find("\n")
if i != -1:
msg = msg[:i]

ellipsis = "..."
format_width = wcswidth(format.format(""))
if format_width + len(ellipsis) > available_width:
return None

if format_width + wcswidth(msg) > available_width:
available_width -= len(ellipsis)
msg = msg[:available_width]
while format_width + wcswidth(msg) > available_width:
msg = msg[:-1]
msg += ellipsis

return format.format(msg)


def _get_line_with_reprcrash_message(
config: Config, rep: BaseReport, termwidth: int
) -> str:
Expand All @@ -1257,34 +1293,19 @@ def _get_line_with_reprcrash_message(
pos = _get_pos(config, rep)

line = f"{verbose_word} {pos}"
len_line = wcswidth(line)
ellipsis, len_ellipsis = "...", 3
if len_line > termwidth - len_ellipsis:
# No space for an additional message.
return line
line_width = wcswidth(line)

try:
# Type ignored intentionally -- possible AttributeError expected.
msg = rep.longrepr.reprcrash.message # type: ignore[union-attr]
except AttributeError:
pass
else:
# Only use the first line.
i = msg.find("\n")
if i != -1:
msg = msg[:i]
len_msg = wcswidth(msg)

sep, len_sep = " - ", 3
max_len_msg = termwidth - len_line - len_sep
if max_len_msg >= len_ellipsis:
if len_msg > max_len_msg:
max_len_msg -= len_ellipsis
msg = msg[:max_len_msg]
while wcswidth(msg) > max_len_msg:
msg = msg[:-1]
msg += ellipsis
line += sep + msg
available_width = termwidth - line_width
msg = _format_trimmed(" - {}", msg, available_width)
if msg is not None:
line += msg

return line


Expand Down Expand Up @@ -1361,3 +1382,22 @@ def format_session_duration(seconds: float) -> str:
else:
dt = datetime.timedelta(seconds=int(seconds))
return f"{seconds:.2f}s ({dt})"


def _get_raw_skip_reason(report: TestReport) -> str:
"""Get the reason string of a skip/xfail/xpass test report.
The string is just the part given by the user.
"""
if hasattr(report, "wasxfail"):
reason = cast(str, report.wasxfail)
if reason.startswith("reason: "):
reason = reason[len("reason: ") :]
return reason
else:
assert report.skipped
assert isinstance(report.longrepr, tuple)
_, _, reason = report.longrepr
if reason.startswith("Skipped: "):
reason = reason[len("Skipped: ") :]
return reason
55 changes: 55 additions & 0 deletions testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import textwrap
from io import StringIO
from pathlib import Path
from types import SimpleNamespace
from typing import cast
from typing import Dict
from typing import List
Expand All @@ -23,8 +24,11 @@
from _pytest.pytester import Pytester
from _pytest.reports import BaseReport
from _pytest.reports import CollectReport
from _pytest.reports import TestReport
from _pytest.terminal import _folded_skips
from _pytest.terminal import _format_trimmed
from _pytest.terminal import _get_line_with_reprcrash_message
from _pytest.terminal import _get_raw_skip_reason
from _pytest.terminal import _plugin_nameversions
from _pytest.terminal import getreportopt
from _pytest.terminal import TerminalReporter
Expand Down Expand Up @@ -342,6 +346,33 @@ def test_foobar():
color_mapping.format_for_fnmatch(["*{red}FOO{reset}*"])
)

def test_verbose_skip_reason(self, pytester: Pytester) -> None:
pytester.makepyfile(
"""
import pytest
@pytest.mark.skip(reason="123")
def test_1():
pass
@pytest.mark.xfail(reason="456")
def test_2():
pass
@pytest.mark.xfail(reason="789")
def test_3():
assert False
"""
)
result = pytester.runpytest("-v")
result.stdout.fnmatch_lines(
[
"test_verbose_skip_reason.py::test_1 SKIPPED (123) *",
"test_verbose_skip_reason.py::test_2 XPASS (456) *",
"test_verbose_skip_reason.py::test_3 XFAIL (789) *",
]
)


class TestCollectonly:
def test_collectonly_basic(self, pytester: Pytester) -> None:
Expand Down Expand Up @@ -2345,3 +2376,27 @@ def test_foo():
]
)
)


def test_raw_skip_reason_skipped() -> None:
report = SimpleNamespace()
report.skipped = True
report.longrepr = ("xyz", 3, "Skipped: Just so")

reason = _get_raw_skip_reason(cast(TestReport, report))
assert reason == "Just so"


def test_raw_skip_reason_xfail() -> None:
report = SimpleNamespace()
report.wasxfail = "reason: To everything there is a season"

reason = _get_raw_skip_reason(cast(TestReport, report))
assert reason == "To everything there is a season"


def test_format_trimmed() -> None:
msg = "unconditional skip"

assert _format_trimmed(" ({}) ", msg, len(msg) + 4) == " (unconditional skip) "
assert _format_trimmed(" ({}) ", msg, len(msg) + 3) == " (unconditional ...) "

0 comments on commit 902739c

Please sign in to comment.