Skip to content

Commit

Permalink
Allow augmenting the variables evaluating string conditions in xfail/…
Browse files Browse the repository at this point in the history
…skipif markers.
  • Loading branch information
s0undt3ch committed Aug 30, 2020
1 parent 91dbdb6 commit 6c7d015
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/_pytest/hookspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,18 @@ 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.
:param _pytest.config.Config config: The pytest config object.
:returns: A dictionary of additional globals to add.
"""


# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions src/_pytest/skipping.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import platform
import sys
import traceback
from collections.abc import Mapping
from typing import Generator
from typing import Optional
from typing import Tuple
Expand Down Expand Up @@ -101,6 +102,14 @@ def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool,
"platform": platform,
"config": item.config,
}
for dictionary in 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:
Expand Down
155 changes: 155 additions & 0 deletions testing/test_skipping.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
import textwrap

import pytest
from _pytest.pytester import Testdir
Expand Down Expand Up @@ -153,6 +154,133 @@ def test_func(self):
assert skipped
assert skipped.reason == "condition: config._hackxyz"

def test_skipif_markeval_namespace(self, testdir):
testdir.makeconftest(
"""
import pytest
def pytest_markeval_namespace():
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_markeval_namespace_multiple(self, testdir):
root = testdir.mkdir("root")
root.ensure("__init__.py")
root.join("conftest.py").write(
textwrap.dedent(
"""\
import pytest
def pytest_markeval_namespace():
return {"arg": "root"}
"""
)
)
root.join("test_root.py").write(
textwrap.dedent(
"""\
import pytest
@pytest.mark.skipif("arg == 'root'")
def test_root():
assert False
"""
)
)
foo = root.mkdir("foo")
foo.ensure("__init__.py")
foo.join("conftest.py").write(
textwrap.dedent(
"""\
import pytest
def pytest_markeval_namespace():
return {"arg": "foo"}
"""
)
)
foo.join("test_foo.py").write(
textwrap.dedent(
"""\
import pytest
@pytest.mark.skipif("arg == 'foo'")
def test_foo():
assert False
"""
)
)
bar = root.mkdir("bar")
bar.ensure("__init__.py")
bar.join("conftest.py").write(
textwrap.dedent(
"""\
import pytest
def pytest_markeval_namespace():
return {"arg": "bar"}
"""
)
)
bar.join("test_bar.py").write(
textwrap.dedent(
"""\
import pytest
@pytest.mark.skipif("arg == 'bar'")
def test_bar():
assert False
"""
)
)

reprec = testdir.inline_run("-vs", "--capture=no")
reprec.assertoutcome(skipped=3)

def test_skipif_markeval_namespace_ValueError(self, testdir):
testdir.makeconftest(
"""
import pytest
def pytest_markeval_namespace():
return True
"""
)
p = testdir.makepyfile(
"""
import pytest
@pytest.mark.skipif("color == 'green'")
def test_1():
assert True
"""
)
res = testdir.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])
Expand Down Expand Up @@ -569,6 +697,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_markeval_namespace(self, testdir):
testdir.makeconftest(
"""
import pytest
def pytest_markeval_namespace():
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):
Expand Down

0 comments on commit 6c7d015

Please sign in to comment.