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
1 change: 1 addition & 0 deletions changelog/15134.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``DumpError`` from ``pytest-xdist`` when a ``str`` subclass (such as a :class:`~enum.StrEnum` member) is passed as the ``msg`` of :fixture:`subtests` -- the message is now converted to a plain ``str``.
6 changes: 6 additions & 0 deletions src/_pytest/subtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ def __post_init__(self) -> None:
object.__setattr__(
self, "kwargs", {k: saferepr(v) for (k, v) in self.kwargs.items()}
)
# Ensure msg is a plain str: str subclasses such as StrEnum members
# cannot be serialized by execnet (pytest-dev/pytest-xdist#1161).
# str.__str__ keeps the string data, while str() on a (str, Enum)
# mixin would return "Cls.MEMBER".
if isinstance(self.msg, str) and type(self.msg) is not str:
object.__setattr__(self, "msg", str.__str__(self.msg))

def _to_json(self) -> dict[str, Any]:
result = dataclasses.asdict(self)
Expand Down
28 changes: 27 additions & 1 deletion testing/test_subtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,25 @@ def test_serialization() -> None:
)


class MyStrEnum(str, Enum):
B = "B"


@pytest.mark.parametrize(
("msg", "expected"),
[
pytest.param(MyStrEnum.B, "B", id="str-subclass"),
pytest.param("plain", "plain", id="str"),
pytest.param(None, None, id="none"),
],
)
def test_msg_is_plain_str(msg: str | None, expected: str | None) -> None:
"""Ensure subtest's msg is a plain str, which execnet can serialize (pytest-dev/pytest-xdist#1161)."""
context = SubtestContext(msg=msg, kwargs={})
assert context.msg == expected
assert type(context.msg) is type(expected)


def test_serialization_xdist(pytester: pytest.Pytester) -> None: # pragma: no cover
"""Regression test for pytest-dev/pytest-xdist#1273."""
pytest.importorskip("xdist")
Expand All @@ -1059,10 +1078,17 @@ def test_serialization_xdist(pytester: pytest.Pytester) -> None: # pragma: no c
class MyEnum(Enum):
A = "A"

class MyStrEnum(str, Enum):
B = "B"

def test(subtests):
with subtests.test(a=MyEnum.A):
pass

def test_str_subclass_msg(subtests):
with subtests.test(msg=MyStrEnum.B):
pass

class T(unittest.TestCase):

def test(self):
Expand All @@ -1072,4 +1098,4 @@ def test(self):
)
pytester.syspathinsert()
result = pytester.runpytest("-n1", "-pxdist.plugin")
result.assert_outcomes(passed=2)
result.assert_outcomes(passed=3)
Loading