Skip to content

Commit de87d63

Browse files
authored
xfail reason & hookimpl(tryfirst=True) (#30)
* collect xfail reason * add 'tryfirst' --------- Co-authored-by: Bryn Lloyd <12702862+dyollb@users.noreply.github.com>
1 parent dd5f763 commit de87d63

File tree

5 files changed

+43
-13
lines changed

5 files changed

+43
-13
lines changed

src/pytest_isolated/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""pytest-isolated: Run pytest tests in isolated subprocesses."""
22

3-
__version__ = "0.4.2"
3+
__version__ = "0.4.3"

src/pytest_isolated/execution.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
_emit_failure_for_items,
2929
_emit_report,
3030
_format_crash_message,
31+
_get_xfail_reason,
3132
_TestRecord,
3233
)
3334

@@ -275,14 +276,14 @@ def _handle_mid_test_crash(
275276
)
276277

277278
# Emit call phase as failed with crash info
278-
xfail_marker = it.get_closest_marker("xfail")
279-
if xfail_marker:
279+
xfail_reason = _get_xfail_reason(it)
280+
if xfail_reason:
280281
_emit_report(
281282
it,
282283
when="call",
283284
outcome="skipped",
284285
longrepr=crash_msg,
285-
wasxfail=True,
286+
wasxfail=xfail_reason,
286287
)
287288
else:
288289
_emit_report(
@@ -363,13 +364,14 @@ def _emit_all_results(
363364
stderr=rec.get("stderr", ""),
364365
sections=rec.get("sections"),
365366
user_properties=rec.get("user_properties"),
366-
wasxfail=rec.get("wasxfail", False),
367+
wasxfail=rec.get("wasxfail"),
367368
)
368369

369370
if when == "call" and rec["outcome"] == "failed":
370371
ctx.session.testsfailed += 1
371372

372373

374+
@pytest.hookimpl(tryfirst=True)
373375
def pytest_runtestloop(session: pytest.Session) -> int | None:
374376
"""Execute isolated test groups in subprocesses and remaining tests in-process.
375377

src/pytest_isolated/grouping.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def _has_isolated_marker(obj: Any) -> bool:
1919
return any(getattr(m, "name", None) == "isolated" for m in markers)
2020

2121

22+
@pytest.hookimpl(tryfirst=True)
2223
def pytest_collection_modifyitems(
2324
config: pytest.Config, items: list[pytest.Item]
2425
) -> None:

src/pytest_isolated/reporting.py

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,28 @@
1212
from .config import SUBPROC_REPORT_PATH
1313

1414

15+
def _get_xfail_reason(item: pytest.Item) -> str | None:
16+
"""Extract xfail reason from marker if present.
17+
18+
Returns the xfail reason string, or None if no xfail marker.
19+
"""
20+
xfail_marker = item.get_closest_marker("xfail")
21+
if not xfail_marker:
22+
return None
23+
24+
# Try to get reason from kwargs first, then from args
25+
reason = xfail_marker.kwargs.get("reason")
26+
if reason:
27+
return str(reason)
28+
29+
# If no reason in kwargs, try first positional arg
30+
if xfail_marker.args:
31+
return str(xfail_marker.args[0])
32+
33+
# Default if no reason provided
34+
return "xfail"
35+
36+
1537
class _TestRecord(TypedDict, total=False):
1638
"""Structure for test phase results from subprocess."""
1739

@@ -25,7 +47,7 @@ class _TestRecord(TypedDict, total=False):
2547
keywords: list[str]
2648
sections: list[tuple[str, str]]
2749
user_properties: list[tuple[str, Any]]
28-
wasxfail: bool
50+
wasxfail: str # xfail reason string
2951

3052

3153
def _format_crash_reason(returncode: int) -> str:
@@ -82,8 +104,10 @@ def pytest_runtest_logreport(report: pytest.TestReport) -> None:
82104
"keywords": list(report.keywords),
83105
"sections": getattr(report, "sections", []), # captured logs, etc.
84106
"user_properties": getattr(report, "user_properties", []),
85-
"wasxfail": hasattr(report, "wasxfail"),
86107
}
108+
# Store xfail reason if present
109+
if hasattr(report, "wasxfail"):
110+
rec["wasxfail"] = str(report.wasxfail)
87111
with Path(path).open("a", encoding="utf-8") as f:
88112
f.write(json.dumps(rec) + "\n")
89113

@@ -99,7 +123,7 @@ def _emit_report(
99123
stderr: str = "",
100124
sections: list[tuple[str, str]] | None = None,
101125
user_properties: list[tuple[str, Any]] | None = None,
102-
wasxfail: bool = False,
126+
wasxfail: str | None = None,
103127
) -> None:
104128
"""Emit a test report for a specific test phase."""
105129
call = pytest.CallInfo.from_call(lambda: None, when=when)
@@ -111,7 +135,7 @@ def _emit_report(
111135
rep.user_properties = user_properties
112136

113137
if wasxfail:
114-
rep.wasxfail = "reason: xfail"
138+
rep.wasxfail = wasxfail
115139

116140
# For skipped tests, longrepr needs to be a tuple (path, lineno, reason)
117141
if outcome == "skipped" and longrepr:
@@ -159,15 +183,15 @@ def _emit_failure_for_items(
159183
empty output in later phases can clear the captured output from earlier phases.
160184
"""
161185
for it in items:
162-
xfail_marker = it.get_closest_marker("xfail")
186+
xfail_reason = _get_xfail_reason(it)
163187
_emit_report(it, when="setup", outcome="passed", stdout=stdout, stderr=stderr)
164-
if xfail_marker:
188+
if xfail_reason:
165189
_emit_report(
166190
it,
167191
when="call",
168192
outcome="skipped",
169193
longrepr=error_message,
170-
wasxfail=True,
194+
wasxfail=xfail_reason,
171195
stdout=stdout,
172196
stderr=stderr,
173197
)

tests/test_reporting.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def test_skipped():
5151

5252

5353
def test_xfail_test_handling(pytester: Pytester):
54-
"""Test that xfail tests are properly handled."""
54+
"""Test that xfail tests are properly handled and reason is displayed."""
5555
pytester.makepyfile(
5656
"""
5757
import pytest
@@ -66,6 +66,9 @@ def test_xfail():
6666
result = pytester.runpytest("-v")
6767
result.assert_outcomes(xfailed=1)
6868

69+
# Verify the xfail reason is displayed in the output
70+
assert "Expected to fail" in result.stdout.str()
71+
6972

7073
def test_junit_xml_output(pytester: Pytester):
7174
"""Test that JUnit XML output works with subprocess tests."""

0 commit comments

Comments
 (0)