Skip to content

Commit 557e6b7

Browse files
This allows the test runner to execute test_multiprocessing and test_asyncio in parallel.
By sharding the individual tests in test_multiprocessing and test_asyncio we are able to reduce considerably the time it takes to run the whole test suite.
1 parent 44b5c21 commit 557e6b7

File tree

1 file changed

+51
-34
lines changed

1 file changed

+51
-34
lines changed

Lib/test/libregrtest/runtest.py

+51-34
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919

2020
class TestResult:
2121
def __init__(
22-
self,
23-
name: str,
24-
duration_sec: float = 0.0,
25-
xml_data: list[str] | None = None,
22+
self,
23+
name: str,
24+
duration_sec: float = 0.0,
25+
xml_data: list[str] | None = None,
2626
) -> None:
2727
self.name = name
2828
self.duration_sec = duration_sec
@@ -39,12 +39,12 @@ def __str__(self) -> str:
3939

4040
class Failed(TestResult):
4141
def __init__(
42-
self,
43-
name: str,
44-
duration_sec: float = 0.0,
45-
xml_data: list[str] | None = None,
46-
errors: list[tuple[str, str]] | None = None,
47-
failures: list[tuple[str, str]] | None = None,
42+
self,
43+
name: str,
44+
duration_sec: float = 0.0,
45+
xml_data: list[str] | None = None,
46+
errors: list[tuple[str, str]] | None = None,
47+
failures: list[tuple[str, str]] | None = None,
4848
) -> None:
4949
super().__init__(name, duration_sec=duration_sec, xml_data=xml_data)
5050
self.errors = errors
@@ -128,21 +128,30 @@ def __str__(self) -> str:
128128
# small set of tests to determine if we have a basically functioning interpreter
129129
# (i.e. if any of these fail, then anything else is likely to follow)
130130
STDTESTS = [
131-
'test_grammar',
132-
'test_opcodes',
133-
'test_dict',
134-
'test_builtin',
135-
'test_exceptions',
136-
'test_types',
137-
'test_unittest',
138-
'test_doctest',
139-
'test_doctest2',
140-
'test_support'
131+
'test_grammar',
132+
'test_opcodes',
133+
'test_dict',
134+
'test_builtin',
135+
'test_exceptions',
136+
'test_types',
137+
'test_unittest',
138+
'test_doctest',
139+
'test_doctest2',
140+
'test_support'
141141
]
142142

143143
# set of tests that we don't want to be executed when using regrtest
144144
NOTTESTS = set()
145145

146+
#If these test directories are encountered recurse into them and treat each
147+
# test_ .py or dir as a separate test module. This can increase parallelism.
148+
# Beware this can't generally be done for any directory with sub-tests as the
149+
# __init__.py may do things which alter what tests are to be run.
150+
151+
SPLITTESTDIRS = {
152+
"test_asyncio",
153+
"test_compiler",
154+
}
146155

147156
# Storage of uncollectable objects
148157
FOUND_GARBAGE = []
@@ -158,16 +167,24 @@ def findtestdir(path=None):
158167
return path or os.path.dirname(os.path.dirname(__file__)) or os.curdir
159168

160169

161-
def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS):
170+
def findtests(testdir=None, stdtests=STDTESTS, nottests=NOTTESTS, splittestdirs=SPLITTESTDIRS, base_mod=""):
162171
"""Return a list of all applicable test modules."""
163172
testdir = findtestdir(testdir)
164173
names = os.listdir(testdir)
165174
tests = []
166175
others = set(stdtests) | nottests
167176
for name in names:
168177
mod, ext = os.path.splitext(name)
169-
if mod[:5] == "test_" and ext in (".py", "") and mod not in others:
170-
tests.append(mod)
178+
if mod[:5] == "test_" and mod not in others:
179+
if mod in splittestdirs:
180+
subdir = os.path.join(testdir, mod)
181+
if len(base_mod):
182+
mod = f"{base_mod}.{mod}"
183+
else:
184+
mod = f"test.{mod}"
185+
tests.extend(findtests(subdir, [], nottests, splittestdirs, mod))
186+
elif ext in (".py", ""):
187+
tests.append(f"{base_mod}.{mod}" if len(base_mod) else mod)
171188
return stdtests + sorted(tests)
172189

173190

@@ -186,7 +203,7 @@ def _runtest(ns: Namespace, test_name: str) -> TestResult:
186203
output_on_failure = ns.verbose3
187204

188205
use_timeout = (
189-
ns.timeout is not None and threading_helper.can_start_thread
206+
ns.timeout is not None and threading_helper.can_start_thread
190207
)
191208
if use_timeout:
192209
faulthandler.dump_traceback_later(ns.timeout, exit=True)
@@ -217,7 +234,7 @@ def _runtest(ns: Namespace, test_name: str) -> TestResult:
217234
print_warning.orig_stderr = stream
218235

219236
result = _runtest_inner(ns, test_name,
220-
display_failure=False)
237+
display_failure=False)
221238
if not isinstance(result, Passed):
222239
output = stream.getvalue()
223240
finally:
@@ -233,13 +250,13 @@ def _runtest(ns: Namespace, test_name: str) -> TestResult:
233250
support.verbose = ns.verbose
234251

235252
result = _runtest_inner(ns, test_name,
236-
display_failure=not ns.verbose)
253+
display_failure=not ns.verbose)
237254

238255
if xml_list:
239256
import xml.etree.ElementTree as ET
240257
result.xml_data = [
241-
ET.tostring(x).decode('us-ascii')
242-
for x in xml_list
258+
ET.tostring(x).decode('us-ascii')
259+
for x in xml_list
243260
]
244261

245262
result.duration_sec = time.perf_counter() - start_time
@@ -267,7 +284,7 @@ def runtest(ns: Namespace, test_name: str) -> TestResult:
267284
if not ns.pgo:
268285
msg = traceback.format_exc()
269286
print(f"test {test_name} crashed -- {msg}",
270-
file=sys.stderr, flush=True)
287+
file=sys.stderr, flush=True)
271288
return Failed(test_name)
272289

273290

@@ -328,7 +345,7 @@ def _runtest_inner2(ns: Namespace, test_name: str) -> bool:
328345
if gc.garbage:
329346
support.environment_altered = True
330347
print_warning(f"{test_name} created {len(gc.garbage)} "
331-
f"uncollectable object(s).")
348+
f"uncollectable object(s).")
332349

333350
# move the uncollectable objects somewhere,
334351
# so we don't see them again
@@ -341,7 +358,7 @@ def _runtest_inner2(ns: Namespace, test_name: str) -> bool:
341358

342359

343360
def _runtest_inner(
344-
ns: Namespace, test_name: str, display_failure: bool = True
361+
ns: Namespace, test_name: str, display_failure: bool = True
345362
) -> TestResult:
346363
# Detect environment changes, handle exceptions.
347364

@@ -387,7 +404,7 @@ def _runtest_inner(
387404
if not ns.pgo:
388405
msg = traceback.format_exc()
389406
print(f"test {test_name} crashed -- {msg}",
390-
file=sys.stderr, flush=True)
407+
file=sys.stderr, flush=True)
391408
return UncaughtException(test_name)
392409

393410
if refleak:
@@ -415,7 +432,7 @@ def cleanup_test_droppings(test_name: str, verbose: int) -> None:
415432
kind, nuker = "file", os.unlink
416433
else:
417434
raise RuntimeError(f"os.path says {name!r} exists but is neither "
418-
f"directory nor file")
435+
f"directory nor file")
419436

420437
if verbose:
421438
print_warning(f"{test_name} left behind {kind} {name!r}")
@@ -428,4 +445,4 @@ def cleanup_test_droppings(test_name: str, verbose: int) -> None:
428445
nuker(name)
429446
except Exception as exc:
430447
print_warning(f"{test_name} left behind {kind} {name!r} "
431-
f"and it couldn't be removed: {exc}")
448+
f"and it couldn't be removed: {exc}")

0 commit comments

Comments
 (0)