Skip to content

Commit d559793

Browse files
felixxmnessita
authored andcommitted
Fixed #36531 -- Added forkserver support to parallel test runner.
1 parent d4dd3e5 commit d559793

5 files changed

Lines changed: 29 additions & 14 deletions

File tree

django/db/backends/sqlite3/creation.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def get_test_db_clone_settings(self, suffix):
6262
start_method = multiprocessing.get_start_method()
6363
if start_method == "fork":
6464
return orig_settings_dict
65-
if start_method == "spawn":
65+
if start_method in {"forkserver", "spawn"}:
6666
return {
6767
**orig_settings_dict,
6868
"NAME": f"{self.connection.alias}_{suffix}.sqlite3",
@@ -99,9 +99,9 @@ def _clone_test_db(self, suffix, verbosity, keepdb=False):
9999
self.log("Got an error cloning the test database: %s" % e)
100100
sys.exit(2)
101101
# Forking automatically makes a copy of an in-memory database.
102-
# Spawn requires migrating to disk which will be re-opened in
103-
# setup_worker_connection.
104-
elif multiprocessing.get_start_method() == "spawn":
102+
# Forkserver and spawn require migrating to disk which will be
103+
# re-opened in setup_worker_connection.
104+
elif multiprocessing.get_start_method() in {"forkserver", "spawn"}:
105105
ondisk_db = sqlite3.connect(target_database_name, uri=True)
106106
self.connection.connection.backup(ondisk_db)
107107
ondisk_db.close()
@@ -137,7 +137,7 @@ def setup_worker_connection(self, _worker_id):
137137
# Update settings_dict in place.
138138
self.connection.settings_dict.update(settings_dict)
139139
self.connection.close()
140-
elif start_method == "spawn":
140+
elif start_method in {"forkserver", "spawn"}:
141141
alias = self.connection.alias
142142
connection_str = (
143143
f"file:memorydb_{alias}_{_worker_id}?mode=memory&cache=shared"

django/test/runner.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -404,8 +404,9 @@ def get_max_test_processes():
404404
The maximum number of test processes when using the --parallel option.
405405
"""
406406
# The current implementation of the parallel test runner requires
407-
# multiprocessing to start subprocesses with fork() or spawn().
408-
if multiprocessing.get_start_method() not in {"fork", "spawn"}:
407+
# multiprocessing to start subprocesses with fork(), forkserver(), or
408+
# spawn().
409+
if multiprocessing.get_start_method() not in {"fork", "spawn", "forkserver"}:
409410
return 1
410411
try:
411412
return int(os.environ["DJANGO_TEST_PROCESSES"])
@@ -450,9 +451,12 @@ def _init_worker(
450451
counter.value += 1
451452
_worker_id = counter.value
452453

453-
start_method = multiprocessing.get_start_method()
454+
is_spawn_or_forkserver = multiprocessing.get_start_method() in {
455+
"forkserver",
456+
"spawn",
457+
}
454458

455-
if start_method == "spawn":
459+
if is_spawn_or_forkserver:
456460
if process_setup and callable(process_setup):
457461
if process_setup_args is None:
458462
process_setup_args = ()
@@ -463,7 +467,7 @@ def _init_worker(
463467
db_aliases = used_aliases if used_aliases is not None else connections
464468
for alias in db_aliases:
465469
connection = connections[alias]
466-
if start_method == "spawn":
470+
if is_spawn_or_forkserver:
467471
# Restore initial settings in spawned processes.
468472
connection.settings_dict.update(initial_settings[alias])
469473
if value := serialized_contents.get(alias):
@@ -606,7 +610,7 @@ def __iter__(self):
606610
return iter(self.subsuites)
607611

608612
def initialize_suite(self):
609-
if multiprocessing.get_start_method() == "spawn":
613+
if multiprocessing.get_start_method() in {"forkserver", "spawn"}:
610614
self.initial_settings = {
611615
alias: connections[alias].settings_dict for alias in connections
612616
}

docs/releases/6.0.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,8 @@ Templates
348348
Tests
349349
~~~~~
350350

351-
* ...
351+
* The :class:`.DiscoverRunner` now supports parallel test execution on systems
352+
using the ``forkserver`` :mod:`multiprocessing` start method.
352353

353354
URLs
354355
~~~~

tests/backends/sqlite/test_creation.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ def test_get_test_db_clone_settings_name(self):
3636
clone_settings_dict = creation_class.get_test_db_clone_settings("1")
3737
self.assertEqual(clone_settings_dict["NAME"], expected_clone_name)
3838

39-
@mock.patch.object(multiprocessing, "get_start_method", return_value="forkserver")
39+
@mock.patch.object(multiprocessing, "get_start_method", return_value="unsupported")
4040
def test_get_test_db_clone_settings_not_supported(self, *mocked_objects):
41-
msg = "Cloning with start method 'forkserver' is not supported."
41+
msg = "Cloning with start method 'unsupported' is not supported."
4242
with self.assertRaisesMessage(NotSupportedError, msg):
4343
connection.creation.get_test_db_clone_settings(1)

tests/test_runner/test_discover_runner.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ def test_get_max_test_processes_forkserver(
9797
mocked_cpu_count,
9898
):
9999
mocked_get_start_method.return_value = "forkserver"
100+
self.assertEqual(get_max_test_processes(), 12)
101+
with mock.patch.dict(os.environ, {"DJANGO_TEST_PROCESSES": "7"}):
102+
self.assertEqual(get_max_test_processes(), 7)
103+
104+
def test_get_max_test_processes_other(
105+
self,
106+
mocked_get_start_method,
107+
mocked_cpu_count,
108+
):
109+
mocked_get_start_method.return_value = "other"
100110
self.assertEqual(get_max_test_processes(), 1)
101111
with mock.patch.dict(os.environ, {"DJANGO_TEST_PROCESSES": "7"}):
102112
self.assertEqual(get_max_test_processes(), 1)

0 commit comments

Comments
 (0)