From 211ea2b0228d8c0152bea0316198a51f8ae7af0b Mon Sep 17 00:00:00 2001 From: Ronny Pfannschmidt Date: Fri, 2 Apr 2021 00:59:34 +0200 Subject: [PATCH] enhance excinfo.match failure output * now multiline and compares regexes * warns on forgotten escaping --- src/_pytest/_code/code.py | 9 +++++---- testing/code/test_excinfo.py | 14 ++++++++------ testing/python/raises.py | 17 ++++++++++------- 3 files changed, 23 insertions(+), 17 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index bd2132a9a22..70958059217 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -665,10 +665,11 @@ def match(self, regexp: Union[str, Pattern[str]]) -> "Literal[True]": If it matches `True` is returned, otherwise an `AssertionError` is raised. """ __tracebackhide__ = True - msg = "Regex pattern {!r} does not match {!r}." - if regexp == str(self.value): - msg += " Did you mean to `re.escape()` the regex?" - assert re.search(regexp, str(self.value)), msg.format(regexp, str(self.value)) + value = str(self.value) + msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}" + if regexp == value: + msg += "\n Did you mean to `re.escape()` the regex?" + assert re.search(regexp, value), msg # Return True to allow for "assert excinfo.match()". return True diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 9cd862ae433..7e1c476714f 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -420,18 +420,20 @@ def test_division_zero(): excinfo.match(r'[123]+') """ ) - result = pytester.runpytest() + result = pytester.runpytest("--tb=short") assert result.ret != 0 - exc_msg = "Regex pattern '[[]123[]]+' does not match 'division by zero'." - result.stdout.fnmatch_lines([f"E * AssertionError: {exc_msg}"]) + match = [ + r"E .* AssertionError: Regex pattern did not match.", + r"E .* Regex: '\[123\]\+'", + r"E .* Input: 'division by zero'", + ] + result.stdout.re_match_lines(match) result.stdout.no_fnmatch_line("*__tracebackhide__ = True*") result = pytester.runpytest("--fulltrace") assert result.ret != 0 - result.stdout.fnmatch_lines( - ["*__tracebackhide__ = True*", f"E * AssertionError: {exc_msg}"] - ) + result.stdout.re_match_lines([r".*__tracebackhide__ = True.*", *match]) class TestFormattedExcinfo: diff --git a/testing/python/raises.py b/testing/python/raises.py index 2d62e91091b..112dec06cdc 100644 --- a/testing/python/raises.py +++ b/testing/python/raises.py @@ -191,10 +191,12 @@ def test_raises_match(self) -> None: int("asdf") msg = "with base 16" - expr = "Regex pattern {!r} does not match \"invalid literal for int() with base 10: 'asdf'\".".format( - msg + expr = ( + "Regex pattern did not match.\n" + f" Regex: {msg!r}\n" + " Input: \"invalid literal for int() with base 10: 'asdf'\"" ) - with pytest.raises(AssertionError, match=re.escape(expr)): + with pytest.raises(AssertionError, match="(?m)" + re.escape(expr)): with pytest.raises(ValueError, match=msg): int("asdf", base=10) @@ -217,7 +219,7 @@ def test_match_failure_string_quoting(self): with pytest.raises(AssertionError, match="'foo"): raise AssertionError("'bar") (msg,) = excinfo.value.args - assert msg == 'Regex pattern "\'foo" does not match "\'bar".' + assert msg == '''Regex pattern did not match.\n Regex: "'foo"\n Input: "'bar"''' def test_match_failure_exact_string_message(self): message = "Oh here is a message with (42) numbers in parameters" @@ -226,9 +228,10 @@ def test_match_failure_exact_string_message(self): raise AssertionError(message) (msg,) = excinfo.value.args assert msg == ( - "Regex pattern 'Oh here is a message with (42) numbers in " - "parameters' does not match 'Oh here is a message with (42) " - "numbers in parameters'. Did you mean to `re.escape()` the regex?" + "Regex pattern did not match.\n" + " Regex: 'Oh here is a message with (42) numbers in parameters'\n" + " Input: 'Oh here is a message with (42) numbers in parameters'\n" + " Did you mean to `re.escape()` the regex?" ) def test_raises_match_wrong_type(self):