diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index d4aaba1ec23..dfaf5046813 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -808,6 +808,16 @@ def pytest_warning_recorded( """ +# ------------------------------------------------------------------------- +# Hooks for influencing skipping +# ------------------------------------------------------------------------- +def pytest_skipif_additional_globals() -> Dict[str, Any]: + """Called when constructing the globals dictionary used for + evaluating skipif contditions. + Return a dictionary of additional globals to add. + """ + + # ------------------------------------------------------------------------- # error handling and internal debugging hooks # ------------------------------------------------------------------------- diff --git a/src/_pytest/skipping.py b/src/_pytest/skipping.py index c5b4ff39e85..0fd02fda82d 100644 --- a/src/_pytest/skipping.py +++ b/src/_pytest/skipping.py @@ -101,6 +101,10 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, "platform": platform, "config": item.config, } + for dictionary in item.config.hook.pytest_skipif_additional_globals(): + if not isinstance(dictionary, dict) or not dictionary: + continue + globals_.update(dictionary) if hasattr(item, "obj"): globals_.update(item.obj.__globals__) # type: ignore[attr-defined] try: diff --git a/testing/test_skipping.py b/testing/test_skipping.py index b32d2267d21..f0eabb51d96 100644 --- a/testing/test_skipping.py +++ b/testing/test_skipping.py @@ -153,6 +153,59 @@ def test_func(self): assert skipped assert skipped.reason == "condition: config._hackxyz" + def test_skipif_additional_globals(self, testdir): + testdir.makeconftest( + """ + import pytest + + def pytest_skipif_additional_globals(): + return {"color": "green"} + """ + ) + p = testdir.makepyfile( + """ + import pytest + + @pytest.mark.skipif("color == 'green'") + def test_1(): + assert True + + @pytest.mark.skipif("color == 'red'") + def test_2(): + assert True + """ + ) + res = testdir.runpytest(p) + assert res.ret == 0 + res.stdout.fnmatch_lines(["*1 skipped*"]) + res.stdout.fnmatch_lines(["*1 passed*"]) + + def test_skipif_empty_additional_globals(self, testdir): + testdir.makeconftest( + """ + import pytest + + def pytest_skipif_additional_globals(): + return {} + """ + ) + p = testdir.makepyfile( + """ + import pytest + + @pytest.mark.skipif(True, reason="because") + def test_1(): + assert True + + def test_2(): + assert True + """ + ) + res = testdir.runpytest(p) + assert res.ret == 0 + res.stdout.fnmatch_lines(["*1 skipped*"]) + res.stdout.fnmatch_lines(["*1 passed*"]) + class TestXFail: @pytest.mark.parametrize("strict", [True, False]) @@ -569,6 +622,33 @@ def test_foo(): result.stdout.fnmatch_lines(["*1 failed*" if strict else "*1 xpassed*"]) assert result.ret == (1 if strict else 0) + def test_skipif_additional_globals(self, testdir): + testdir.makeconftest( + """ + import pytest + + def pytest_skipif_additional_globals(): + return {"color": "green"} + """ + ) + p = testdir.makepyfile( + """ + import pytest + + @pytest.mark.xfail("color == 'green'") + def test_1(): + assert False + + @pytest.mark.xfail("color == 'red'") + def test_2(): + assert False + """ + ) + res = testdir.runpytest(p) + assert res.ret == 1 + res.stdout.fnmatch_lines(["*1 failed*"]) + res.stdout.fnmatch_lines(["*1 xfailed*"]) + class TestXFailwithSetupTeardown: def test_failing_setup_issue9(self, testdir):