Skip to content

Commit a38b20d

Browse files
raises: is_fully_escaped does not handle consecutive backslashes correctly (#14393)
1 parent d4fde45 commit a38b20d

3 files changed

Lines changed: 21 additions & 3 deletions

File tree

changelog/14392.bugfix.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Fixed a bug in :func:`pytest.raises(match=...) <pytest.raises>` "fully escaped" detection, causing the regex diff display to be shown in some instances when the raw string diff display should be shown instead.

src/_pytest/raises.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -345,9 +345,10 @@ def _check_raw_type(
345345
def is_fully_escaped(s: str) -> bool:
346346
# we know we won't compile with re.VERBOSE, so whitespace doesn't need to be escaped
347347
metacharacters = "{}()+.*?^$[]|"
348-
return not any(
349-
c in metacharacters and (i == 0 or s[i - 1] != "\\") for (i, c) in enumerate(s)
350-
)
348+
# Strip all escape sequences (backslash + any char), then check if any
349+
# metacharacter remains unescaped in the resulting string.
350+
stripped = re.sub(r"\\.", "", s)
351+
return not any(c in metacharacters for c in stripped)
351352

352353

353354
def unescape(s: str) -> str:

testing/python/raises.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,3 +444,19 @@ def test_pipe_is_treated_as_regex_metacharacter(self) -> None:
444444
assert not is_fully_escaped("foo|bar")
445445
assert is_fully_escaped(r"foo\|bar")
446446
assert unescape(r"foo\|bar") == "foo|bar"
447+
448+
def test_consecutive_backslashes_in_escape_check(self) -> None:
449+
"""Consecutive backslashes escape each other, leaving the metachar unescaped."""
450+
from _pytest.raises import is_fully_escaped
451+
452+
# r"\." -> one backslash escapes the dot -> fully escaped
453+
assert is_fully_escaped(r"\.")
454+
# r"\\." -> two backslashes: the first escapes the second, dot is unescaped
455+
assert not is_fully_escaped(r"\\.")
456+
# r"\\\." -> three backslashes: pair escapes pair, last escapes dot -> fully escaped
457+
assert is_fully_escaped(r"\\\.")
458+
# Same idea with pipe metachar
459+
# "\\\\|" is the string \\| (2 backslashes + pipe): even count, pipe is unescaped
460+
assert not is_fully_escaped("\\\\|")
461+
# r"\\\\|" is the string \\\\| (4 backslashes + pipe): even count, pipe is unescaped
462+
assert not is_fully_escaped(r"\\\\|")

0 commit comments

Comments
 (0)