diff --git a/changelog/6026.improvement.rst b/changelog/6026.improvement.rst new file mode 100644 index 000000000..34dfb278d --- /dev/null +++ b/changelog/6026.improvement.rst @@ -0,0 +1 @@ +Align prefixes in output of pytester's ``LineMatcher``. diff --git a/src/_pytest/pytester.py b/src/_pytest/pytester.py index a050dad09..2974420f5 100644 --- a/src/_pytest/pytester.py +++ b/src/_pytest/pytester.py @@ -1344,7 +1344,6 @@ class LineMatcher: 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) @@ -1352,6 +1351,7 @@ class LineMatcher: nextline = None extralines = [] __tracebackhide__ = True + wnick = len(match_nickname) + 1 for line in lines2: nomatchprinted = False while lines1: @@ -1361,17 +1361,21 @@ class LineMatcher: 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``. @@ -1396,16 +1400,19 @@ class LineMatcher: """ __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()) 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 = [] diff --git a/testing/test_pytester.py b/testing/test_pytester.py index f8b0896c5..63710143a 100644 --- a/testing/test_pytester.py +++ b/testing/test_pytester.py @@ -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" @@ -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