Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion src/_pytest/compat.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
python version compatibility code
"""
import ast
import enum
import functools
import inspect
Expand Down Expand Up @@ -33,7 +34,7 @@


if TYPE_CHECKING:
from typing import Type
from typing import Type # noqa: F401 (used in type string)
from typing_extensions import Final


Expand All @@ -60,6 +61,22 @@ class NotSetType(enum.Enum):
import importlib_metadata # noqa: F401


_LITERAL_IDENTS = {
"True": True,
"False": False,
"None": None,
}


def _ident_to_name(name: str) -> ast.expr:
if name in _LITERAL_IDENTS:
literal = _LITERAL_IDENTS[name]
if sys.version_info >= (3, 8):
return ast.Constant(value=literal)
return ast.NameConstant(value=literal)
return ast.Name(id=name, ctx=ast.Load())


def _format_args(func: Callable[..., Any]) -> str:
return str(signature(func))

Expand Down
6 changes: 3 additions & 3 deletions src/_pytest/mark/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

import attr

from _pytest.compat import TYPE_CHECKING
from _pytest.compat import TYPE_CHECKING, _ident_to_name

if TYPE_CHECKING:
from typing import NoReturn
Expand Down Expand Up @@ -129,7 +129,7 @@ def reject(self, expected: Sequence[TokenType]) -> "NoReturn":

def expression(s: Scanner) -> ast.Expression:
if s.accept(TokenType.EOF):
ret = ast.NameConstant(False) # type: ast.expr
ret = _ident_to_name("False")
else:
ret = expr(s)
s.accept(TokenType.EOF, reject=True)
Expand Down Expand Up @@ -161,7 +161,7 @@ def not_expr(s: Scanner) -> ast.expr:
return ret
ident = s.accept(TokenType.IDENT)
if ident:
return ast.Name(ident.value, ast.Load())
return _ident_to_name(ident.value)
s.reject((TokenType.NOT, TokenType.LPAREN, TokenType.IDENT))


Expand Down
13 changes: 11 additions & 2 deletions testing/test_mark_expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,6 @@ def test_syntax_errors(expr: str, column: int, message: str) -> None:
"not[and]or",
"1234+5678",
"123.232",
"True",
"False",
"if",
"else",
"while",
Expand All @@ -139,6 +137,17 @@ def test_valid_idents(ident: str) -> None:
assert evaluate(ident, {ident: True}.__getitem__)


@pytest.mark.parametrize(
("expr", "expected"),
(("True", True), ("False", False), ("None", None)),
)
def test_literal_idents(expr: str, expected: object) -> None:
def _unexpected_lookup(ident: str) -> bool:
pytest.fail("unexpected lookup for {!r}".format(ident))

assert evaluate(expr, _unexpected_lookup) is expected


@pytest.mark.parametrize(
"ident",
(
Expand Down