From 439f3efb7e0049d7c4cc1d58a592d837988f72b8 Mon Sep 17 00:00:00 2001 From: EternalRights <162705204+EternalRights@users.noreply.github.com> Date: Wed, 13 May 2026 23:42:37 +0800 Subject: [PATCH] mark/expression: fix scanner - search for backslash in string value, not entire input (#14475) The backslash check in the string literal lexer was searching the entire input expression (input.find("\")) instead of only the current string value (value.find("\")). This caused false rejections when an identifier containing a backslash appeared in the same expression as a string literal. For example, `pytest -k 'test\nfoo\n and mark(x="y")'` would fail with "escaping not supported" even though the backslash is in the identifier, not the string. Closes #14474 (cherry picked from commit 984cabfaccf8aa69fe49097ed3d07bd60d05f240) --- changelog/14474.bugfix.rst | 1 + src/_pytest/mark/expression.py | 4 ++-- testing/test_mark_expression.py | 14 ++++++++++++++ 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 changelog/14474.bugfix.rst diff --git a/changelog/14474.bugfix.rst b/changelog/14474.bugfix.rst new file mode 100644 index 00000000000..333d4d34d9a --- /dev/null +++ b/changelog/14474.bugfix.rst @@ -0,0 +1 @@ +Fixed a regression where ``-k`` and ``-m`` expressions containing both backslash characters in identifiers and string literal arguments would incorrectly raise a ``SyntaxError`` about escaping. diff --git a/src/_pytest/mark/expression.py b/src/_pytest/mark/expression.py index 3bdbd03c2b5..4b4a68d8a74 100644 --- a/src/_pytest/mark/expression.py +++ b/src/_pytest/mark/expression.py @@ -102,10 +102,10 @@ def lex(self, input: str) -> Iterator[Token]: (FILE_NAME, 1, pos + 1, input), ) value = input[pos : end_quote_pos + 1] - if (backslash_pos := input.find("\\")) != -1: + if (backslash_pos := value.find("\\")) != -1: raise SyntaxError( r'escaping with "\" not supported in marker expression', - (FILE_NAME, 1, backslash_pos + 1, input), + (FILE_NAME, 1, pos + backslash_pos + 1, input), ) yield Token(TokenType.STRING, value, pos) pos += len(value) diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 1e3c769347c..3a606bac17c 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -86,6 +86,20 @@ def matcher(name: str, /, **kwargs: str | int | bool | None) -> bool: evaluate("\nfoo\n", matcher) +def test_backslash_in_identifier_with_string_literal() -> None: + r"""Backslashes in identifiers should not cause false rejections when the + expression also contains string literals. Regression test for a bug where + the scanner searched the entire input for backslashes instead of only the + current string literal value.""" + + def matcher(name: str, /, **kwargs: str | int | bool | None) -> bool: + return {r"\nfoo\n", r"test\case", "mark"}.__contains__(name) + + assert evaluate(r'\nfoo\n and mark(x="y")', matcher) + assert evaluate(r'mark(x="y") and \nfoo\n', matcher) + assert evaluate(r'test\case and mark(x="y")', matcher) + + @pytest.mark.parametrize( ("expr", "column", "message"), (