Fixed #31621 -- Added support for '--parallel auto' to test management command.
This commit is contained in:
parent
7e38a8d66f
commit
ae89daf46f
|
@ -1,3 +1,4 @@
|
|||
import argparse
|
||||
import ctypes
|
||||
import faulthandler
|
||||
import hashlib
|
||||
|
@ -335,16 +336,20 @@ class RemoteTestRunner:
|
|||
return result
|
||||
|
||||
|
||||
def default_test_processes():
|
||||
"""Default number of test processes when using the --parallel option."""
|
||||
def parallel_type(value):
|
||||
"""Parse value passed to the --parallel option."""
|
||||
# The current implementation of the parallel test runner requires
|
||||
# multiprocessing to start subprocesses with fork().
|
||||
if multiprocessing.get_start_method() != 'fork':
|
||||
return 1
|
||||
try:
|
||||
return int(os.environ['DJANGO_TEST_PROCESSES'])
|
||||
except KeyError:
|
||||
if value == 'auto':
|
||||
return multiprocessing.cpu_count()
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError(
|
||||
f"{value!r} is not an integer or the string 'auto'"
|
||||
)
|
||||
|
||||
|
||||
_worker_id = 0
|
||||
|
@ -611,10 +616,17 @@ class DiscoverRunner:
|
|||
'-d', '--debug-sql', action='store_true',
|
||||
help='Prints logged SQL queries on failure.',
|
||||
)
|
||||
try:
|
||||
default_parallel = int(os.environ['DJANGO_TEST_PROCESSES'])
|
||||
except KeyError:
|
||||
default_parallel = 0
|
||||
parser.add_argument(
|
||||
'--parallel', nargs='?', default=1, type=int,
|
||||
const=default_test_processes(), metavar='N',
|
||||
help='Run tests using up to N parallel processes.',
|
||||
'--parallel', nargs='?', const='auto', default=default_parallel,
|
||||
type=parallel_type, metavar='N',
|
||||
help=(
|
||||
'Run tests using up to N parallel processes. Use the value '
|
||||
'"auto" to run one test process for each processor core.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--tag', action='append', dest='tags',
|
||||
|
|
|
@ -1467,10 +1467,12 @@ Enables :ref:`SQL logging <django-db-logger>` for failing tests. If
|
|||
Runs tests in separate parallel processes. Since modern processors have
|
||||
multiple cores, this allows running tests significantly faster.
|
||||
|
||||
By default ``--parallel`` runs one process per core according to
|
||||
:func:`multiprocessing.cpu_count()`. You can adjust the number of processes
|
||||
either by providing it as the option's value, e.g. ``--parallel=4``, or by
|
||||
setting the :envvar:`DJANGO_TEST_PROCESSES` environment variable.
|
||||
Using ``--parallel`` without a value, or with the value ``auto``, runs one test
|
||||
process per core according to :func:`multiprocessing.cpu_count()`. You can
|
||||
override this by passing the desired number of processes, e.g.
|
||||
``--parallel 4``. You can also enable ``--parallel`` without passing the flag
|
||||
by setting the :envvar:`DJANGO_TEST_PROCESSES` environment variable to the
|
||||
desired number of processes.
|
||||
|
||||
Django distributes test cases — :class:`unittest.TestCase` subclasses — to
|
||||
subprocesses. If there are fewer test cases than configured processes, Django
|
||||
|
@ -1511,6 +1513,10 @@ don't.
|
|||
in order to exchange them between processes. See
|
||||
:ref:`python:pickle-picklable` for details.
|
||||
|
||||
.. versionchanged:: 4.0
|
||||
|
||||
Support for the value ``auto`` was added.
|
||||
|
||||
.. option:: --tag TAGS
|
||||
|
||||
Runs only tests :ref:`marked with the specified tags <topics-tagging-tests>`.
|
||||
|
|
|
@ -350,6 +350,9 @@ Tests
|
|||
* Django test runner now supports a :option:`--shuffle <test --shuffle>` option
|
||||
to execute tests in a random order.
|
||||
|
||||
* The :option:`test --parallel` option now supports the value ``auto`` to run
|
||||
one test process for each processor core.
|
||||
|
||||
URLs
|
||||
~~~~
|
||||
|
||||
|
|
|
@ -23,7 +23,7 @@ else:
|
|||
from django.conf import settings
|
||||
from django.db import connection, connections
|
||||
from django.test import TestCase, TransactionTestCase
|
||||
from django.test.runner import default_test_processes
|
||||
from django.test.runner import parallel_type
|
||||
from django.test.selenium import SeleniumTestCaseBase
|
||||
from django.test.utils import NullTimeKeeper, TimeKeeper, get_runner
|
||||
from django.utils.deprecation import (
|
||||
|
@ -329,7 +329,7 @@ def actual_test_processes(parallel):
|
|||
if parallel == 0:
|
||||
# This doesn't work before django.setup() on some databases.
|
||||
if all(conn.features.can_clone_databases for conn in connections.all()):
|
||||
return default_test_processes()
|
||||
return parallel_type('auto')
|
||||
else:
|
||||
return 1
|
||||
else:
|
||||
|
@ -354,11 +354,12 @@ def django_tests(verbosity, interactive, failfast, keepdb, reverse,
|
|||
test_labels, debug_sql, parallel, tags, exclude_tags,
|
||||
test_name_patterns, start_at, start_after, pdb, buffer,
|
||||
timing, shuffle):
|
||||
actual_parallel = actual_test_processes(parallel)
|
||||
|
||||
if verbosity >= 1:
|
||||
msg = "Testing against Django installed in '%s'" % os.path.dirname(django.__file__)
|
||||
max_parallel = default_test_processes() if parallel == 0 else parallel
|
||||
if max_parallel > 1:
|
||||
msg += " with up to %d processes" % max_parallel
|
||||
if actual_parallel > 1:
|
||||
msg += " with up to %d processes" % actual_parallel
|
||||
print(msg)
|
||||
|
||||
test_labels, state = setup_run_tests(verbosity, start_at, start_after, test_labels)
|
||||
|
@ -373,7 +374,7 @@ def django_tests(verbosity, interactive, failfast, keepdb, reverse,
|
|||
keepdb=keepdb,
|
||||
reverse=reverse,
|
||||
debug_sql=debug_sql,
|
||||
parallel=actual_test_processes(parallel),
|
||||
parallel=actual_parallel,
|
||||
tags=tags,
|
||||
exclude_tags=exclude_tags,
|
||||
test_name_patterns=test_name_patterns,
|
||||
|
@ -562,10 +563,18 @@ if __name__ == "__main__":
|
|||
'--debug-sql', action='store_true',
|
||||
help='Turn on the SQL query logger within tests.',
|
||||
)
|
||||
try:
|
||||
default_parallel = int(os.environ['DJANGO_TEST_PROCESSES'])
|
||||
except KeyError:
|
||||
# actual_test_processes() converts this to "auto" later on.
|
||||
default_parallel = 0
|
||||
parser.add_argument(
|
||||
'--parallel', nargs='?', default=0, type=int,
|
||||
const=default_test_processes(), metavar='N',
|
||||
help='Run tests using up to N parallel processes.',
|
||||
'--parallel', nargs='?', const='auto', default=default_parallel,
|
||||
type=parallel_type, metavar='N',
|
||||
help=(
|
||||
'Run tests using up to N parallel processes. Use the value "auto" '
|
||||
'to run one test process for each processor core.'
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
'--tag', dest='tags', action='append',
|
||||
|
|
|
@ -50,16 +50,31 @@ class DiscoverRunnerParallelArgumentTests(SimpleTestCase):
|
|||
|
||||
def test_parallel_default(self, *mocked_objects):
|
||||
result = self.get_parser().parse_args([])
|
||||
self.assertEqual(result.parallel, 1)
|
||||
self.assertEqual(result.parallel, 0)
|
||||
|
||||
def test_parallel_flag(self, *mocked_objects):
|
||||
result = self.get_parser().parse_args(['--parallel'])
|
||||
self.assertEqual(result.parallel, 12)
|
||||
|
||||
def test_parallel_auto(self, *mocked_objects):
|
||||
result = self.get_parser().parse_args(['--parallel', 'auto'])
|
||||
self.assertEqual(result.parallel, 12)
|
||||
|
||||
def test_parallel_count(self, *mocked_objects):
|
||||
result = self.get_parser().parse_args(['--parallel', '17'])
|
||||
self.assertEqual(result.parallel, 17)
|
||||
|
||||
def test_parallel_invalid(self, *mocked_objects):
|
||||
with self.assertRaises(SystemExit), captured_stderr() as stderr:
|
||||
self.get_parser().parse_args(['--parallel', 'unaccepted'])
|
||||
msg = "argument --parallel: 'unaccepted' is not an integer or the string 'auto'"
|
||||
self.assertIn(msg, stderr.getvalue())
|
||||
|
||||
@mock.patch.dict(os.environ, {'DJANGO_TEST_PROCESSES': '7'})
|
||||
def test_parallel_env_var(self, *mocked_objects):
|
||||
result = self.get_parser().parse_args([])
|
||||
self.assertEqual(result.parallel, 7)
|
||||
|
||||
@mock.patch.dict(os.environ, {'DJANGO_TEST_PROCESSES': 'typo'})
|
||||
def test_parallel_env_var_non_int(self, *mocked_objects):
|
||||
with self.assertRaises(ValueError):
|
||||
|
|
Loading…
Reference in New Issue