Improve way in which skip location is fixed up when skipped by mark

When `pytest.skip()` is called inside a test function, the skip location
should be reported as the line that made the call, however when
`pytest.skip()` is called by the `pytest.mark.skip` and similar
mechanisms, the location should be reported at the item's location,
because the exact location is some irrelevant internal code.

Currently the item-location case is implemented by the caller setting a
boolean key on the item's store and the `skipping` plugin checking it
and fixing up the location if needed. This is really roundabout IMO and
breaks encapsulation.

Instead, allow the caller to specify directly on the skip exception
whether to use the item's location or not. For now, this is entirely
private.
This commit is contained in:
Ran Benita 2021-01-14 11:58:59 +02:00
parent 3dde519f53
commit 7f989203ed
4 changed files with 14 additions and 22 deletions

View File

@ -58,9 +58,14 @@ class Skipped(OutcomeException):
msg: Optional[str] = None,
pytrace: bool = True,
allow_module_level: bool = False,
*,
_use_item_location: bool = False,
) -> None:
OutcomeException.__init__(self, msg=msg, pytrace=pytrace)
self.allow_module_level = allow_module_level
# If true, the skip location is reported as the item's location,
# instead of the place that raises the exception/calls skip().
self._use_item_location = _use_item_location
class Failed(OutcomeException):

View File

@ -324,7 +324,12 @@ class TestReport(BaseReport):
elif isinstance(excinfo.value, skip.Exception):
outcome = "skipped"
r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message)
if excinfo.value._use_item_location:
filename, line = item.reportinfo()[:2]
assert line is not None
longrepr = str(filename), line + 1, r.message
else:
longrepr = (str(r.path), r.lineno, r.message)
else:
outcome = "failed"
if call.when == "call":

View File

@ -230,8 +230,6 @@ def evaluate_xfail_marks(item: Item) -> Optional[Xfail]:
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]]()
@ -239,9 +237,8 @@ xfailed_key = StoreKey[Optional[Xfail]]()
@hookimpl(tryfirst=True)
def pytest_runtest_setup(item: Item) -> None:
skipped = evaluate_skip_marks(item)
item._store[skipped_by_mark_key] = skipped is not None
if skipped:
skip(skipped.reason)
raise skip.Exception(skipped.reason, _use_item_location=True)
item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item)
if xfailed and not item.config.option.runxfail and not xfailed.run:
@ -292,19 +289,6 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
rep.outcome = "passed"
rep.wasxfail = xfailed.reason
if (
item._store.get(skipped_by_mark_key, True)
and rep.skipped
and type(rep.longrepr) is tuple
):
# Skipped by mark.skipif; change the location of the failure
# to point to the item definition, otherwise it will display
# the location of where the skip exception was raised within pytest.
_, _, reason = rep.longrepr
filename, line = item.reportinfo()[:2]
assert line is not None
rep.longrepr = str(filename), line + 1, reason
def pytest_report_teststatus(report: BaseReport) -> Optional[Tuple[str, str, str]]:
if hasattr(report, "wasxfail"):

View File

@ -29,7 +29,6 @@ from _pytest.python import Class
from _pytest.python import Function
from _pytest.python import PyCollector
from _pytest.runner import CallInfo
from _pytest.skipping import skipped_by_mark_key
if TYPE_CHECKING:
import unittest
@ -150,7 +149,7 @@ def _make_xunit_fixture(
def fixture(self, request: FixtureRequest) -> Generator[None, None, None]:
if _is_skipped(self):
reason = self.__unittest_skip_why__
pytest.skip(reason)
raise pytest.skip.Exception(reason, _use_item_location=True)
if setup is not None:
try:
if pass_self:
@ -256,9 +255,8 @@ class TestCaseFunction(Function):
def addSkip(self, testcase: "unittest.TestCase", reason: str) -> None:
try:
skip(reason)
raise pytest.skip.Exception(reason, _use_item_location=True)
except skip.Exception:
self._store[skipped_by_mark_key] = True
self._addexcinfo(sys.exc_info())
def addExpectedFailure(