From ca64d244beb4bd2bf78e4800d45a97213d683850 Mon Sep 17 00:00:00 2001 From: eternalrights <3147268827@qq.com> Date: Wed, 13 May 2026 15:07:20 +0800 Subject: [PATCH 1/3] fix mark expression scanner: search for backslash in string value, not entire input The backslash check in the string literal lexer was searching the entire input expression instead of only the current string value. This caused false rejections when an identifier containing a backslash appeared in the same expression as a string literal. Changed input.find("\\") to value.find("\\") and adjusted the column offset from backslash_pos + 1 to pos + backslash_pos + 1 so the error points to the correct position when a backslash is inside a string. Closes #14474 --- 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..3c54899281a --- /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 -- the backslash check was searching the entire input instead of only the string literal value. 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..51f70c9b1c2 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"}.__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"), ( From 463f3403606669caf880bdf627fdc62533c36e8b Mon Sep 17 00:00:00 2001 From: eternalrights <3147268827@qq.com> Date: Wed, 13 May 2026 15:33:34 +0800 Subject: [PATCH 2/3] fix test: include 'mark' and 'test\case' in matcher set The test_backslash_in_identifier_with_string_literal test was failing because the matcher function only recognized '\nfoo\n' as a valid identifier. When the expression contained 'mark(x="y")', the matcher returned False for 'mark', causing the entire 'and' expression to evaluate to False instead of True. --- testing/test_mark_expression.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testing/test_mark_expression.py b/testing/test_mark_expression.py index 51f70c9b1c2..3a606bac17c 100644 --- a/testing/test_mark_expression.py +++ b/testing/test_mark_expression.py @@ -93,7 +93,7 @@ def test_backslash_in_identifier_with_string_literal() -> None: current string literal value.""" def matcher(name: str, /, **kwargs: str | int | bool | None) -> bool: - return {r"\nfoo\n"}.__contains__(name) + 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) From 1340174f98f9a1dafa474d2969d5ac42b0774398 Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Wed, 13 May 2026 18:28:16 +0300 Subject: [PATCH 3/3] Update changelog/14474.bugfix.rst --- changelog/14474.bugfix.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog/14474.bugfix.rst b/changelog/14474.bugfix.rst index 3c54899281a..333d4d34d9a 100644 --- a/changelog/14474.bugfix.rst +++ b/changelog/14474.bugfix.rst @@ -1 +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 -- the backslash check was searching the entire input instead of only the string literal value. +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.