Repository navigation
Quadratic blowup when using TestCase subtests in python 3.10 #13965
Description
Activity
- addedtype: performanceperformance or memory problem/improvementperformance or memory problem/improvementtype: regressionindicates a problem that was introduced in a release which was working previouslyindicates a problem that was introduced in a release which was working previouslyplugin: unittestrelated to the unittest integration builtin pluginrelated to the unittest integration builtin pluginplugin: subtestsrelated to the subtests builtin pluginrelated to the subtests builtin plugin
on Nov 14, 2025 Interesting. From your analysis of the code, do you think there is some workaround that pytest can do on Python 3.10 only to fix it?
(I was going to suggest a workaround of disabling the subtests plugin using
-p no:subtests, but that doesn't work currently because the unittest plugin does subtest stuff even if the subtests plugin is disabled, we might want to fix that)I'm not familiar with the codebase to be able to suggest a well-thought-out solution, and I didn't see any quick and obvious workarounds.
My ideas all revolve around some kind of caching of the values
non_subtest_skip(unittest.py#448) andsubtest_errors(unittest.py#453). I crafted a somewhat hacky solution that replaces the original_Outcomeclass with a class that caches those values, and invalidates the cache when_Outcome.skippedor_Outcome.errorsis modified (either replaced completely, or some elements are removed/added/replaced). This patch makes the test withN=100in python 3.10 finish in roughly the same amount of time (6~8s) as in python 3.11. Precisely, it runs ~1.25 times longer, but this might be because the code performs additional checks in the first place in python 3.10.Let me know if this kind of approach seems reasonable.
Please open a draft pr for easy review
- added a commit that references this issue
on Nov 21, 2025 - added a commit that references this issue
on Nov 22, 2025 - added a commit that references this issue
on Nov 22, 2025 - added a commit that references this issue
on Nov 22, 2025 - added 2 commits that reference this issue
on Feb 4, 2026 - added a commit that references this issue
on Jul 4, 2026 - added a commit that references this issue
on Jul 19, 2026 - added a commit that references this issue
on Aug 31, 2026
Running the following test using pytest 9.0.0 in python 3.10 leads to a quadratic blowup:
For a given
N, it takesO(N^2)time to complete. Here are a few samples:N=10-> 10sN=20-> 35sN=30-> 73sN=40-> 130sWhen running this test using
python -m unittest test.py, it takes less than 0.5s forN=40.I traced this test a little:
TestCaseFunction.runtest()is called (unittest.py#360)testcase(result=self)(unittest.py#389) which isTestCase.run()(case.py#557)outcome = _Outcome(result)(case.py#582) and assigns toselfa line laterself._callTestMethod(testMethod)(case.py#591)with self.subTest()which isTestCase.subTest()(case.py#480)TestCase.subTest()constructs_SubTest(case.py#495)with self._outcome.testPartExecutor(self._subtest, isTest=True): yield(case.py#497) which is_Outcome.testPartExecutor()(case.py#55)_Subtestinstance is appended to the_Outcome.errorslist withNoneas error (case.py#79)TestCase.run()callsself._feedErrorsToResult(result, outcome.errors)(case.py#599)outcome.errorshas1000 * NelementsTestCase._feedErrorsToResult()iterates over all those errors (case.py#511)O(N)result.addSubTest(test.test_case, test, exc_info)(case.py#513) which isTestCaseFunction.addSubTest()(unittest.py#404)TestCaseFunction.addSubTest()eventually iterates overself.instance._outcome.errors(unittest.py#455) which is the same list asoutcome.errorsabove (1000 * Nelements)O(N), which in total gives usO(N^2)for the entire testThere might be similar issues with skipped tests (unittest.py#450). Another potential hot spot is unittest.py#318.
pip listoutput:Python:
3.10.17