Merge pull request #7388 from bluetech/mark-evaluate
skipping: refactor mark evaluation
This commit is contained in:
commit
99d34ba029
|
@ -1,124 +0,0 @@
|
|||
import os
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Optional
|
||||
|
||||
from ..outcomes import fail
|
||||
from ..outcomes import TEST_OUTCOME
|
||||
from .structures import Mark
|
||||
from _pytest.nodes import Item
|
||||
|
||||
|
||||
def compiled_eval(expr: str, d: Dict[str, object]) -> Any:
|
||||
import _pytest._code
|
||||
|
||||
exprcode = _pytest._code.compile(expr, mode="eval")
|
||||
return eval(exprcode, d)
|
||||
|
||||
|
||||
class MarkEvaluator:
|
||||
def __init__(self, item: Item, name: str) -> None:
|
||||
self.item = item
|
||||
self._marks = None # type: Optional[List[Mark]]
|
||||
self._mark = None # type: Optional[Mark]
|
||||
self._mark_name = name
|
||||
|
||||
def __bool__(self) -> bool:
|
||||
# don't cache here to prevent staleness
|
||||
return bool(self._get_marks())
|
||||
|
||||
def wasvalid(self) -> bool:
|
||||
return not hasattr(self, "exc")
|
||||
|
||||
def _get_marks(self) -> List[Mark]:
|
||||
return list(self.item.iter_markers(name=self._mark_name))
|
||||
|
||||
def invalidraise(self, exc) -> Optional[bool]:
|
||||
raises = self.get("raises")
|
||||
if not raises:
|
||||
return None
|
||||
return not isinstance(exc, raises)
|
||||
|
||||
def istrue(self) -> bool:
|
||||
try:
|
||||
return self._istrue()
|
||||
except TEST_OUTCOME:
|
||||
self.exc = sys.exc_info()
|
||||
if isinstance(self.exc[1], SyntaxError):
|
||||
# TODO: Investigate why SyntaxError.offset is Optional, and if it can be None here.
|
||||
assert self.exc[1].offset is not None
|
||||
msg = [" " * (self.exc[1].offset + 4) + "^"]
|
||||
msg.append("SyntaxError: invalid syntax")
|
||||
else:
|
||||
msg = traceback.format_exception_only(*self.exc[:2])
|
||||
fail(
|
||||
"Error evaluating %r expression\n"
|
||||
" %s\n"
|
||||
"%s" % (self._mark_name, self.expr, "\n".join(msg)),
|
||||
pytrace=False,
|
||||
)
|
||||
|
||||
def _getglobals(self) -> Dict[str, object]:
|
||||
d = {"os": os, "sys": sys, "platform": platform, "config": self.item.config}
|
||||
if hasattr(self.item, "obj"):
|
||||
d.update(self.item.obj.__globals__) # type: ignore[attr-defined] # noqa: F821
|
||||
return d
|
||||
|
||||
def _istrue(self) -> bool:
|
||||
if hasattr(self, "result"):
|
||||
result = getattr(self, "result") # type: bool
|
||||
return result
|
||||
self._marks = self._get_marks()
|
||||
|
||||
if self._marks:
|
||||
self.result = False
|
||||
for mark in self._marks:
|
||||
self._mark = mark
|
||||
if "condition" not in mark.kwargs:
|
||||
args = mark.args
|
||||
else:
|
||||
args = (mark.kwargs["condition"],)
|
||||
|
||||
for expr in args:
|
||||
self.expr = expr
|
||||
if isinstance(expr, str):
|
||||
d = self._getglobals()
|
||||
result = compiled_eval(expr, d)
|
||||
else:
|
||||
if "reason" not in mark.kwargs:
|
||||
# XXX better be checked at collection time
|
||||
msg = (
|
||||
"you need to specify reason=STRING "
|
||||
"when using booleans as conditions."
|
||||
)
|
||||
fail(msg)
|
||||
result = bool(expr)
|
||||
if result:
|
||||
self.result = True
|
||||
self.reason = mark.kwargs.get("reason", None)
|
||||
self.expr = expr
|
||||
return self.result
|
||||
|
||||
if not args:
|
||||
self.result = True
|
||||
self.reason = mark.kwargs.get("reason", None)
|
||||
return self.result
|
||||
return False
|
||||
|
||||
def get(self, attr, default=None):
|
||||
if self._mark is None:
|
||||
return default
|
||||
return self._mark.kwargs.get(attr, default)
|
||||
|
||||
def getexplanation(self):
|
||||
expl = getattr(self, "reason", None) or self.get("reason", None)
|
||||
if not expl:
|
||||
if not hasattr(self, "expr"):
|
||||
return ""
|
||||
else:
|
||||
return "condition: " + str(self.expr)
|
||||
return expl
|
|
@ -1,24 +1,30 @@
|
|||
""" support for skip/xfail functions and markers. """
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
from typing import Generator
|
||||
from typing import Optional
|
||||
from typing import Tuple
|
||||
|
||||
import attr
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.compat import TYPE_CHECKING
|
||||
from _pytest.config import Config
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config.argparsing import Parser
|
||||
from _pytest.mark.evaluate import MarkEvaluator
|
||||
from _pytest.mark.structures import Mark
|
||||
from _pytest.nodes import Item
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import xfail
|
||||
from _pytest.python import Function
|
||||
from _pytest.reports import BaseReport
|
||||
from _pytest.runner import CallInfo
|
||||
from _pytest.store import StoreKey
|
||||
|
||||
|
||||
skipped_by_mark_key = StoreKey[bool]()
|
||||
evalxfail_key = StoreKey[MarkEvaluator]()
|
||||
unexpectedsuccess_key = StoreKey[str]()
|
||||
if TYPE_CHECKING:
|
||||
from typing import Type
|
||||
|
||||
|
||||
def pytest_addoption(parser: Parser) -> None:
|
||||
|
@ -62,81 +68,200 @@ def pytest_configure(config: Config) -> None:
|
|||
)
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"skipif(condition): skip the given test function if eval(condition) "
|
||||
"results in a True value. Evaluation happens within the "
|
||||
"module global context. Example: skipif('sys.platform == \"win32\"') "
|
||||
"skips the test if we are on the win32 platform. see "
|
||||
"https://docs.pytest.org/en/latest/skipping.html",
|
||||
"skipif(condition, ..., *, reason=...): "
|
||||
"skip the given test function if any of the conditions evaluate to True. "
|
||||
"Example: skipif(sys.platform == 'win32') skips the test if we are on the win32 platform. "
|
||||
"See https://docs.pytest.org/en/stable/reference.html#pytest-mark-skipif",
|
||||
)
|
||||
config.addinivalue_line(
|
||||
"markers",
|
||||
"xfail(condition, reason=None, run=True, raises=None, strict=False): "
|
||||
"mark the test function as an expected failure if eval(condition) "
|
||||
"has a True value. Optionally specify a reason for better reporting "
|
||||
"xfail(condition, ..., *, reason=..., run=True, raises=None, strict=xfail_strict): "
|
||||
"mark the test function as an expected failure if any of the conditions "
|
||||
"evaluate to True. Optionally specify a reason for better reporting "
|
||||
"and run=False if you don't even want to execute the test function. "
|
||||
"If only specific exception(s) are expected, you can list them in "
|
||||
"raises, and if the test fails in other ways, it will be reported as "
|
||||
"a true failure. See https://docs.pytest.org/en/latest/skipping.html",
|
||||
"a true failure. See https://docs.pytest.org/en/stable/reference.html#pytest-mark-xfail",
|
||||
)
|
||||
|
||||
|
||||
def evaluate_condition(item: Item, mark: Mark, condition: object) -> Tuple[bool, str]:
|
||||
"""Evaluate a single skipif/xfail condition.
|
||||
|
||||
If an old-style string condition is given, it is eval()'d, otherwise the
|
||||
condition is bool()'d. If this fails, an appropriately formatted pytest.fail
|
||||
is raised.
|
||||
|
||||
Returns (result, reason). The reason is only relevant if the result is True.
|
||||
"""
|
||||
# String condition.
|
||||
if isinstance(condition, str):
|
||||
globals_ = {
|
||||
"os": os,
|
||||
"sys": sys,
|
||||
"platform": platform,
|
||||
"config": item.config,
|
||||
}
|
||||
if hasattr(item, "obj"):
|
||||
globals_.update(item.obj.__globals__) # type: ignore[attr-defined]
|
||||
try:
|
||||
condition_code = _pytest._code.compile(condition, mode="eval")
|
||||
result = eval(condition_code, globals_)
|
||||
except SyntaxError as exc:
|
||||
msglines = [
|
||||
"Error evaluating %r condition" % mark.name,
|
||||
" " + condition,
|
||||
" " + " " * (exc.offset or 0) + "^",
|
||||
"SyntaxError: invalid syntax",
|
||||
]
|
||||
fail("\n".join(msglines), pytrace=False)
|
||||
except Exception as exc:
|
||||
msglines = [
|
||||
"Error evaluating %r condition" % mark.name,
|
||||
" " + condition,
|
||||
*traceback.format_exception_only(type(exc), exc),
|
||||
]
|
||||
fail("\n".join(msglines), pytrace=False)
|
||||
|
||||
# Boolean condition.
|
||||
else:
|
||||
try:
|
||||
result = bool(condition)
|
||||
except Exception as exc:
|
||||
msglines = [
|
||||
"Error evaluating %r condition as a boolean" % mark.name,
|
||||
*traceback.format_exception_only(type(exc), exc),
|
||||
]
|
||||
fail("\n".join(msglines), pytrace=False)
|
||||
|
||||
reason = mark.kwargs.get("reason", None)
|
||||
if reason is None:
|
||||
if isinstance(condition, str):
|
||||
reason = "condition: " + condition
|
||||
else:
|
||||
# XXX better be checked at collection time
|
||||
msg = (
|
||||
"Error evaluating %r: " % mark.name
|
||||
+ "you need to specify reason=STRING when using booleans as conditions."
|
||||
)
|
||||
fail(msg, pytrace=False)
|
||||
|
||||
return result, reason
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class Skip:
|
||||
"""The result of evaluate_skip_marks()."""
|
||||
|
||||
reason = attr.ib(type=str)
|
||||
|
||||
|
||||
def evaluate_skip_marks(item: Item) -> Optional[Skip]:
|
||||
"""Evaluate skip and skipif marks on item, returning Skip if triggered."""
|
||||
for mark in item.iter_markers(name="skipif"):
|
||||
if "condition" not in mark.kwargs:
|
||||
conditions = mark.args
|
||||
else:
|
||||
conditions = (mark.kwargs["condition"],)
|
||||
|
||||
# Unconditional.
|
||||
if not conditions:
|
||||
reason = mark.kwargs.get("reason", "")
|
||||
return Skip(reason)
|
||||
|
||||
# If any of the conditions are true.
|
||||
for condition in conditions:
|
||||
result, reason = evaluate_condition(item, mark, condition)
|
||||
if result:
|
||||
return Skip(reason)
|
||||
|
||||
for mark in item.iter_markers(name="skip"):
|
||||
if "reason" in mark.kwargs:
|
||||
reason = mark.kwargs["reason"]
|
||||
elif mark.args:
|
||||
reason = mark.args[0]
|
||||
else:
|
||||
reason = "unconditional skip"
|
||||
return Skip(reason)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
@attr.s(slots=True, frozen=True)
|
||||
class Xfail:
|
||||
"""The result of evaluate_xfail_marks()."""
|
||||
|
||||
reason = attr.ib(type=str)
|
||||
run = attr.ib(type=bool)
|
||||
strict = attr.ib(type=bool)
|
||||
raises = attr.ib(type=Optional[Tuple["Type[BaseException]", ...]])
|
||||
|
||||
|
||||
def evaluate_xfail_marks(item: Item) -> Optional[Xfail]:
|
||||
"""Evaluate xfail marks on item, returning Xfail if triggered."""
|
||||
for mark in item.iter_markers(name="xfail"):
|
||||
run = mark.kwargs.get("run", True)
|
||||
strict = mark.kwargs.get("strict", item.config.getini("xfail_strict"))
|
||||
raises = mark.kwargs.get("raises", None)
|
||||
if "condition" not in mark.kwargs:
|
||||
conditions = mark.args
|
||||
else:
|
||||
conditions = (mark.kwargs["condition"],)
|
||||
|
||||
# Unconditional.
|
||||
if not conditions:
|
||||
reason = mark.kwargs.get("reason", "")
|
||||
return Xfail(reason, run, strict, raises)
|
||||
|
||||
# If any of the conditions are true.
|
||||
for condition in conditions:
|
||||
result, reason = evaluate_condition(item, mark, condition)
|
||||
if result:
|
||||
return Xfail(reason, run, strict, raises)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# Whether skipped due to skip or skipif marks.
|
||||
skipped_by_mark_key = StoreKey[bool]()
|
||||
# Saves the xfail mark evaluation. Can be refreshed during call if None.
|
||||
xfailed_key = StoreKey[Optional[Xfail]]()
|
||||
unexpectedsuccess_key = StoreKey[str]()
|
||||
|
||||
|
||||
@hookimpl(tryfirst=True)
|
||||
def pytest_runtest_setup(item: Item) -> None:
|
||||
# Check if skip or skipif are specified as pytest marks
|
||||
item._store[skipped_by_mark_key] = False
|
||||
eval_skipif = MarkEvaluator(item, "skipif")
|
||||
if eval_skipif.istrue():
|
||||
item._store[skipped_by_mark_key] = True
|
||||
skip(eval_skipif.getexplanation())
|
||||
|
||||
for skip_info in item.iter_markers(name="skip"):
|
||||
skipped = evaluate_skip_marks(item)
|
||||
if skipped:
|
||||
item._store[skipped_by_mark_key] = True
|
||||
if "reason" in skip_info.kwargs:
|
||||
skip(skip_info.kwargs["reason"])
|
||||
elif skip_info.args:
|
||||
skip(skip_info.args[0])
|
||||
else:
|
||||
skip("unconditional skip")
|
||||
skip(skipped.reason)
|
||||
|
||||
item._store[evalxfail_key] = MarkEvaluator(item, "xfail")
|
||||
check_xfail_no_run(item)
|
||||
if not item.config.option.runxfail:
|
||||
item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item)
|
||||
if xfailed and not xfailed.run:
|
||||
xfail("[NOTRUN] " + xfailed.reason)
|
||||
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(pyfuncitem: Function):
|
||||
check_xfail_no_run(pyfuncitem)
|
||||
outcome = yield
|
||||
passed = outcome.excinfo is None
|
||||
if passed:
|
||||
check_strict_xfail(pyfuncitem)
|
||||
def pytest_runtest_call(item: Item) -> Generator[None, None, None]:
|
||||
xfailed = item._store.get(xfailed_key, None)
|
||||
if xfailed is None:
|
||||
item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item)
|
||||
|
||||
|
||||
def check_xfail_no_run(item: Item) -> None:
|
||||
"""check xfail(run=False)"""
|
||||
if not item.config.option.runxfail:
|
||||
evalxfail = item._store[evalxfail_key]
|
||||
if evalxfail.istrue():
|
||||
if not evalxfail.get("run", True):
|
||||
xfail("[NOTRUN] " + evalxfail.getexplanation())
|
||||
if xfailed and not xfailed.run:
|
||||
xfail("[NOTRUN] " + xfailed.reason)
|
||||
|
||||
|
||||
def check_strict_xfail(pyfuncitem: Function) -> None:
|
||||
"""check xfail(strict=True) for the given PASSING test"""
|
||||
evalxfail = pyfuncitem._store[evalxfail_key]
|
||||
if evalxfail.istrue():
|
||||
strict_default = pyfuncitem.config.getini("xfail_strict")
|
||||
is_strict_xfail = evalxfail.get("strict", strict_default)
|
||||
if is_strict_xfail:
|
||||
del pyfuncitem._store[evalxfail_key]
|
||||
explanation = evalxfail.getexplanation()
|
||||
fail("[XPASS(strict)] " + explanation, pytrace=False)
|
||||
yield
|
||||
|
||||
|
||||
@hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
|
||||
outcome = yield
|
||||
rep = outcome.get_result()
|
||||
evalxfail = item._store.get(evalxfail_key, None)
|
||||
xfailed = item._store.get(xfailed_key, None)
|
||||
# unittest special case, see setting of unexpectedsuccess_key
|
||||
if unexpectedsuccess_key in item._store and rep.when == "call":
|
||||
reason = item._store[unexpectedsuccess_key]
|
||||
|
@ -145,30 +270,27 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
|
|||
else:
|
||||
rep.longrepr = "Unexpected success"
|
||||
rep.outcome = "failed"
|
||||
|
||||
elif item.config.option.runxfail:
|
||||
pass # don't interfere
|
||||
elif call.excinfo and isinstance(call.excinfo.value, xfail.Exception):
|
||||
assert call.excinfo.value.msg is not None
|
||||
rep.wasxfail = "reason: " + call.excinfo.value.msg
|
||||
rep.outcome = "skipped"
|
||||
elif evalxfail and not rep.skipped and evalxfail.wasvalid() and evalxfail.istrue():
|
||||
elif not rep.skipped and xfailed:
|
||||
if call.excinfo:
|
||||
if evalxfail.invalidraise(call.excinfo.value):
|
||||
raises = xfailed.raises
|
||||
if raises is not None and not isinstance(call.excinfo.value, raises):
|
||||
rep.outcome = "failed"
|
||||
else:
|
||||
rep.outcome = "skipped"
|
||||
rep.wasxfail = evalxfail.getexplanation()
|
||||
rep.wasxfail = xfailed.reason
|
||||
elif call.when == "call":
|
||||
strict_default = item.config.getini("xfail_strict")
|
||||
is_strict_xfail = evalxfail.get("strict", strict_default)
|
||||
explanation = evalxfail.getexplanation()
|
||||
if is_strict_xfail:
|
||||
if xfailed.strict:
|
||||
rep.outcome = "failed"
|
||||
rep.longrepr = "[XPASS(strict)] {}".format(explanation)
|
||||
rep.longrepr = "[XPASS(strict)] " + xfailed.reason
|
||||
else:
|
||||
rep.outcome = "passed"
|
||||
rep.wasxfail = explanation
|
||||
rep.wasxfail = xfailed.reason
|
||||
elif (
|
||||
item._store.get(skipped_by_mark_key, True)
|
||||
and rep.skipped
|
||||
|
@ -183,9 +305,6 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
|
|||
rep.longrepr = str(filename), line + 1, reason
|
||||
|
||||
|
||||
# called by terminalreporter progress reporting
|
||||
|
||||
|
||||
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
|
||||
if hasattr(report, "wasxfail"):
|
||||
if report.skipped:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue