99from  contextlib  import  ExitStack 
1010from  contextlib  import  nullcontext 
1111import  dataclasses 
12- import  sys 
1312import  time 
1413from  typing  import  Any 
1514from  typing  import  TYPE_CHECKING 
16- from  unittest  import  TestCase 
1715
1816import  pluggy 
1917
3129from  _pytest .reports  import  TestReport 
3230from  _pytest .runner  import  CallInfo 
3331from  _pytest .runner  import  check_interactive_exception 
34- from  _pytest .unittest  import  TestCaseFunction 
35- from  _pytest .warning_types  import  PytestDeprecationWarning 
3632
3733
3834if  TYPE_CHECKING :
@@ -60,12 +56,14 @@ def pytest_addoption(parser: Parser) -> None:
6056
6157@dataclasses .dataclass  
6258class  SubTestContext :
59+     """The values passed to SubTests.test() that are included in the test report.""" 
60+ 
6361    msg : str  |  None 
6462    kwargs : dict [str , Any ]
6563
6664
6765@dataclasses .dataclass (init = False ) 
68- class  SubTestReport (TestReport ):   # type: ignore[misc] 
66+ class  SubTestReport (TestReport ):
6967    context : SubTestContext 
7068
7169    @property  
@@ -105,122 +103,6 @@ def _from_test_report(cls, test_report: TestReport) -> SubTestReport:
105103        return  super ()._from_json (test_report ._to_json ())
106104
107105
108- def  _addSkip (self : TestCaseFunction , testcase : TestCase , reason : str ) ->  None :
109-     from  unittest .case  import  _SubTest   # type: ignore[attr-defined] 
110- 
111-     if  isinstance (testcase , _SubTest ):
112-         self ._originaladdSkip (testcase , reason )  # type: ignore[attr-defined] 
113-         if  self ._excinfo  is  not None :
114-             exc_info  =  self ._excinfo [- 1 ]
115-             self .addSubTest (testcase .test_case , testcase , exc_info )  # type: ignore[attr-defined] 
116-     else :
117-         # For python < 3.11: the non-subtest skips have to be added by `_originaladdSkip` only after all subtest 
118-         # failures are processed by `_addSubTest`. (`self.instance._outcome` has no attribute `skipped/errors` anymore.) 
119-         # For python < 3.11, we also need to check if `self.instance._outcome` is `None` (this happens if the test 
120-         # class/method is decorated with `unittest.skip`, see #173). 
121-         if  sys .version_info  <  (3 , 11 ) and  self .instance ._outcome  is  not None :
122-             subtest_errors  =  [
123-                 x 
124-                 for  x , y  in  self .instance ._outcome .errors 
125-                 if  isinstance (x , _SubTest ) and  y  is  not None 
126-             ]
127-             if  len (subtest_errors ) ==  0 :
128-                 self ._originaladdSkip (testcase , reason )  # type: ignore[attr-defined] 
129-         else :
130-             self ._originaladdSkip (testcase , reason )  # type: ignore[attr-defined] 
131- 
132- 
133- def  _addSubTest (
134-     self : TestCaseFunction ,
135-     test_case : Any ,
136-     test : TestCase ,
137-     exc_info : tuple [type [BaseException ], BaseException , TracebackType ] |  None ,
138- ) ->  None :
139-     msg  =  test ._message  if  isinstance (test ._message , str ) else  None   # type: ignore[attr-defined] 
140-     call_info  =  make_call_info (
141-         ExceptionInfo (exc_info , _ispytest = True ) if  exc_info  else  None ,
142-         start = 0 ,
143-         stop = 0 ,
144-         duration = 0 ,
145-         when = "call" ,
146-     )
147-     report  =  self .ihook .pytest_runtest_makereport (item = self , call = call_info )
148-     sub_report  =  SubTestReport ._from_test_report (report )
149-     sub_report .context  =  SubTestContext (msg , dict (test .params ))  # type: ignore[attr-defined] 
150-     self .ihook .pytest_runtest_logreport (report = sub_report )
151-     if  check_interactive_exception (call_info , sub_report ):
152-         self .ihook .pytest_exception_interact (
153-             node = self , call = call_info , report = sub_report 
154-         )
155- 
156-     # For python < 3.11: add non-subtest skips once all subtest failures are processed by # `_addSubTest`. 
157-     if  sys .version_info  <  (3 , 11 ):
158-         from  unittest .case  import  _SubTest   # type: ignore[attr-defined] 
159- 
160-         non_subtest_skip  =  [
161-             (x , y )
162-             for  x , y  in  self .instance ._outcome .skipped 
163-             if  not  isinstance (x , _SubTest )
164-         ]
165-         subtest_errors  =  [
166-             (x , y )
167-             for  x , y  in  self .instance ._outcome .errors 
168-             if  isinstance (x , _SubTest ) and  y  is  not None 
169-         ]
170-         # Check if we have non-subtest skips: if there are also sub failures, non-subtest skips are not treated in 
171-         # `_addSubTest` and have to be added using `_originaladdSkip` after all subtest failures are processed. 
172-         if  len (non_subtest_skip ) >  0  and  len (subtest_errors ) >  0 :
173-             # Make sure we have processed the last subtest failure 
174-             last_subset_error  =  subtest_errors [- 1 ]
175-             if  exc_info  is  last_subset_error [- 1 ]:
176-                 # Add non-subtest skips (as they could not be treated in `_addSkip`) 
177-                 for  testcase , reason  in  non_subtest_skip :
178-                     self ._originaladdSkip (testcase , reason )  # type: ignore[attr-defined] 
179- 
180- 
181- def  pytest_configure (config : Config ) ->  None :
182-     TestCaseFunction .addSubTest  =  _addSubTest   # type: ignore[attr-defined] 
183-     TestCaseFunction .failfast  =  False   # type: ignore[attr-defined] 
184-     # This condition is to prevent `TestCaseFunction._originaladdSkip` being assigned again in a subprocess from a 
185-     # parent python process where `addSkip` is already `_addSkip`. A such case is when running tests in 
186-     # `test_subtests.py` where `pytester.runpytest` is used. Without this guard condition, `_originaladdSkip` is 
187-     # assigned to `_addSkip` which is wrong as well as causing an infinite recursion in some cases. 
188-     if  not  hasattr (TestCaseFunction , "_originaladdSkip" ):
189-         TestCaseFunction ._originaladdSkip  =  TestCaseFunction .addSkip   # type: ignore[attr-defined] 
190-     TestCaseFunction .addSkip  =  _addSkip   # type: ignore[method-assign] 
191- 
192-     # Hack (#86): the terminal does not know about the "subtests" 
193-     # status, so it will by default turn the output to yellow. 
194-     # This forcibly adds the new 'subtests' status. 
195-     import  _pytest .terminal 
196- 
197-     new_types  =  tuple (
198-         f"subtests { outcome }   for  outcome  in  ("passed" , "failed" , "skipped" )
199-     )
200-     # We need to check if we are not re-adding because we run our own tests 
201-     # with pytester in-process mode, so this will be called multiple times. 
202-     if  new_types [0 ] not  in _pytest .terminal .KNOWN_TYPES :
203-         _pytest .terminal .KNOWN_TYPES  =  _pytest .terminal .KNOWN_TYPES  +  new_types   # type: ignore[assignment] 
204- 
205-     _pytest .terminal ._color_for_type .update (
206-         {
207-             f"subtests { outcome }  : _pytest .terminal ._color_for_type [outcome ]
208-             for  outcome  in  ("passed" , "failed" , "skipped" )
209-             if  outcome  in  _pytest .terminal ._color_for_type 
210-         }
211-     )
212- 
213- 
214- def  pytest_unconfigure () ->  None :
215-     if  hasattr (TestCaseFunction , "addSubTest" ):
216-         del  TestCaseFunction .addSubTest 
217-     if  hasattr (TestCaseFunction , "failfast" ):
218-         del  TestCaseFunction .failfast 
219-     if  hasattr (TestCaseFunction , "_originaladdSkip" ):
220-         TestCaseFunction .addSkip  =  TestCaseFunction ._originaladdSkip   # type: ignore[method-assign] 
221-         del  TestCaseFunction ._originaladdSkip 
222- 
223- 
224106@fixture  
225107def  subtests (request : SubRequest ) ->  Generator [SubTests , None , None ]:
226108    """Provides subtests functionality.""" 
@@ -293,7 +175,7 @@ class _SubTestContextManager:
293175
294176    Note: initially this logic was implemented directly in SubTests.test() as a @contextmanager, however 
295177    it is not possible to control the output fully when exiting from it due to an exception when 
296-     in --exitfirst mode, so this was refactored into an explicit context manager class (#134). 
178+     in --exitfirst mode, so this was refactored into an explicit context manager class (pytest-dev/pytest-subtests #134). 
297179    """ 
298180
299181    ihook : pluggy .HookRelay 
@@ -390,11 +272,9 @@ def capturing_output(request: SubRequest) -> Iterator[Captured]:
390272    capture_fixture_active  =  getattr (capman , "_capture_fixture" , None )
391273
392274    if  option  ==  "sys"  and  not  capture_fixture_active :
393-         with  ignore_pytest_private_warning ():
394-             fixture  =  CaptureFixture (SysCapture , request )
275+         fixture  =  CaptureFixture (SysCapture , request , _ispytest = True )
395276    elif  option  ==  "fd"  and  not  capture_fixture_active :
396-         with  ignore_pytest_private_warning ():
397-             fixture  =  CaptureFixture (FDCapture , request )
277+         fixture  =  CaptureFixture (FDCapture , request , _ispytest = True )
398278    else :
399279        fixture  =  None 
400280
@@ -428,20 +308,7 @@ def capturing_logs(
428308            yield  captured_logs 
429309
430310
431- @contextmanager  
432- def  ignore_pytest_private_warning () ->  Generator [None , None , None ]:
433-     import  warnings 
434- 
435-     with  warnings .catch_warnings ():
436-         warnings .filterwarnings (
437-             "ignore" ,
438-             "A private pytest class or function was used." ,
439-             category = PytestDeprecationWarning ,
440-         )
441-         yield 
442- 
443- 
444- @dataclasses .dataclass () 
311+ @dataclasses .dataclass  
445312class  Captured :
446313    out : str  =  "" 
447314    err : str  =  "" 
@@ -453,12 +320,12 @@ def update_report(self, report: TestReport) -> None:
453320            report .sections .append (("Captured stderr call" , self .err ))
454321
455322
323+ @dataclasses .dataclass  
456324class  CapturedLogs :
457-     def  __init__ (self , handler : LogCaptureHandler ) ->  None :
458-         self ._handler  =  handler 
325+     handler : LogCaptureHandler 
459326
460327    def  update_report (self , report : TestReport ) ->  None :
461-         report .sections .append (("Captured log call" , self ._handler .stream .getvalue ()))
328+         report .sections .append (("Captured log call" , self .handler .stream .getvalue ()))
462329
463330
464331class  NullCapturedLogs :
0 commit comments