Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ repos:
hooks:
- id: black
args: [--safe, --quiet]
additional_dependencies: [click==7.1.2]
- repo: https://github.com/asottile/blacken-docs
rev: v1.7.0
hooks:
Expand All @@ -20,7 +21,7 @@ repos:
- id: debug-statements
exclude: _pytest/(debugging|hookspec).py
language_version: python3
- repo: https://gitlab.com/pycqa/flake8
- repo: https://github.com/pycqa/flake8
rev: 3.8.2
hooks:
- id: flake8
Expand Down Expand Up @@ -52,7 +53,7 @@ repos:
hooks:
- id: rst
name: rst
entry: rst-lint --encoding utf-8
entry: rst-lint
files: ^(RELEASING.rst|README.rst|TIDELIFT.rst)$
language: python
additional_dependencies: [pygments, restructuredtext_lint]
Expand Down
1 change: 1 addition & 0 deletions extra/setup-py.test/setup.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys

from distutils.core import setup

if __name__ == "__main__":
Expand Down
38 changes: 30 additions & 8 deletions src/_pytest/skipping.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,20 +239,24 @@ def pytest_runtest_setup(item: Item) -> None:
skip(skipped.reason)

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)
xfailed = evaluate_xfail_marks(item)
if xfailed:
item._store[xfailed_key] = xfailed
if not xfailed.run:
xfail("[NOTRUN] " + xfailed.reason)
else:
try:
del item._store[xfailed_key]
except KeyError:
pass


@hookimpl(hookwrapper=True)
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)

if not item.config.option.runxfail:
if xfailed and not xfailed.run:
xfail("[NOTRUN] " + xfailed.reason)
if not item.config.option.runxfail and xfailed and not xfailed.run:
xfail("[NOTRUN] " + xfailed.reason)

yield

Expand All @@ -262,6 +266,10 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
outcome = yield
rep = outcome.get_result()
xfailed = item._store.get(xfailed_key, None)
if not item.config.option.runxfail and call.when == "call" and xfailed is None:
xfailed = evaluate_xfail_marks(item)
if xfailed:
item._store[xfailed_key] = xfailed
# unittest special case, see setting of unexpectedsuccess_key
if unexpectedsuccess_key in item._store and rep.when == "call":
reason = item._store[unexpectedsuccess_key]
Expand Down Expand Up @@ -292,6 +300,20 @@ def pytest_runtest_makereport(item: Item, call: CallInfo[None]):
rep.outcome = "passed"
rep.wasxfail = xfailed.reason

if (
not item.config.option.runxfail
and call.when == "call"
and xfailed
and not xfailed.run
and not getattr(rep, "wasxfail", None)
):
notrun_reason = "[NOTRUN]"
if xfailed.reason:
notrun_reason += " " + xfailed.reason
rep.wasxfail = "reason: " + notrun_reason
if not rep.skipped:
rep.outcome = "skipped"

if (
item._store.get(skipped_by_mark_key, True)
and rep.skipped
Expand Down
53 changes: 53 additions & 0 deletions testing/test_dynamic_xfail.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pytest


pytest_plugins = "pytester"


@pytest.fixture
def pytester(testdir):
return testdir


def _disable_plugin_autoload(pytester):
pytester.monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")


def test_dynamic_xfail_during_call(pytester):
_disable_plugin_autoload(pytester)
pytester.makepyfile(
"""
import pytest


def test_dynamic_xfail(request):
request.node.add_marker(pytest.mark.xfail(reason="late xfail"))
assert 0
"""
)

result = pytester.runpytest("-rx")
result.assert_outcomes(xfailed=1)
result.stdout.fnmatch_lines(
["*XFAIL*test_dynamic_xfail*", "*late xfail*"]
)


def test_xfail_run_false_prevents_call(pytester):
_disable_plugin_autoload(pytester)
pytester.makepyfile(
"""
import pytest


@pytest.mark.xfail(run=False, reason="dismiss")
def test_not_run():
raise AssertionError("should not run")
"""
)

result = pytester.runpytest("-rx")
result.assert_outcomes(xfailed=1)
result.stdout.fnmatch_lines(
["*XFAIL*test_not_run*", " reason: [NOTRUN] dismiss"]
)