typing: pytester: LineMatcher

This commit is contained in:
Daniel Hahler 2020-02-01 22:30:34 +01:00
parent b10ab0211c
commit 2681b0aed7
2 changed files with 45 additions and 37 deletions

View File

@ -413,8 +413,8 @@ class RunResult:
def __init__(
self,
ret: Union[int, ExitCode],
outlines: Sequence[str],
errlines: Sequence[str],
outlines: List[str],
errlines: List[str],
duration: float,
) -> None:
try:
@ -1327,48 +1327,42 @@ class LineMatcher:
The constructor takes a list of lines without their trailing newlines, i.e.
``text.splitlines()``.
"""
def __init__(self, lines):
def __init__(self, lines: List[str]) -> None:
self.lines = lines
self._log_output = []
self._log_output = [] # type: List[str]
def str(self):
"""Return the entire original text."""
return "\n".join(self.lines)
def _getlines(self, lines2):
def _getlines(self, lines2: Union[str, Sequence[str], Source]) -> Sequence[str]:
if isinstance(lines2, str):
lines2 = Source(lines2)
if isinstance(lines2, Source):
lines2 = lines2.strip().lines
return lines2
def fnmatch_lines_random(self, lines2):
def fnmatch_lines_random(self, lines2: Sequence[str]) -> None:
"""Check lines exist in the output using in any order.
Lines are checked using ``fnmatch.fnmatch``. The argument is a list of
lines which have to occur in the output, in any order.
"""
self._match_lines_random(lines2, fnmatch)
def re_match_lines_random(self, lines2):
def re_match_lines_random(self, lines2: Sequence[str]) -> None:
"""Check lines exist in the output using ``re.match``, in any order.
The argument is a list of lines which have to occur in the output, in
any order.
"""
self._match_lines_random(lines2, lambda name, pat: re.match(pat, name))
self._match_lines_random(lines2, lambda name, pat: bool(re.match(pat, name)))
def _match_lines_random(self, lines2, match_func):
def _match_lines_random(
self, lines2: Sequence[str], match_func: Callable[[str, str], bool]
) -> None:
"""Check lines exist in the output.
The argument is a list of lines which have to occur in the output, in
any order. Each line can contain glob whildcards.
"""
lines2 = self._getlines(lines2)
for line in lines2:
@ -1380,25 +1374,24 @@ class LineMatcher:
self._log("line %r not found in output" % line)
raise ValueError(self._log_text)
def get_lines_after(self, fnline):
def get_lines_after(self, fnline: str) -> Sequence[str]:
"""Return all lines following the given line in the text.
The given line can contain glob wildcards.
"""
for i, line in enumerate(self.lines):
if fnline == line or fnmatch(line, fnline):
return self.lines[i + 1 :]
raise ValueError("line %r not found in output" % fnline)
def _log(self, *args):
def _log(self, *args) -> None:
self._log_output.append(" ".join(str(x) for x in args))
@property
def _log_text(self):
def _log_text(self) -> str:
return "\n".join(self._log_output)
def fnmatch_lines(self, lines2):
def fnmatch_lines(self, lines2: Sequence[str]) -> None:
"""Search captured text for matching lines using ``fnmatch.fnmatch``.
The argument is a list of lines which have to match and can use glob
@ -1408,7 +1401,7 @@ class LineMatcher:
__tracebackhide__ = True
self._match_lines(lines2, fnmatch, "fnmatch")
def re_match_lines(self, lines2):
def re_match_lines(self, lines2: Sequence[str]) -> None:
"""Search captured text for matching lines using ``re.match``.
The argument is a list of lines which have to match using ``re.match``.
@ -1417,9 +1410,16 @@ class LineMatcher:
The matches and non-matches are also shown as part of the error message.
"""
__tracebackhide__ = True
self._match_lines(lines2, lambda name, pat: re.match(pat, name), "re.match")
self._match_lines(
lines2, lambda name, pat: bool(re.match(pat, name)), "re.match"
)
def _match_lines(self, lines2, match_func, match_nickname):
def _match_lines(
self,
lines2: Sequence[str],
match_func: Callable[[str, str], bool],
match_nickname: str,
) -> None:
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``.
:param list[str] lines2: list of string patterns to match. The actual
@ -1465,7 +1465,7 @@ class LineMatcher:
self._fail(msg)
self._log_output = []
def no_fnmatch_line(self, pat):
def no_fnmatch_line(self, pat: str) -> None:
"""Ensure captured lines do not match the given pattern, using ``fnmatch.fnmatch``.
:param str pat: the pattern to match lines.
@ -1473,15 +1473,19 @@ class LineMatcher:
__tracebackhide__ = True
self._no_match_line(pat, fnmatch, "fnmatch")
def no_re_match_line(self, pat):
def no_re_match_line(self, pat: str) -> None:
"""Ensure captured lines do not match the given pattern, using ``re.match``.
:param str pat: the regular expression to match lines.
"""
__tracebackhide__ = True
self._no_match_line(pat, lambda name, pat: re.match(pat, name), "re.match")
self._no_match_line(
pat, lambda name, pat: bool(re.match(pat, name)), "re.match"
)
def _no_match_line(self, pat, match_func, match_nickname):
def _no_match_line(
self, pat: str, match_func: Callable[[str, str], bool], match_nickname: str
) -> None:
"""Ensure captured lines does not have a the given pattern, using ``fnmatch.fnmatch``
:param str pat: the pattern to match lines
@ -1502,8 +1506,12 @@ class LineMatcher:
self._log("{:>{width}}".format("and:", width=wnick), repr(line))
self._log_output = []
def _fail(self, msg):
def _fail(self, msg: str) -> None:
__tracebackhide__ = True
log_text = self._log_text
self._log_output = []
pytest.fail(log_text)
def str(self) -> str:
"""Return the entire original text."""
return "\n".join(self.lines)

View File

@ -462,20 +462,20 @@ def test_linematcher_with_nonlist() -> None:
lm = LineMatcher([])
with pytest.raises(TypeError, match="invalid type for lines2: set"):
lm.fnmatch_lines(set())
lm.fnmatch_lines(set()) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: dict"):
lm.fnmatch_lines({})
lm.fnmatch_lines({}) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: set"):
lm.re_match_lines(set())
lm.re_match_lines(set()) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: dict"):
lm.re_match_lines({})
lm.re_match_lines({}) # type: ignore[arg-type] # noqa: F821
with pytest.raises(TypeError, match="invalid type for lines2: Source"):
lm.fnmatch_lines(Source())
lm.fnmatch_lines(Source()) # type: ignore[arg-type] # noqa: F821
lm.fnmatch_lines([])
lm.fnmatch_lines(())
lm.fnmatch_lines("")
assert lm._getlines({}) == {}
assert lm._getlines(set()) == set()
assert lm._getlines({}) == {} # type: ignore[arg-type,comparison-overlap] # noqa: F821
assert lm._getlines(set()) == set() # type: ignore[arg-type,comparison-overlap] # noqa: F821
assert lm._getlines(Source()) == []
assert lm._getlines(Source("pass\npass")) == ["pass", "pass"]