Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6026.improvement.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Align prefixes in output of pytester's ``LineMatcher``.
25 changes: 16 additions & 9 deletions src/_pytest/pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -1344,14 +1344,14 @@ def _match_lines(self, lines2, match_func, match_nickname):
pattern
:param str match_nickname: the nickname for the match function that
will be logged to stdout when a match occurs

"""
assert isinstance(lines2, Sequence)
lines2 = self._getlines(lines2)
lines1 = self.lines[:]
nextline = None
extralines = []
__tracebackhide__ = True
wnick = len(match_nickname) + 1
for line in lines2:
nomatchprinted = False
while lines1:
Expand All @@ -1361,17 +1361,21 @@ def _match_lines(self, lines2, match_func, match_nickname):
break
elif match_func(nextline, line):
self._log("%s:" % match_nickname, repr(line))
self._log(" with:", repr(nextline))
self._log(
"{:>{width}}".format("with:", width=wnick), repr(nextline)
)
break
else:
if not nomatchprinted:
self._log("nomatch:", repr(line))
self._log(
"{:>{width}}".format("nomatch:", width=wnick), repr(line)
)
nomatchprinted = True
self._log(" and:", repr(nextline))
self._log("{:>{width}}".format("and:", width=wnick), repr(nextline))
extralines.append(nextline)
else:
self._log("remains unmatched: {!r}".format(line))
pytest.fail(self._log_text)
pytest.fail(self._log_text.lstrip())

def no_fnmatch_line(self, pat):
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
Expand All @@ -1396,16 +1400,19 @@ def _no_match_line(self, pat, match_func, match_nickname):
"""
__tracebackhide__ = True
nomatch_printed = False
wnick = len(match_nickname) + 1
try:
for line in self.lines:
if match_func(line, pat):
self._log("%s:" % match_nickname, repr(pat))
self._log(" with:", repr(line))
pytest.fail(self._log_text)
self._log("{:>{width}}".format("with:", width=wnick), repr(line))
pytest.fail(self._log_text.lstrip())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the lstrip is kind of a hack, to not have leading whitespace with the pytest.fail.Exception message.
Could be revisited after #6003 .

else:
if not nomatch_printed:
self._log("nomatch:", repr(pat))
self._log(
"{:>{width}}".format("nomatch:", width=wnick), repr(pat)
)
nomatch_printed = True
self._log(" and:", repr(line))
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
finally:
self._log_output = []
59 changes: 44 additions & 15 deletions testing/test_pytester.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,16 +457,39 @@ def test_linematcher_with_nonlist():
assert lm._getlines(set()) == set()


def test_linematcher_match_failure():
lm = LineMatcher(["foo", "foo", "bar"])
with pytest.raises(pytest.fail.Exception) as e:
lm.fnmatch_lines(["foo", "f*", "baz"])
assert e.value.msg.splitlines() == [
"exact match: 'foo'",
"fnmatch: 'f*'",
" with: 'foo'",
"nomatch: 'baz'",
" and: 'bar'",
"remains unmatched: 'baz'",
]

lm = LineMatcher(["foo", "foo", "bar"])
with pytest.raises(pytest.fail.Exception) as e:
lm.re_match_lines(["foo", "^f.*", "baz"])
assert e.value.msg.splitlines() == [
"exact match: 'foo'",
"re.match: '^f.*'",
" with: 'foo'",
" nomatch: 'baz'",
" and: 'bar'",
"remains unmatched: 'baz'",
]


@pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"])
def test_no_matching(function):
""""""
if function == "no_fnmatch_line":
match_func_name = "fnmatch"
good_pattern = "*.py OK*"
bad_pattern = "*X.py OK*"
else:
assert function == "no_re_match_line"
match_func_name = "re.match"
good_pattern = r".*py OK"
bad_pattern = r".*Xpy OK"

Expand All @@ -480,24 +503,30 @@ def test_no_matching(function):
]
)

def check_failure_lines(lines):
expected = [
"nomatch: '{}'".format(good_pattern),
" and: 'cachedir: .pytest_cache'",
" and: 'collecting ... collected 1 item'",
" and: ''",
"{}: '{}'".format(match_func_name, good_pattern),
" with: 'show_fixtures_per_test.py OK'",
]
assert lines == expected

# check the function twice to ensure we don't accumulate the internal buffer
for i in range(2):
with pytest.raises(pytest.fail.Exception) as e:
func = getattr(lm, function)
func(good_pattern)
obtained = str(e.value).splitlines()
check_failure_lines(obtained)
if function == "no_fnmatch_line":
assert obtained == [
"nomatch: '{}'".format(good_pattern),
" and: 'cachedir: .pytest_cache'",
" and: 'collecting ... collected 1 item'",
" and: ''",
"fnmatch: '{}'".format(good_pattern),
" with: 'show_fixtures_per_test.py OK'",
]
else:
assert obtained == [
"nomatch: '{}'".format(good_pattern),
" and: 'cachedir: .pytest_cache'",
" and: 'collecting ... collected 1 item'",
" and: ''",
"re.match: '{}'".format(good_pattern),
" with: 'show_fixtures_per_test.py OK'",
]

func = getattr(lm, function)
func(bad_pattern) # bad pattern does not match any line: passes
Expand Down