summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Johnson <me@adamj.eu>2020-05-23 19:32:22 +0100
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2021-08-03 09:57:04 +0200
commitae89daf46f83a7b39d599d289624c3377bfa4ab1 (patch)
treed13424f391dc541289db612d896542856bc4080f
parent7e38a8d66f9a4fbbf373de30f1cc6be906502559 (diff)
downloaddjango-ae89daf46f83a7b39d599d289624c3377bfa4ab1.tar.gz
Fixed #31621 -- Added support for '--parallel auto' to test management command.
-rw-r--r--django/test/runner.py28
-rw-r--r--docs/ref/django-admin.txt14
-rw-r--r--docs/releases/4.0.txt3
-rwxr-xr-xtests/runtests.py27
-rw-r--r--tests/test_runner/test_discover_runner.py17
5 files changed, 67 insertions, 22 deletions
diff --git a/django/test/runner.py b/django/test/runner.py
index b6cd1ad487..a9787d9b2f 100644
--- a/django/test/runner.py
+++ b/django/test/runner.py
@@ -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',
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 4af510731f..0a346b5af7 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -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>`.
diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt
index 825a669e00..3af4726bc9 100644
--- a/docs/releases/4.0.txt
+++ b/docs/releases/4.0.txt
@@ -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
~~~~
diff --git a/tests/runtests.py b/tests/runtests.py
index 648ac27e05..dfbc70818c 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -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',
diff --git a/tests/test_runner/test_discover_runner.py b/tests/test_runner/test_discover_runner.py
index 327a6625ae..f62f157149 100644
--- a/tests/test_runner/test_discover_runner.py
+++ b/tests/test_runner/test_discover_runner.py
@@ -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):