Skip to content

Commit

Permalink
summary/OutcomeException: add support for short_msg
Browse files Browse the repository at this point in the history
This is meant to improve the short msg with `-r`, where you want to see
the non-matched line, and not the first line (that might have matched),
from pytester's `_match_lines`.

This will also come in handy for a better `__repr__`.
  • Loading branch information
blueyed committed Oct 19, 2019
1 parent c9524af commit 8aedb0b
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 9 deletions.
8 changes: 8 additions & 0 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import _pytest
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.outcomes import OutcomeException

if False: # TYPE_CHECKING
from typing import Type
Expand Down Expand Up @@ -519,6 +520,13 @@ def exconly(self, tryshort: bool = False) -> str:
the exception representation is returned (so 'AssertionError: ' is
removed from the beginning)
"""
if (
tryshort
and isinstance(self.value, OutcomeException)
and self.value.short_msg
):
return self.value.short_msg

lines = format_exception_only(self.type, self.value)
text = "".join(lines)
text = text.rstrip()
Expand Down
30 changes: 24 additions & 6 deletions src/_pytest/outcomes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ class OutcomeException(BaseException):
contain info about test and collection outcomes.
"""

def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
def __init__(
self,
msg: Optional[str] = None,
pytrace: bool = True,
short_msg: Optional[str] = None,
) -> None:
if msg is not None and not isinstance(msg, str):
error_msg = (
"{} expected string as 'msg' parameter, got '{}' instead.\n"
Expand All @@ -27,13 +32,24 @@ def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
BaseException.__init__(self, msg)
self.msg = msg
self.pytrace = pytrace
self.short_msg = short_msg

def __repr__(self) -> str:
if self.short_msg:
return "<{} short_msg={!r}>".format(self.__class__.__name__, self.short_msg)
msg = self.msg
if msg:
lines = msg.split("\n", maxsplit=1)
if len(lines) > 1:
msg = lines[0] + "..."
else:
msg = lines[0]
return "<{} msg={!r}>".format(self.__class__.__name__, msg)

def __str__(self) -> str:
if self.msg:
return self.msg
return "<{} instance>".format(self.__class__.__name__)

__str__ = __repr__
return self.__repr__()


TEST_OUTCOME = (OutcomeException, Exception)
Expand Down Expand Up @@ -116,7 +132,9 @@ def skip(msg: str = "", *, allow_module_level: bool = False) -> "NoReturn":
skip.Exception = Skipped # type: ignore


def fail(msg: str = "", pytrace: bool = True) -> "NoReturn":
def fail(
msg: str = "", pytrace: bool = True, short_msg: Optional[str] = None
) -> "NoReturn":
"""
Explicitly fail an executing test with the given message.
Expand All @@ -125,7 +143,7 @@ def fail(msg: str = "", pytrace: bool = True) -> "NoReturn":
python traceback will be reported.
"""
__tracebackhide__ = True
raise Failed(msg=msg, pytrace=pytrace)
raise Failed(msg=msg, pytrace=pytrace, short_msg=short_msg)


# Ignore type because of https://github.com/python/mypy/issues/2087.
Expand Down
4 changes: 3 additions & 1 deletion src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,9 @@ def _match_lines(self, lines2, match_func, match_nickname):
extralines.append(nextline)
else:
self._log("remains unmatched: {!r}".format(line))
pytest.fail(self._log_text)
pytest.fail(
self._log_text, short_msg="remains unmatched: {!r}".format(line)
)

def no_fnmatch_line(self, pat):
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
Expand Down
11 changes: 11 additions & 0 deletions testing/test_outcomes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from _pytest.outcomes import OutcomeException


def test_OutcomeException():
assert repr(OutcomeException()) == "<OutcomeException msg=None>"
assert repr(OutcomeException(msg="msg")) == "<OutcomeException msg='msg'>"
assert repr(OutcomeException(msg="msg\nline2")) == "<OutcomeException msg='msg...'>"
assert (
repr(OutcomeException(short_msg="short"))
== "<OutcomeException short_msg='short'>"
)
15 changes: 15 additions & 0 deletions testing/test_pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,21 @@ def test_linematcher_with_nonlist():
assert lm._getlines(set()) == set()


def test_linematcher_fnmatch_lines():
lm = LineMatcher(["1", "2", "3"])
with pytest.raises(pytest.fail.Exception) as excinfo:
lm.fnmatch_lines(["2", "last_unmatched"])
assert excinfo.value.short_msg == "remains unmatched: 'last_unmatched'"
assert str(excinfo.value).splitlines() == [
"nomatch: '2'",
" and: '1'",
"exact match: '2'",
"nomatch: 'last_unmatched'",
" and: '3'",
"remains unmatched: 'last_unmatched'",
]


@pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"])
def test_no_matching(function):
""""""
Expand Down
9 changes: 7 additions & 2 deletions testing/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,8 +550,13 @@ def test_pytest_exit():
def test_pytest_fail():
with pytest.raises(pytest.fail.Exception) as excinfo:
pytest.fail("hello")
s = excinfo.exconly(tryshort=True)
assert s.startswith("Failed")
assert excinfo.exconly(tryshort=True) == "Failed: hello"
assert excinfo.exconly(tryshort=False) == "Failed: hello"

with pytest.raises(pytest.fail.Exception) as excinfo:
pytest.fail("hello", short_msg="short message")
assert excinfo.exconly(tryshort=True) == "short message"
assert excinfo.exconly(tryshort=False) == "Failed: hello"


def test_pytest_exit_msg(testdir):
Expand Down

0 comments on commit 8aedb0b

Please sign in to comment.