Skip to content

Commit a3cf834

Browse files
committed
Ensure subtest's context kwargs are JSON serializable
Convert all the values of `SubtestContext.kwargs` to strings using `saferepr`. This complies with the requirement that the returned dict from `pytest_report_to_serializable` is serializable to JSON, at the cost of losing type information for objects that are natively supported by JSON. Fixes pytest-dev/pytest-xdist#1273
1 parent 90b9389 commit a3cf834

File tree

2 files changed

+40
-3
lines changed

2 files changed

+40
-3
lines changed

src/_pytest/subtests.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,10 @@ class SubtestContext:
6262
kwargs: Mapping[str, Any]
6363

6464
def _to_json(self) -> dict[str, Any]:
65-
return dataclasses.asdict(self)
65+
result = dataclasses.asdict(self)
66+
# Brute-force the returned kwargs dict to be JSON serializable (pytest-dev/pytest-xdist#1273).
67+
result["kwargs"] = {k: saferepr(v) for (k, v) in result["kwargs"].items()}
68+
return result
6669

6770
@classmethod
6871
def _from_json(cls, d: dict[str, Any]) -> Self:

testing/test_subtests.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from __future__ import annotations
22

3+
from enum import Enum
34
import sys
45
from typing import Literal
56

7+
from _pytest._io.saferepr import saferepr
68
from _pytest.subtests import SubtestContext
79
from _pytest.subtests import SubtestReport
810
import pytest
@@ -958,20 +960,52 @@ def test(subtests):
958960

959961

960962
def test_serialization() -> None:
963+
"""Ensure subtest's kwargs are serialized using `saferepr` (pytest-dev/pytest-xdist#1273)."""
961964
from _pytest.subtests import pytest_report_from_serializable
962965
from _pytest.subtests import pytest_report_to_serializable
963966

967+
class MyEnum(Enum):
968+
A = "A"
969+
964970
report = SubtestReport(
965971
"test_foo::test_foo",
966972
("test_foo.py", 12, ""),
967973
keywords={},
968974
outcome="passed",
969975
when="call",
970976
longrepr=None,
971-
context=SubtestContext(msg="custom message", kwargs=dict(i=10)),
977+
context=SubtestContext(msg="custom message", kwargs=dict(i=10, a=MyEnum.A)),
972978
)
973979
data = pytest_report_to_serializable(report)
974980
assert data is not None
975981
new_report = pytest_report_from_serializable(data)
976982
assert new_report is not None
977-
assert new_report.context == SubtestContext(msg="custom message", kwargs=dict(i=10))
983+
assert new_report.context == SubtestContext(
984+
msg="custom message", kwargs=dict(i=saferepr(10), a=saferepr(MyEnum.A))
985+
)
986+
987+
988+
def test_serialization_xdist(pytester: pytest.Pytester) -> None:
989+
"""Regression test for pytest-dev/pytest-xdist#1273."""
990+
pytest.importorskip("xdist")
991+
pytester.makepyfile(
992+
"""
993+
from enum import Enum
994+
import unittest
995+
996+
class MyEnum(Enum):
997+
A = "A"
998+
999+
def test(subtests):
1000+
with subtests.test(a=MyEnum.A):
1001+
pass
1002+
1003+
class T(unittest.TestCase):
1004+
1005+
def test(self):
1006+
with self.subTest(a=MyEnum.A):
1007+
pass
1008+
"""
1009+
)
1010+
result = pytester.runpytest("-n1", "-pxdist.plugin")
1011+
result.assert_outcomes(passed=2)

0 commit comments

Comments
 (0)