pytester.LineMatcher: add support for matching lines consecutively

This commit is contained in:
Daniel Hahler 2020-02-01 23:33:24 +01:00
parent 50f81db817
commit 5256542ea4
3 changed files with 47 additions and 4 deletions

View File

@ -0,0 +1 @@
Add support for matching lines consecutively with :attr:`LineMatcher <_pytest.pytester.LineMatcher>`'s :func:`~_pytest.pytester.LineMatcher.fnmatch_lines` and :func:`~_pytest.pytester.LineMatcher.re_match_lines`.

View File

@ -1380,7 +1380,9 @@ class LineMatcher:
def _log_text(self) -> str:
return "\n".join(self._log_output)
def fnmatch_lines(self, lines2: Sequence[str]) -> None:
def fnmatch_lines(
self, lines2: Sequence[str], *, consecutive: bool = False
) -> None:
"""Check lines exist in the output (using :func:`python:fnmatch.fnmatch`).
The argument is a list of lines which have to match and can use glob
@ -1388,11 +1390,14 @@ class LineMatcher:
matches and non-matches are also shown as part of the error message.
:param lines2: string patterns to match.
:param consecutive: match lines consecutive?
"""
__tracebackhide__ = True
self._match_lines(lines2, fnmatch, "fnmatch")
self._match_lines(lines2, fnmatch, "fnmatch", consecutive=consecutive)
def re_match_lines(self, lines2: Sequence[str]) -> None:
def re_match_lines(
self, lines2: Sequence[str], *, consecutive: bool = False
) -> None:
"""Check lines exist in the output (using :func:`python:re.match`).
The argument is a list of lines which have to match using ``re.match``.
@ -1401,10 +1406,14 @@ class LineMatcher:
The matches and non-matches are also shown as part of the error message.
:param lines2: string patterns to match.
:param consecutive: match lines consecutively?
"""
__tracebackhide__ = True
self._match_lines(
lines2, lambda name, pat: bool(re.match(pat, name)), "re.match"
lines2,
lambda name, pat: bool(re.match(pat, name)),
"re.match",
consecutive=consecutive,
)
def _match_lines(
@ -1412,6 +1421,8 @@ class LineMatcher:
lines2: Sequence[str],
match_func: Callable[[str, str], bool],
match_nickname: str,
*,
consecutive: bool = False
) -> None:
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
@ -1422,6 +1433,7 @@ class LineMatcher:
pattern
:param str match_nickname: the nickname for the match function that
will be logged to stdout when a match occurs
:param consecutive: match lines consecutively?
"""
if not isinstance(lines2, collections.abc.Sequence):
raise TypeError("invalid type for lines2: {}".format(type(lines2).__name__))
@ -1431,20 +1443,30 @@ class LineMatcher:
extralines = []
__tracebackhide__ = True
wnick = len(match_nickname) + 1
started = False
for line in lines2:
nomatchprinted = False
while lines1:
nextline = lines1.pop(0)
if line == nextline:
self._log("exact match:", repr(line))
started = True
break
elif match_func(nextline, line):
self._log("%s:" % match_nickname, repr(line))
self._log(
"{:>{width}}".format("with:", width=wnick), repr(nextline)
)
started = True
break
else:
if consecutive and started:
msg = "no consecutive match: {!r}".format(line)
self._log(msg)
self._log(
"{:>{width}}".format("with:", width=wnick), repr(nextline)
)
self._fail(msg)
if not nomatchprinted:
self._log(
"{:>{width}}".format("nomatch:", width=wnick), repr(line)

View File

@ -508,6 +508,26 @@ def test_linematcher_match_failure() -> None:
]
def test_linematcher_consecutive():
lm = LineMatcher(["1", "", "2"])
with pytest.raises(pytest.fail.Exception) as excinfo:
lm.fnmatch_lines(["1", "2"], consecutive=True)
assert str(excinfo.value).splitlines() == [
"exact match: '1'",
"no consecutive match: '2'",
" with: ''",
]
lm.re_match_lines(["1", r"\d?", "2"], consecutive=True)
with pytest.raises(pytest.fail.Exception) as excinfo:
lm.re_match_lines(["1", r"\d", "2"], consecutive=True)
assert str(excinfo.value).splitlines() == [
"exact match: '1'",
r"no consecutive match: '\\d'",
" with: ''",
]
@pytest.mark.parametrize("function", ["no_fnmatch_line", "no_re_match_line"])
def test_linematcher_no_matching(function) -> None:
if function == "no_fnmatch_line":