1313import time
1414from types import TracebackType
1515from typing import Any
16- from typing import Literal
1716from typing import TYPE_CHECKING
1817
1918import pluggy
2019
2120from _pytest ._code import ExceptionInfo
21+ from _pytest ._io .saferepr import saferepr
2222from _pytest .capture import CaptureFixture
2323from _pytest .capture import FDCapture
2424from _pytest .capture import SysCapture
@@ -83,11 +83,11 @@ def head_line(self) -> str:
8383
8484 def _sub_test_description (self ) -> str :
8585 parts = []
86- if isinstance ( self .context .msg , str ) :
86+ if self .context .msg is not None :
8787 parts .append (f"[{ self .context .msg } ]" )
8888 if self .context .kwargs :
8989 params_desc = ", " .join (
90- f"{ k } ={ v !r } " for (k , v ) in sorted ( self .context .kwargs .items () )
90+ f"{ k } ={ saferepr ( v ) } " for (k , v ) in self .context .kwargs .items ()
9191 )
9292 parts .append (f"({ params_desc } )" )
9393 return " " .join (parts ) or "(<subtest>)"
@@ -106,8 +106,12 @@ def _from_json(cls, reportdict: dict[str, Any]) -> SubtestReport:
106106 return report
107107
108108 @classmethod
109- def _from_test_report (cls , test_report : TestReport ) -> SubtestReport :
110- return super ()._from_json (test_report ._to_json ())
109+ def _from_test_report (
110+ cls , test_report : TestReport , context : SubtestContext
111+ ) -> Self :
112+ result = super ()._from_json (test_report ._to_json ())
113+ result .context = context
114+ return result
111115
112116
113117@fixture
@@ -121,8 +125,6 @@ def subtests(request: SubRequest) -> Subtests:
121125 return Subtests (request .node .ihook , suspend_capture_ctx , request , _ispytest = True )
122126
123127
124- # Note: cannot use a dataclass here because Sphinx insists on showing up the __init__ method in the documentation,
125- # even if we explicitly use :exclude-members: __init__.
126128class Subtests :
127129 """Subtests fixture, enables declaring subtests inside test functions via the :meth:`test` method."""
128130
@@ -178,12 +180,13 @@ class _SubTestContextManager:
178180 """
179181 Context manager for subtests, capturing exceptions raised inside the subtest scope and handling
180182 them through the pytest machinery.
181-
182- Note: initially this logic was implemented directly in Subtests.test() as a @contextmanager, however
183- it is not possible to control the output fully when exiting from it due to an exception when
184- in --exitfirst mode, so this was refactored into an explicit context manager class (pytest-dev/pytest-subtests#134).
185183 """
186184
185+ # Note: initially the logic for this context manager was implemented directly
186+ # in Subtests.test() as a @contextmanager, however, it is not possible to control the output fully when
187+ # exiting from it due to an exception when in `--exitfirst` mode, so this was refactored into an
188+ # explicit context manager class (pytest-dev/pytest-subtests#134).
189+
187190 ihook : pluggy .HookRelay
188191 msg : str | None
189192 kwargs : dict [str , Any ]
@@ -224,14 +227,21 @@ def __exit__(
224227 duration = precise_stop - self ._precise_start
225228 stop = time .time ()
226229
227- call_info = make_call_info (
228- exc_info , start = self ._start , stop = stop , duration = duration , when = "call"
230+ call_info = CallInfo [None ](
231+ None ,
232+ exc_info ,
233+ start = self ._start ,
234+ stop = stop ,
235+ duration = duration ,
236+ when = "call" ,
237+ _ispytest = True ,
229238 )
230239 report = self .ihook .pytest_runtest_makereport (
231240 item = self .request .node , call = call_info
232241 )
233- sub_report = SubtestReport ._from_test_report (report )
234- sub_report .context = SubtestContext (msg = self .msg , kwargs = self .kwargs .copy ())
242+ sub_report = SubtestReport ._from_test_report (
243+ report , SubtestContext (msg = self .msg , kwargs = self .kwargs .copy ())
244+ )
235245
236246 self ._captured_output .update_report (sub_report )
237247 self ._captured_logs .update_report (sub_report )
@@ -250,25 +260,6 @@ def __exit__(
250260 return True
251261
252262
253- def make_call_info (
254- exc_info : ExceptionInfo [BaseException ] | None ,
255- * ,
256- start : float ,
257- stop : float ,
258- duration : float ,
259- when : Literal ["collect" , "setup" , "call" , "teardown" ],
260- ) -> CallInfo [Any ]:
261- return CallInfo (
262- None ,
263- exc_info ,
264- start = start ,
265- stop = stop ,
266- duration = duration ,
267- when = when ,
268- _ispytest = True ,
269- )
270-
271-
272263@contextmanager
273264def capturing_output (request : SubRequest ) -> Iterator [Captured ]:
274265 option = request .config .getoption ("capture" , None )
0 commit comments