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