11from __future__ import annotations
22
3+ from enum import Enum
4+ import json
35import sys
46from typing import Literal
57
8+ from _pytest ._io .saferepr import saferepr
69from _pytest .subtests import SubtestContext
710from _pytest .subtests import SubtestReport
811import pytest
@@ -302,10 +305,10 @@ def test_foo(subtests, x):
302305 result = pytester .runpytest ("-v" )
303306 result .stdout .fnmatch_lines (
304307 [
305- "*.py::test_foo[[]0[]] SUBFAILED[[]custom[]] (i=1 ) *[[] 50%[]]" ,
306- "*.py::test_foo[[]0[]] FAILED *[[] 50%[]]" ,
307- "*.py::test_foo[[]1[]] SUBFAILED[[]custom[]] (i=1 ) *[[]100%[]]" ,
308- "*.py::test_foo[[]1[]] FAILED *[[]100%[]]" ,
308+ "*.py::test_foo[[]0[]] SUBFAILED[[]custom[]] (i='1' ) *[[] 50%[]]" ,
309+ "*.py::test_foo[[]0[]] FAILED *[[] 50%[]]" ,
310+ "*.py::test_foo[[]1[]] SUBFAILED[[]custom[]] (i='1' ) *[[]100%[]]" ,
311+ "*.py::test_foo[[]1[]] FAILED *[[]100%[]]" ,
309312 "contains 1 failed subtest" ,
310313 "* 4 failed, 4 subtests passed in *" ,
311314 ]
@@ -320,10 +323,10 @@ def test_foo(subtests, x):
320323 result = pytester .runpytest ("-v" )
321324 result .stdout .fnmatch_lines (
322325 [
323- "*.py::test_foo[[]0[]] SUBFAILED[[]custom[]] (i=1 ) *[[] 50%[]]" ,
324- "*.py::test_foo[[]0[]] FAILED *[[] 50%[]]" ,
325- "*.py::test_foo[[]1[]] SUBFAILED[[]custom[]] (i=1 ) *[[]100%[]]" ,
326- "*.py::test_foo[[]1[]] FAILED *[[]100%[]]" ,
326+ "*.py::test_foo[[]0[]] SUBFAILED[[]custom[]] (i='1' ) *[[] 50%[]]" ,
327+ "*.py::test_foo[[]0[]] FAILED *[[] 50%[]]" ,
328+ "*.py::test_foo[[]1[]] SUBFAILED[[]custom[]] (i='1' ) *[[]100%[]]" ,
329+ "*.py::test_foo[[]1[]] FAILED *[[]100%[]]" ,
327330 "contains 1 failed subtest" ,
328331 "* 4 failed in *" ,
329332 ]
@@ -650,12 +653,12 @@ def test_capturing(self, pytester: pytest.Pytester, mode: str) -> None:
650653 result = pytester .runpytest (f"--capture={ mode } " )
651654 result .stdout .fnmatch_lines (
652655 [
653- "*__ test (i='A') __*" ,
656+ "*__ test (i=\" 'A'\" ) __*" ,
654657 "*Captured stdout call*" ,
655658 "hello stdout A" ,
656659 "*Captured stderr call*" ,
657660 "hello stderr A" ,
658- "*__ test (i='B') __*" ,
661+ "*__ test (i=\" 'B'\" ) __*" ,
659662 "*Captured stdout call*" ,
660663 "hello stdout B" ,
661664 "*Captured stderr call*" ,
@@ -676,8 +679,8 @@ def test_no_capture(self, pytester: pytest.Pytester) -> None:
676679 "hello stdout A" ,
677680 "uhello stdout B" ,
678681 "uend test" ,
679- "*__ test (i='A') __*" ,
680- "*__ test (i='B') __*" ,
682+ "*__ test (i=\" 'A'\" ) __*" ,
683+ "*__ test (i=\" 'B'\" ) __*" ,
681684 "*__ test __*" ,
682685 ]
683686 )
@@ -957,7 +960,14 @@ def test(subtests):
957960 )
958961
959962
963+ class MyEnum (Enum ):
964+ """Used in test_serialization, needs to be declared at the module level to be pickled."""
965+
966+ A = "A"
967+
968+
960969def test_serialization () -> None :
970+ """Ensure subtest's kwargs are serialized using `saferepr` (pytest-dev/pytest-xdist#1273)."""
961971 from _pytest .subtests import pytest_report_from_serializable
962972 from _pytest .subtests import pytest_report_to_serializable
963973
@@ -968,10 +978,41 @@ def test_serialization() -> None:
968978 outcome = "passed" ,
969979 when = "call" ,
970980 longrepr = None ,
971- context = SubtestContext (msg = "custom message" , kwargs = dict (i = 10 )),
981+ context = SubtestContext (msg = "custom message" , kwargs = dict (i = 10 , a = MyEnum . A )),
972982 )
973983 data = pytest_report_to_serializable (report )
974984 assert data is not None
985+ # Ensure the report is actually serializable to JSON.
986+ _ = json .dumps (data )
975987 new_report = pytest_report_from_serializable (data )
976988 assert new_report is not None
977- assert new_report .context == SubtestContext (msg = "custom message" , kwargs = dict (i = 10 ))
989+ assert new_report .context == SubtestContext (
990+ msg = "custom message" , kwargs = dict (i = saferepr (10 ), a = saferepr (MyEnum .A ))
991+ )
992+
993+
994+ def test_serialization_xdist (pytester : pytest .Pytester ) -> None : # pragma: no cover
995+ """Regression test for pytest-dev/pytest-xdist#1273."""
996+ pytest .importorskip ("xdist" )
997+ pytester .makepyfile (
998+ """
999+ from enum import Enum
1000+ import unittest
1001+
1002+ class MyEnum(Enum):
1003+ A = "A"
1004+
1005+ def test(subtests):
1006+ with subtests.test(a=MyEnum.A):
1007+ pass
1008+
1009+ class T(unittest.TestCase):
1010+
1011+ def test(self):
1012+ with self.subTest(a=MyEnum.A):
1013+ pass
1014+ """
1015+ )
1016+ pytester .syspathinsert ()
1017+ result = pytester .runpytest ("-n1" , "-pxdist.plugin" )
1018+ result .assert_outcomes (passed = 2 )
0 commit comments