skipping: refactor skipif/xfail mark evaluation

Previously, skipif/xfail marks were evaluated using a `MarkEvaluator`
class. I found this class very difficult to understand.

Instead of `MarkEvaluator`, rewrite using straight functions which are
hopefully easier to follow.

I tried to keep the semantics exactly as before, except improving a few
error messages.
This commit is contained in:
Ran Benita
2020-06-19 13:33:55 +03:00
parent 6072c9950d
commit 3e6fe92b7e
2 changed files with 251 additions and 233 deletions

View File

@@ -2,68 +2,74 @@ import sys
import pytest
from _pytest.runner import runtestprotocol
from _pytest.skipping import MarkEvaluator
from _pytest.skipping import evaluate_skip_marks
from _pytest.skipping import evaluate_xfail_marks
from _pytest.skipping import pytest_runtest_setup
class TestEvaluator:
class TestEvaluation:
def test_no_marker(self, testdir):
item = testdir.getitem("def test_func(): pass")
evalskipif = MarkEvaluator(item, "skipif")
assert not evalskipif
assert not evalskipif.istrue()
skipped = evaluate_skip_marks(item)
assert not skipped
def test_marked_no_args(self, testdir):
def test_marked_xfail_no_args(self, testdir):
item = testdir.getitem(
"""
import pytest
@pytest.mark.xyz
@pytest.mark.xfail
def test_func():
pass
"""
)
ev = MarkEvaluator(item, "xyz")
assert ev
assert ev.istrue()
expl = ev.getexplanation()
assert expl == ""
assert not ev.get("run", False)
xfailed = evaluate_xfail_marks(item)
assert xfailed
assert xfailed.reason == ""
assert xfailed.run
def test_marked_skipif_no_args(self, testdir):
item = testdir.getitem(
"""
import pytest
@pytest.mark.skipif
def test_func():
pass
"""
)
skipped = evaluate_skip_marks(item)
assert skipped
assert skipped.reason == ""
def test_marked_one_arg(self, testdir):
item = testdir.getitem(
"""
import pytest
@pytest.mark.xyz("hasattr(os, 'sep')")
@pytest.mark.skipif("hasattr(os, 'sep')")
def test_func():
pass
"""
)
ev = MarkEvaluator(item, "xyz")
assert ev
assert ev.istrue()
expl = ev.getexplanation()
assert expl == "condition: hasattr(os, 'sep')"
skipped = evaluate_skip_marks(item)
assert skipped
assert skipped.reason == "condition: hasattr(os, 'sep')"
def test_marked_one_arg_with_reason(self, testdir):
item = testdir.getitem(
"""
import pytest
@pytest.mark.xyz("hasattr(os, 'sep')", attr=2, reason="hello world")
@pytest.mark.skipif("hasattr(os, 'sep')", attr=2, reason="hello world")
def test_func():
pass
"""
)
ev = MarkEvaluator(item, "xyz")
assert ev
assert ev.istrue()
expl = ev.getexplanation()
assert expl == "hello world"
assert ev.get("attr") == 2
skipped = evaluate_skip_marks(item)
assert skipped
assert skipped.reason == "hello world"
def test_marked_one_arg_twice(self, testdir):
lines = [
"""@pytest.mark.skipif("not hasattr(os, 'murks')")""",
"""@pytest.mark.skipif("hasattr(os, 'murks')")""",
"""@pytest.mark.skipif(condition="hasattr(os, 'murks')")""",
]
for i in range(0, 2):
item = testdir.getitem(
@@ -76,11 +82,9 @@ class TestEvaluator:
"""
% (lines[i], lines[(i + 1) % 2])
)
ev = MarkEvaluator(item, "skipif")
assert ev
assert ev.istrue()
expl = ev.getexplanation()
assert expl == "condition: not hasattr(os, 'murks')"
skipped = evaluate_skip_marks(item)
assert skipped
assert skipped.reason == "condition: not hasattr(os, 'murks')"
def test_marked_one_arg_twice2(self, testdir):
item = testdir.getitem(
@@ -92,13 +96,11 @@ class TestEvaluator:
pass
"""
)
ev = MarkEvaluator(item, "skipif")
assert ev
assert ev.istrue()
expl = ev.getexplanation()
assert expl == "condition: not hasattr(os, 'murks')"
skipped = evaluate_skip_marks(item)
assert skipped
assert skipped.reason == "condition: not hasattr(os, 'murks')"
def test_marked_skip_with_not_string(self, testdir) -> None:
def test_marked_skipif_with_boolean_without_reason(self, testdir) -> None:
item = testdir.getitem(
"""
import pytest
@@ -107,14 +109,34 @@ class TestEvaluator:
pass
"""
)
ev = MarkEvaluator(item, "skipif")
exc = pytest.raises(pytest.fail.Exception, ev.istrue)
assert exc.value.msg is not None
with pytest.raises(pytest.fail.Exception) as excinfo:
evaluate_skip_marks(item)
assert excinfo.value.msg is not None
assert (
"""Failed: you need to specify reason=STRING when using booleans as conditions."""
in exc.value.msg
"""Error evaluating 'skipif': you need to specify reason=STRING when using booleans as conditions."""
in excinfo.value.msg
)
def test_marked_skipif_with_invalid_boolean(self, testdir) -> None:
item = testdir.getitem(
"""
import pytest
class InvalidBool:
def __bool__(self):
raise TypeError("INVALID")
@pytest.mark.skipif(InvalidBool(), reason="xxx")
def test_func():
pass
"""
)
with pytest.raises(pytest.fail.Exception) as excinfo:
evaluate_skip_marks(item)
assert excinfo.value.msg is not None
assert "Error evaluating 'skipif' condition as a boolean" in excinfo.value.msg
assert "INVALID" in excinfo.value.msg
def test_skipif_class(self, testdir):
(item,) = testdir.getitems(
"""
@@ -126,10 +148,9 @@ class TestEvaluator:
"""
)
item.config._hackxyz = 3
ev = MarkEvaluator(item, "skipif")
assert ev.istrue()
expl = ev.getexplanation()
assert expl == "condition: config._hackxyz"
skipped = evaluate_skip_marks(item)
assert skipped
assert skipped.reason == "condition: config._hackxyz"
class TestXFail:
@@ -895,10 +916,10 @@ def test_errors_in_xfail_skip_expressions(testdir) -> None:
result.stdout.fnmatch_lines(
[
"*ERROR*test_nameerror*",
"*evaluating*skipif*expression*",
"*evaluating*skipif*condition*",
"*asd*",
"*ERROR*test_syntax*",
"*evaluating*xfail*expression*",
"*evaluating*xfail*condition*",
" syntax error",
markline,
"SyntaxError: invalid syntax",
@@ -924,25 +945,12 @@ def test_xfail_skipif_with_globals(testdir):
result.stdout.fnmatch_lines(["*SKIP*x == 3*", "*XFAIL*test_boolean*", "*x == 3*"])
def test_direct_gives_error(testdir):
testdir.makepyfile(
"""
import pytest
@pytest.mark.skipif(True)
def test_skip1():
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 error*"])
def test_default_markers(testdir):
result = testdir.runpytest("--markers")
result.stdout.fnmatch_lines(
[
"*skipif(*condition)*skip*",
"*xfail(*condition, reason=None, run=True, raises=None, strict=False)*expected failure*",
"*skipif(condition, ..., [*], reason=...)*skip*",
"*xfail(condition, ..., [*], reason=..., run=True, raises=None, strict=xfail_strict)*expected failure*",
]
)
@@ -1137,7 +1145,9 @@ def test_mark_xfail_item(testdir):
class MyItem(pytest.Item):
nodeid = 'foo'
def setup(self):
marker = pytest.mark.xfail(True, reason="Expected failure")
marker = pytest.mark.xfail("1 == 2", reason="Expected failure - false")
self.add_marker(marker)
marker = pytest.mark.xfail(True, reason="Expected failure - true")
self.add_marker(marker)
def runtest(self):
assert False