Skip to content

Commit

Permalink
Merge branch 'summary-short_msg' into my-master
Browse files Browse the repository at this point in the history
  • Loading branch information
blueyed committed Nov 1, 2019
2 parents f16d1a4 + 53568a5 commit 000b906
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 10 deletions.
1 change: 1 addition & 0 deletions changelog/6003.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve short excinfo with LineMatcher failures in short test summaries, via new ``OutcomeException.short_msg``.
7 changes: 7 additions & 0 deletions src/_pytest/_code/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,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)
if isinstance(self.value, OutcomeException):
# Remove module prefix.
Expand Down
31 changes: 25 additions & 6 deletions src/_pytest/outcomes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,13 @@ 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 +33,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 repr(self)


TEST_OUTCOME = (OutcomeException, Exception)
Expand Down Expand Up @@ -110,7 +127,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 @@ -119,7 +138,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 @@ -1413,7 +1413,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.lstrip())
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 @@ -493,6 +493,21 @@ def test_linematcher_match_failure():
]


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):
if function == "no_fnmatch_line":
Expand Down
9 changes: 7 additions & 2 deletions testing/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -559,8 +559,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
14 changes: 13 additions & 1 deletion testing/test_terminal.py
Original file line number Diff line number Diff line change
Expand Up @@ -758,14 +758,26 @@ def test(i):

def test_fail_extra_reporting(testdir, monkeypatch):
monkeypatch.setenv("COLUMNS", "80")
testdir.makepyfile("def test_this(): assert 0, 'this_failed' * 100")
testdir.makepyfile(
"""
def test_this():
assert 0, 'this_failed' * 100
def test_linematcher():
from _pytest.pytester import LineMatcher
LineMatcher(["1", "2", "3"]).fnmatch_lines(["2", "last_unmatched"])
"""
)
result = testdir.runpytest()
result.stdout.no_fnmatch_line("*short test summary*")
result = testdir.runpytest("-rf")
result.stdout.fnmatch_lines(
[
"*test summary*",
"FAILED test_fail_extra_reporting.py::test_this - AssertionError: this_failedt...",
"FAILED test_fail_extra_reporting.py::test_linematcher - remains unmatched: 'l...",
"*= 2 failed in *",
]
)

Expand Down

0 comments on commit 000b906

Please sign in to comment.