Disable caching when evaluating expressions in marks (#7373)
This commit is contained in:
parent
b6fd89ef31
commit
a67c553beb
|
@ -0,0 +1,2 @@
|
||||||
|
Fix possibly incorrect evaluation of string expressions passed to ``pytest.mark.skipif`` and ``pytest.mark.xfail``,
|
||||||
|
in rare circumstances where the exact same string is used but refers to different global values.
|
|
@ -10,25 +10,14 @@ from typing import Optional
|
||||||
from ..outcomes import fail
|
from ..outcomes import fail
|
||||||
from ..outcomes import TEST_OUTCOME
|
from ..outcomes import TEST_OUTCOME
|
||||||
from .structures import Mark
|
from .structures import Mark
|
||||||
from _pytest.config import Config
|
|
||||||
from _pytest.nodes import Item
|
from _pytest.nodes import Item
|
||||||
from _pytest.store import StoreKey
|
|
||||||
|
|
||||||
|
|
||||||
evalcache_key = StoreKey[Dict[str, Any]]()
|
def compiled_eval(expr: str, d: Dict[str, object]) -> Any:
|
||||||
|
import _pytest._code
|
||||||
|
|
||||||
|
exprcode = _pytest._code.compile(expr, mode="eval")
|
||||||
def cached_eval(config: Config, expr: str, d: Dict[str, object]) -> Any:
|
return eval(exprcode, d)
|
||||||
default = {} # type: Dict[str, object]
|
|
||||||
evalcache = config._store.setdefault(evalcache_key, default)
|
|
||||||
try:
|
|
||||||
return evalcache[expr]
|
|
||||||
except KeyError:
|
|
||||||
import _pytest._code
|
|
||||||
|
|
||||||
exprcode = _pytest._code.compile(expr, mode="eval")
|
|
||||||
evalcache[expr] = x = eval(exprcode, d)
|
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
class MarkEvaluator:
|
class MarkEvaluator:
|
||||||
|
@ -98,7 +87,7 @@ class MarkEvaluator:
|
||||||
self.expr = expr
|
self.expr = expr
|
||||||
if isinstance(expr, str):
|
if isinstance(expr, str):
|
||||||
d = self._getglobals()
|
d = self._getglobals()
|
||||||
result = cached_eval(self.item.config, expr, d)
|
result = compiled_eval(expr, d)
|
||||||
else:
|
else:
|
||||||
if "reason" not in mark.kwargs:
|
if "reason" not in mark.kwargs:
|
||||||
# XXX better be checked at collection time
|
# XXX better be checked at collection time
|
||||||
|
|
|
@ -706,6 +706,36 @@ class TestFunctional:
|
||||||
reprec = testdir.inline_run()
|
reprec = testdir.inline_run()
|
||||||
reprec.assertoutcome(skipped=1)
|
reprec.assertoutcome(skipped=1)
|
||||||
|
|
||||||
|
def test_reevaluate_dynamic_expr(self, testdir):
|
||||||
|
"""#7360"""
|
||||||
|
py_file1 = testdir.makepyfile(
|
||||||
|
test_reevaluate_dynamic_expr1="""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
skip = True
|
||||||
|
|
||||||
|
@pytest.mark.skipif("skip")
|
||||||
|
def test_should_skip():
|
||||||
|
assert True
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
py_file2 = testdir.makepyfile(
|
||||||
|
test_reevaluate_dynamic_expr2="""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
skip = False
|
||||||
|
|
||||||
|
@pytest.mark.skipif("skip")
|
||||||
|
def test_should_not_skip():
|
||||||
|
assert True
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
file_name1 = os.path.basename(py_file1.strpath)
|
||||||
|
file_name2 = os.path.basename(py_file2.strpath)
|
||||||
|
reprec = testdir.inline_run(file_name1, file_name2)
|
||||||
|
reprec.assertoutcome(passed=1, skipped=1)
|
||||||
|
|
||||||
|
|
||||||
class TestKeywordSelection:
|
class TestKeywordSelection:
|
||||||
def test_select_simple(self, testdir):
|
def test_select_simple(self, testdir):
|
||||||
|
|
Loading…
Reference in New Issue