summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRan Benita <ran@unusedvar.com>2020-12-09 10:11:47 +0200
committerGitHub <noreply@github.com>2020-12-09 10:11:47 +0200
commit902739cfc3bbc3379e6ef99c8e250de35f52ecde (patch)
treedece55f0456647aed40855e8bc40cfa976816e7f
parent810b878ef8eac6f39cfff35705a6a10083ace0bc (diff)
parent612f157dbd021c231cca24726e53ed5c50debe48 (diff)
downloadpytest-902739cfc3bbc3379e6ef99c8e250de35f52ecde.tar.gz
Merge pull request #7208 from CarycaKatarzyna/issue2044
Issue 2044 - show skipping reason in verbose mode
-rw-r--r--changelog/2044.improvement.rst1
-rw-r--r--src/_pytest/terminal.py82
-rw-r--r--testing/test_terminal.py55
3 files changed, 117 insertions, 21 deletions
diff --git a/changelog/2044.improvement.rst b/changelog/2044.improvement.rst
new file mode 100644
index 000000000..c9e47c3f6
--- /dev/null
+++ b/changelog/2044.improvement.rst
@@ -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".
diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py
index 7d2943dd0..2e68e2575 100644
--- a/src/_pytest/terminal.py
+++ b/src/_pytest/terminal.py
@@ -13,6 +13,7 @@ from functools import partial
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
@@ -545,6 +546,16 @@ class TerminalReporter:
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:
@@ -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:
@@ -1257,11 +1293,7 @@ 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.
@@ -1269,22 +1301,11 @@ def _get_line_with_reprcrash_message(
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
@@ -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
diff --git a/testing/test_terminal.py b/testing/test_terminal.py
index a4d22d2aa..fdd4301f9 100644
--- a/testing/test_terminal.py
+++ b/testing/test_terminal.py
@@ -5,6 +5,7 @@ import sys
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
@@ -23,8 +24,11 @@ from _pytest.monkeypatch import MonkeyPatch
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
@@ -342,6 +346,33 @@ class TestTerminal:
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:
@@ -2345,3 +2376,27 @@ class TestCodeHighlight:
]
)
)
+
+
+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 ...) "