diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index 82a7a7bbe..f77845ca4 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -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) diff --git a/testing/test_pytester.py b/testing/test_pytester.py index da5fb99a1..a6901e967 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -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"]