Add `pytest_markeval_namespace` hook.
Add a new hook , `pytest_markeval_namespace` which should return a dictionary. This dictionary will be used to augment the "global" variables available to evaluate skipif/xfail/xpass markers. Pseudo example ``conftest.py``: .. code-block:: python def pytest_markeval_namespace(): return {"color": "red"} ``test_func.py``: .. code-block:: python @pytest.mark.skipif("color == 'blue'", reason="Color is not red") def test_func(): assert False
This commit is contained in:
parent
902739cfc3
commit
b16c091253
|
@ -0,0 +1,19 @@
|
|||
A new hook was added, `pytest_markeval_namespace` which should return a dictionary.
|
||||
This dictionary will be used to augment the "global" variables available to evaluate skipif/xfail/xpass markers.
|
||||
|
||||
Pseudo example
|
||||
|
||||
``conftest.py``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return {"color": "red"}
|
||||
|
||||
``test_func.py``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif("color == 'blue'", reason="Color is not red")
|
||||
def test_func():
|
||||
assert False
|
|
@ -808,6 +808,27 @@ def pytest_warning_recorded(
|
|||
"""
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Hooks for influencing skipping
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
|
||||
def pytest_markeval_namespace(config: "Config") -> Dict[str, Any]:
|
||||
"""Called when constructing the globals dictionary used for
|
||||
evaluating string conditions in xfail/skipif markers.
|
||||
|
||||
This is useful when the condition for a marker requires
|
||||
objects that are expensive or impossible to obtain during
|
||||
collection time, which is required by normal boolean
|
||||
conditions.
|
||||
|
||||
.. versionadded:: 6.2
|
||||
|
||||
:param _pytest.config.Config config: The pytest config object.
|
||||
:returns: A dictionary of additional globals to add.
|
||||
"""
|
||||
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# error handling and internal debugging hooks
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from collections.abc import Mapping
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
@ -98,6 +99,16 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
|
|||
"platform": platform,
|
||||
"config": item.config,
|
||||
}
|
||||
for dictionary in reversed(
|
||||
item.ihook.pytest_markeval_namespace(config=item.config)
|
||||
):
|
||||
if not isinstance(dictionary, Mapping):
|
||||
raise ValueError(
|
||||
"pytest_markeval_namespace() needs to return a dict, got {!r}".format(
|
||||
dictionary
|
||||
)
|
||||
)
|
||||
globals_.update(dictionary)
|
||||
if hasattr(item, "obj"):
|
||||
globals_.update(item.obj.__globals__) # type: ignore[attr-defined]
|
||||
try:
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
from _pytest.pytester import Pytester
|
||||
|
@ -155,6 +156,136 @@ class TestEvaluation:
|
|||
assert skipped
|
||||
assert skipped.reason == "condition: config._hackxyz"
|
||||
|
||||
def test_skipif_markeval_namespace(self, pytester: Pytester) -> None:
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return {"color": "green"}
|
||||
"""
|
||||
)
|
||||
p = pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skipif("color == 'green'")
|
||||
def test_1():
|
||||
assert True
|
||||
|
||||
@pytest.mark.skipif("color == 'red'")
|
||||
def test_2():
|
||||
assert True
|
||||
"""
|
||||
)
|
||||
res = pytester.runpytest(p)
|
||||
assert res.ret == 0
|
||||
res.stdout.fnmatch_lines(["*1 skipped*"])
|
||||
res.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
def test_skipif_markeval_namespace_multiple(self, pytester: Pytester) -> None:
|
||||
"""Keys defined by ``pytest_markeval_namespace()`` in nested plugins override top-level ones."""
|
||||
root = pytester.mkdir("root")
|
||||
root.joinpath("__init__.py").touch()
|
||||
root.joinpath("conftest.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return {"arg": "root"}
|
||||
"""
|
||||
)
|
||||
)
|
||||
root.joinpath("test_root.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skipif("arg == 'root'")
|
||||
def test_root():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
)
|
||||
foo = root.joinpath("foo")
|
||||
foo.mkdir()
|
||||
foo.joinpath("__init__.py").touch()
|
||||
foo.joinpath("conftest.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return {"arg": "foo"}
|
||||
"""
|
||||
)
|
||||
)
|
||||
foo.joinpath("test_foo.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skipif("arg == 'foo'")
|
||||
def test_foo():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
)
|
||||
bar = root.joinpath("bar")
|
||||
bar.mkdir()
|
||||
bar.joinpath("__init__.py").touch()
|
||||
bar.joinpath("conftest.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return {"arg": "bar"}
|
||||
"""
|
||||
)
|
||||
)
|
||||
bar.joinpath("test_bar.py").write_text(
|
||||
textwrap.dedent(
|
||||
"""\
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skipif("arg == 'bar'")
|
||||
def test_bar():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
reprec = pytester.inline_run("-vs", "--capture=no")
|
||||
reprec.assertoutcome(skipped=3)
|
||||
|
||||
def test_skipif_markeval_namespace_ValueError(self, pytester: Pytester) -> None:
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return True
|
||||
"""
|
||||
)
|
||||
p = pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.skipif("color == 'green'")
|
||||
def test_1():
|
||||
assert True
|
||||
"""
|
||||
)
|
||||
res = pytester.runpytest(p)
|
||||
assert res.ret == 1
|
||||
res.stdout.fnmatch_lines(
|
||||
[
|
||||
"*ValueError: pytest_markeval_namespace() needs to return a dict, got True*"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class TestXFail:
|
||||
@pytest.mark.parametrize("strict", [True, False])
|
||||
|
@ -577,6 +708,33 @@ class TestXFail:
|
|||
result.stdout.fnmatch_lines(["*1 failed*" if strict else "*1 xpassed*"])
|
||||
assert result.ret == (1 if strict else 0)
|
||||
|
||||
def test_xfail_markeval_namespace(self, pytester: Pytester) -> None:
|
||||
pytester.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_markeval_namespace():
|
||||
return {"color": "green"}
|
||||
"""
|
||||
)
|
||||
p = pytester.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
@pytest.mark.xfail("color == 'green'")
|
||||
def test_1():
|
||||
assert False
|
||||
|
||||
@pytest.mark.xfail("color == 'red'")
|
||||
def test_2():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
res = pytester.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, pytester: Pytester) -> None:
|
||||
|
|
Loading…
Reference in New Issue