@@ -225,6 +225,10 @@ def setup(self) -> None:
225225 # A bound method to be called during teardown() if set (see 'runtest()').
226226 self ._explicit_tearDown : Callable [[], None ] | None = None
227227 super ().setup ()
228+ if sys .version_info < (3 , 11 ):
229+ # A cache of the subTest errors and non-subtest skips in self._outcome.
230+ # Compute and cache these lists once, instead of computing them again and again for each subtest (#13965).
231+ self ._cached_errors_and_skips : tuple [list [Any ], list [Any ]] | None = None
228232
229233 def teardown (self ) -> None :
230234 if self ._explicit_tearDown is not None :
@@ -313,11 +317,7 @@ def add_skip() -> None:
313317 # We also need to check if `self.instance._outcome` is `None` (this happens if the test
314318 # class/method is decorated with `unittest.skip`, see pytest-dev/pytest-subtests#173).
315319 if sys .version_info < (3 , 11 ) and self .instance ._outcome is not None :
316- subtest_errors = [
317- x
318- for x , y in self .instance ._outcome .errors
319- if isinstance (x , _SubTest ) and y is not None
320- ]
320+ subtest_errors , _ = self ._obtain_errors_and_skips ()
321321 if len (subtest_errors ) == 0 :
322322 add_skip ()
323323 else :
@@ -443,18 +443,8 @@ def addSubTest(
443443
444444 # For python < 3.11: add non-subtest skips once all subtest failures are processed by # `_addSubTest`.
445445 if sys .version_info < (3 , 11 ):
446- from unittest .case import _SubTest # type: ignore[attr-defined]
447-
448- non_subtest_skip = [
449- (x , y )
450- for x , y in self .instance ._outcome .skipped
451- if not isinstance (x , _SubTest )
452- ]
453- subtest_errors = [
454- (x , y )
455- for x , y in self .instance ._outcome .errors
456- if isinstance (x , _SubTest ) and y is not None
457- ]
446+ subtest_errors , non_subtest_skip = self ._obtain_errors_and_skips ()
447+
458448 # Check if we have non-subtest skips: if there are also sub failures, non-subtest skips are not treated in
459449 # `_addSubTest` and have to be added using `add_skip` after all subtest failures are processed.
460450 if len (non_subtest_skip ) > 0 and len (subtest_errors ) > 0 :
@@ -465,6 +455,30 @@ def addSubTest(
465455 for testcase , reason in non_subtest_skip :
466456 self .addSkip (testcase , reason , handle_subtests = False )
467457
458+ def _obtain_errors_and_skips (self ) -> tuple [list [Any ], list [Any ]]:
459+ """Compute or obtain the cached values for subtest errors and non-subtest skips."""
460+ from unittest .case import _SubTest # type: ignore[attr-defined]
461+
462+ assert sys .version_info < (3 , 11 ), (
463+ "This workaround only should be used in Python 3.10"
464+ )
465+ if self ._cached_errors_and_skips is not None :
466+ return self ._cached_errors_and_skips
467+
468+ subtest_errors = [
469+ (x , y )
470+ for x , y in self .instance ._outcome .errors
471+ if isinstance (x , _SubTest ) and y is not None
472+ ]
473+
474+ non_subtest_skips = [
475+ (x , y )
476+ for x , y in self .instance ._outcome .skipped
477+ if not isinstance (x , _SubTest )
478+ ]
479+ self ._cached_errors_and_skips = (subtest_errors , non_subtest_skips )
480+ return subtest_errors , non_subtest_skips
481+
468482
469483@hookimpl (tryfirst = True )
470484def pytest_runtest_makereport (item : Item , call : CallInfo [None ]) -> None :
0 commit comments