Fixed #33719 -- Fixed test command crash when running in parallel.

Thanks Pēteris Caune for the report.

Regression in 3b3f38b3b0.
This commit is contained in:
Mariusz Felisiak 2022-05-19 10:20:51 +02:00 committed by GitHub
parent 981c23c0cc
commit 41c4cb253c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 9 deletions

View File

@ -419,7 +419,10 @@ def _init_worker(
start_method = multiprocessing.get_start_method() start_method = multiprocessing.get_start_method()
if start_method == "spawn": if start_method == "spawn":
process_setup(*process_setup_args) if process_setup and callable(process_setup):
if process_setup_args is None:
process_setup_args = ()
process_setup(*process_setup_args)
setup_test_environment() setup_test_environment()
for alias in connections: for alias in connections:
@ -465,6 +468,7 @@ class ParallelTestSuite(unittest.TestSuite):
# In case someone wants to modify these in a subclass. # In case someone wants to modify these in a subclass.
init_worker = _init_worker init_worker = _init_worker
process_setup_args = ()
run_subsuite = _run_subsuite run_subsuite = _run_subsuite
runner_class = RemoteTestRunner runner_class = RemoteTestRunner
@ -477,6 +481,14 @@ class ParallelTestSuite(unittest.TestSuite):
self.serialized_contents = None self.serialized_contents = None
super().__init__() super().__init__()
def process_setup(self, *args):
"""
Stub method to simplify run() implementation. "self" is never actually
passed because a function implementing this method (__func__) is
always used, not the method itself.
"""
pass
def run(self, result): def run(self, result):
""" """
Distribute test cases across workers. Distribute test cases across workers.
@ -496,11 +508,13 @@ class ParallelTestSuite(unittest.TestSuite):
counter = multiprocessing.Value(ctypes.c_int, 0) counter = multiprocessing.Value(ctypes.c_int, 0)
pool = multiprocessing.Pool( pool = multiprocessing.Pool(
processes=self.processes, processes=self.processes,
initializer=self.init_worker, initializer=self.init_worker.__func__,
initargs=[ initargs=[
counter, counter,
self.initial_settings, self.initial_settings,
self.serialized_contents, self.serialized_contents,
self.process_setup.__func__,
self.process_setup_args,
], ],
) )
args = [ args = [

View File

@ -11,7 +11,6 @@ import subprocess
import sys import sys
import tempfile import tempfile
import warnings import warnings
from functools import partial
from pathlib import Path from pathlib import Path
try: try:
@ -26,7 +25,7 @@ else:
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.db import connection, connections from django.db import connection, connections
from django.test import TestCase, TransactionTestCase from django.test import TestCase, TransactionTestCase
from django.test.runner import _init_worker, get_max_test_processes, parallel_type from django.test.runner import get_max_test_processes, parallel_type
from django.test.selenium import SeleniumTestCaseBase from django.test.selenium import SeleniumTestCaseBase
from django.test.utils import NullTimeKeeper, TimeKeeper, get_runner from django.test.utils import NullTimeKeeper, TimeKeeper, get_runner
from django.utils.deprecation import ( from django.utils.deprecation import (
@ -405,11 +404,8 @@ def django_tests(
parallel = 1 parallel = 1
TestRunner = get_runner(settings) TestRunner = get_runner(settings)
TestRunner.parallel_test_suite.init_worker = partial( TestRunner.parallel_test_suite.process_setup = setup_run_tests
_init_worker, TestRunner.parallel_test_suite.process_setup_args = process_setup_args
process_setup=setup_run_tests,
process_setup_args=process_setup_args,
)
test_runner = TestRunner( test_runner = TestRunner(
verbosity=verbosity, verbosity=verbosity,
interactive=interactive, interactive=interactive,

View File

@ -19,6 +19,7 @@ from django.test import SimpleTestCase, TransactionTestCase, skipUnlessDBFeature
from django.test.runner import ( from django.test.runner import (
DiscoverRunner, DiscoverRunner,
Shuffler, Shuffler,
_init_worker,
reorder_test_bin, reorder_test_bin,
reorder_tests, reorder_tests,
shuffle_tests, shuffle_tests,
@ -684,6 +685,46 @@ class NoInitializeSuiteTestRunnerTests(SimpleTestCase):
) )
class TestRunnerInitializerTests(SimpleTestCase):
# Raise an exception to don't actually run tests.
@mock.patch.object(
multiprocessing, "Pool", side_effect=Exception("multiprocessing.Pool()")
)
def test_no_initialize_suite_test_runner(self, mocked_pool):
class StubTestRunner(DiscoverRunner):
def setup_test_environment(self, **kwargs):
return
def setup_databases(self, **kwargs):
return
def run_checks(self, databases):
return
def teardown_databases(self, old_config, **kwargs):
return
def teardown_test_environment(self, **kwargs):
return
def run_suite(self, suite, **kwargs):
kwargs = self.get_test_runner_kwargs()
runner = self.test_runner(**kwargs)
return runner.run(suite)
runner = StubTestRunner(verbosity=0, interactive=False, parallel=2)
with self.assertRaisesMessage(Exception, "multiprocessing.Pool()"):
runner.run_tests(
[
"test_runner_apps.sample.tests_sample.TestDjangoTestCase",
"test_runner_apps.simple.tests",
]
)
# Initializer must be a function.
self.assertIs(mocked_pool.call_args.kwargs["initializer"], _init_worker)
class Ticket17477RegressionTests(AdminScriptTestCase): class Ticket17477RegressionTests(AdminScriptTestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()