Skip to content

Commit 5841ccf

Browse files
Merge pull request #34 from webknjaz/features/xfail-support
Add support for xfail
2 parents 295e065 + eea6536 commit 5841ccf

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

src/pytest_forked/__init__.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
import os
2+
import warnings
3+
24
import py
35
# we know this bit is bad, but we cant help it with the current pytest setup
46
from _pytest import runner
@@ -88,4 +90,24 @@ def report_process_crash(item, result):
8890
rep.sections.append(("captured stdout", result.out))
8991
if result.err:
9092
rep.sections.append(("captured stderr", result.err))
93+
94+
xfail_marker = item.get_closest_marker('xfail')
95+
if not xfail_marker:
96+
return rep
97+
98+
rep.outcome = "skipped"
99+
rep.wasxfail = (
100+
"reason: {xfail_reason}; "
101+
"pytest-forked reason: {crash_info}".
102+
format(
103+
xfail_reason=xfail_marker.kwargs['reason'],
104+
crash_info=info,
105+
)
106+
)
107+
warnings.warn(
108+
'pytest-forked xfail support is incomplete at the moment and may '
109+
'output a misleading reason message',
110+
RuntimeWarning,
111+
)
112+
91113
return rep

testing/test_xfail_behavior.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
# -*- coding: utf-8 -*-
2+
"""Tests for xfail support."""
3+
import os
4+
import signal
5+
6+
import pytest
7+
8+
IS_PYTEST4_PLUS = int(pytest.__version__[0]) >= 4 # noqa: WPS609
9+
FAILED_WORD = 'FAILED' if IS_PYTEST4_PLUS else 'FAIL'
10+
11+
pytestmark = pytest.mark.skipif( # pylint: disable=invalid-name
12+
not hasattr(os, 'fork'), # noqa: WPS421
13+
reason='os.fork required',
14+
)
15+
16+
17+
@pytest.mark.parametrize(
18+
('is_crashing', 'is_strict'),
19+
(
20+
pytest.param(True, True, id='strict xfail'),
21+
pytest.param(False, True, id='strict xpass'),
22+
pytest.param(True, False, id='non-strict xfail'),
23+
pytest.param(False, False, id='non-strict xpass'),
24+
),
25+
)
26+
def test_xfail(is_crashing, is_strict, testdir):
27+
"""Test xfail/xpass/strict permutations."""
28+
# pylint: disable=possibly-unused-variable
29+
sig_num = signal.SIGTERM.numerator
30+
31+
test_func_body = (
32+
'os.kill(os.getpid(), signal.SIGTERM)'
33+
if is_crashing
34+
else 'assert True'
35+
)
36+
37+
if is_crashing:
38+
# marked xfailed and crashing, no matter strict or not
39+
expected_letter = 'x' # XFAILED
40+
expected_lowercase = 'xfailed'
41+
expected_word = 'XFAIL'
42+
elif is_strict:
43+
# strict and not failing as expected should cause failure
44+
expected_letter = 'F' # FAILED
45+
expected_lowercase = 'failed'
46+
expected_word = FAILED_WORD
47+
elif not is_strict:
48+
# non-strict and not failing as expected should cause xpass
49+
expected_letter = 'X' # XPASS
50+
expected_lowercase = 'xpassed'
51+
expected_word = 'XPASS'
52+
53+
session_start_title = '*==== test session starts ====*'
54+
loaded_pytest_plugins = 'plugins: forked*'
55+
collected_tests_num = 'collected 1 item'
56+
expected_progress = 'test_xfail.py {expected_letter!s}'.format(**locals())
57+
failures_title = '*==== FAILURES ====*'
58+
failures_test_name = '*____ test_function ____*'
59+
failures_test_reason = '[XPASS(strict)] The process gets terminated'
60+
short_test_summary_title = '*==== short test summary info ====*'
61+
short_test_summary = (
62+
'{expected_word!s} test_xfail.py::test_function'.
63+
format(**locals())
64+
)
65+
if expected_lowercase == 'xpassed':
66+
# XPASS wouldn't have the crash message from
67+
# pytest-forked because the crash doesn't happen
68+
short_test_summary = ' '.join((
69+
short_test_summary, 'The process gets terminated',
70+
))
71+
reason_string = (
72+
' reason: The process gets terminated; '
73+
'pytest-forked reason: '
74+
'*:*: running the test CRASHED with signal {sig_num:d}'.
75+
format(**locals())
76+
)
77+
total_summary_line = (
78+
'*==== 1 {expected_lowercase!s} in 0.*s* ====*'.
79+
format(**locals())
80+
)
81+
82+
expected_lines = (
83+
session_start_title,
84+
loaded_pytest_plugins,
85+
collected_tests_num,
86+
expected_progress,
87+
)
88+
if expected_word == FAILED_WORD:
89+
# XPASS(strict)
90+
expected_lines += (
91+
failures_title,
92+
failures_test_name,
93+
failures_test_reason,
94+
)
95+
expected_lines += (
96+
short_test_summary_title,
97+
short_test_summary,
98+
)
99+
if expected_lowercase == 'xpassed' and expected_word == FAILED_WORD:
100+
# XPASS(strict)
101+
expected_lines += (
102+
reason_string,
103+
)
104+
expected_lines += (
105+
total_summary_line,
106+
)
107+
108+
test_module = testdir.makepyfile(
109+
"""
110+
import os
111+
import signal
112+
113+
import pytest
114+
115+
@pytest.mark.xfail(
116+
reason='The process gets terminated',
117+
strict={is_strict!s},
118+
)
119+
@pytest.mark.forked
120+
def test_function():
121+
{test_func_body!s}
122+
""".
123+
format(**locals())
124+
)
125+
126+
pytest_run_result = testdir.runpytest(
127+
test_module,
128+
'-ra',
129+
'-p', 'no:warnings', # the current implementation emits RuntimeWarning
130+
)
131+
pytest_run_result.stdout.fnmatch_lines(expected_lines)

0 commit comments

Comments
 (0)