Fixed #28478 -- Make DiscoverRunner skip creating unused test databases.
SimpleTestCase.databases makes it possible to determine the set of databases required to run the discovered tests.
This commit is contained in:
parent
8c775391b7
commit
41e73de39d
|
@ -391,6 +391,9 @@ class ParallelTestSuite(unittest.TestSuite):
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.subsuites)
|
||||||
|
|
||||||
|
|
||||||
class DiscoverRunner:
|
class DiscoverRunner:
|
||||||
"""A Django test runner that uses unittest2 test discovery."""
|
"""A Django test runner that uses unittest2 test discovery."""
|
||||||
|
@ -587,6 +590,27 @@ class DiscoverRunner:
|
||||||
def suite_result(self, suite, result, **kwargs):
|
def suite_result(self, suite, result, **kwargs):
|
||||||
return len(result.failures) + len(result.errors)
|
return len(result.failures) + len(result.errors)
|
||||||
|
|
||||||
|
def _get_databases(self, suite):
|
||||||
|
databases = set()
|
||||||
|
for test in suite:
|
||||||
|
if isinstance(test, unittest.TestCase):
|
||||||
|
test_databases = getattr(test, 'databases', None)
|
||||||
|
if test_databases == '__all__':
|
||||||
|
return set(connections)
|
||||||
|
if test_databases:
|
||||||
|
databases.update(test_databases)
|
||||||
|
else:
|
||||||
|
databases.update(self._get_databases(test))
|
||||||
|
return databases
|
||||||
|
|
||||||
|
def get_databases(self, suite):
|
||||||
|
databases = self._get_databases(suite)
|
||||||
|
if self.verbosity >= 2:
|
||||||
|
unused_databases = [alias for alias in connections if alias not in databases]
|
||||||
|
if unused_databases:
|
||||||
|
print('Skipping setup of unused database(s): %s.' % ', '.join(sorted(unused_databases)))
|
||||||
|
return databases
|
||||||
|
|
||||||
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
def run_tests(self, test_labels, extra_tests=None, **kwargs):
|
||||||
"""
|
"""
|
||||||
Run the unit tests for all the test labels in the provided list.
|
Run the unit tests for all the test labels in the provided list.
|
||||||
|
@ -601,7 +625,8 @@ class DiscoverRunner:
|
||||||
"""
|
"""
|
||||||
self.setup_test_environment()
|
self.setup_test_environment()
|
||||||
suite = self.build_suite(test_labels, extra_tests)
|
suite = self.build_suite(test_labels, extra_tests)
|
||||||
old_config = self.setup_databases()
|
databases = self.get_databases(suite)
|
||||||
|
old_config = self.setup_databases(aliases=databases)
|
||||||
run_failed = False
|
run_failed = False
|
||||||
try:
|
try:
|
||||||
self.run_checks()
|
self.run_checks()
|
||||||
|
|
|
@ -152,9 +152,9 @@ def teardown_test_environment():
|
||||||
del mail.outbox
|
del mail.outbox
|
||||||
|
|
||||||
|
|
||||||
def setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs):
|
def setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, aliases=None, **kwargs):
|
||||||
"""Create the test databases."""
|
"""Create the test databases."""
|
||||||
test_databases, mirrored_aliases = get_unique_databases_and_mirrors()
|
test_databases, mirrored_aliases = get_unique_databases_and_mirrors(aliases)
|
||||||
|
|
||||||
old_names = []
|
old_names = []
|
||||||
|
|
||||||
|
@ -238,7 +238,7 @@ def dependency_ordered(test_databases, dependencies):
|
||||||
return ordered_test_databases
|
return ordered_test_databases
|
||||||
|
|
||||||
|
|
||||||
def get_unique_databases_and_mirrors():
|
def get_unique_databases_and_mirrors(aliases=None):
|
||||||
"""
|
"""
|
||||||
Figure out which databases actually need to be created.
|
Figure out which databases actually need to be created.
|
||||||
|
|
||||||
|
@ -250,6 +250,8 @@ def get_unique_databases_and_mirrors():
|
||||||
where all aliases share the same underlying database.
|
where all aliases share the same underlying database.
|
||||||
- mirrored_aliases: mapping of mirror aliases to original aliases.
|
- mirrored_aliases: mapping of mirror aliases to original aliases.
|
||||||
"""
|
"""
|
||||||
|
if aliases is None:
|
||||||
|
aliases = connections
|
||||||
mirrored_aliases = {}
|
mirrored_aliases = {}
|
||||||
test_databases = {}
|
test_databases = {}
|
||||||
dependencies = {}
|
dependencies = {}
|
||||||
|
@ -262,7 +264,7 @@ def get_unique_databases_and_mirrors():
|
||||||
if test_settings['MIRROR']:
|
if test_settings['MIRROR']:
|
||||||
# If the database is marked as a test mirror, save the alias.
|
# If the database is marked as a test mirror, save the alias.
|
||||||
mirrored_aliases[alias] = test_settings['MIRROR']
|
mirrored_aliases[alias] = test_settings['MIRROR']
|
||||||
else:
|
elif alias in aliases:
|
||||||
# Store a tuple with DB parameters that uniquely identify it.
|
# Store a tuple with DB parameters that uniquely identify it.
|
||||||
# If we have two aliases with the same values for that tuple,
|
# If we have two aliases with the same values for that tuple,
|
||||||
# we only need to create the test database once.
|
# we only need to create the test database once.
|
||||||
|
|
|
@ -293,6 +293,9 @@ Tests
|
||||||
for older versions of SQLite because they would require expensive table
|
for older versions of SQLite because they would require expensive table
|
||||||
introspection there.
|
introspection there.
|
||||||
|
|
||||||
|
* :class:`~django.test.runner.DiscoverRunner` now skips the setup of databases
|
||||||
|
not :ref:`referenced by tests<testing-multi-db>`.
|
||||||
|
|
||||||
URLs
|
URLs
|
||||||
~~~~
|
~~~~
|
||||||
|
|
||||||
|
|
|
@ -614,7 +614,7 @@ utility methods in the ``django.test.utils`` module.
|
||||||
Performs global post-test teardown, such as removing instrumentation from
|
Performs global post-test teardown, such as removing instrumentation from
|
||||||
the template system and restoring normal email services.
|
the template system and restoring normal email services.
|
||||||
|
|
||||||
.. function:: setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, **kwargs)
|
.. function:: setup_databases(verbosity, interactive, keepdb=False, debug_sql=False, parallel=0, aliases=None, **kwargs)
|
||||||
|
|
||||||
Creates the test databases.
|
Creates the test databases.
|
||||||
|
|
||||||
|
@ -622,6 +622,14 @@ utility methods in the ``django.test.utils`` module.
|
||||||
that have been made. This data will be provided to the
|
that have been made. This data will be provided to the
|
||||||
:func:`teardown_databases` function at the conclusion of testing.
|
:func:`teardown_databases` function at the conclusion of testing.
|
||||||
|
|
||||||
|
The ``aliases`` argument determines which :setting:`DATABASES` aliases test
|
||||||
|
databases should be setup for. If it's not provided, it defaults to all of
|
||||||
|
:setting:`DATABASES` aliases.
|
||||||
|
|
||||||
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
|
The ``aliases`` argument was added.
|
||||||
|
|
||||||
.. function:: teardown_databases(old_config, parallel=0, keepdb=False)
|
.. function:: teardown_databases(old_config, parallel=0, keepdb=False)
|
||||||
|
|
||||||
Destroys the test databases, restoring pre-test conditions.
|
Destroys the test databases, restoring pre-test conditions.
|
||||||
|
|
|
@ -1134,13 +1134,14 @@ Multi-database support
|
||||||
.. versionadded:: 2.2
|
.. versionadded:: 2.2
|
||||||
|
|
||||||
Django sets up a test database corresponding to every database that is
|
Django sets up a test database corresponding to every database that is
|
||||||
defined in the :setting:`DATABASES` definition in your settings
|
defined in the :setting:`DATABASES` definition in your settings and referred to
|
||||||
file. However, a big part of the time taken to run a Django TestCase
|
by at least one test through ``databases``.
|
||||||
is consumed by the call to ``flush`` that ensures that you have a
|
|
||||||
clean database at the start of each test run. If you have multiple
|
However, a big part of the time taken to run a Django ``TestCase`` is consumed
|
||||||
databases, multiple flushes are required (one for each database),
|
by the call to ``flush`` that ensures that you have a clean database at the
|
||||||
which can be a time consuming activity -- especially if your tests
|
start of each test run. If you have multiple databases, multiple flushes are
|
||||||
don't need to test multi-database activity.
|
required (one for each database), which can be a time consuming activity --
|
||||||
|
especially if your tests don't need to test multi-database activity.
|
||||||
|
|
||||||
As an optimization, Django only flushes the ``default`` database at
|
As an optimization, Django only flushes the ``default`` database at
|
||||||
the start of each test run. If your setup contains multiple databases,
|
the start of each test run. If your setup contains multiple databases,
|
||||||
|
|
|
@ -3,6 +3,7 @@ from argparse import ArgumentParser
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from unittest import TestSuite, TextTestRunner, defaultTestLoader
|
from unittest import TestSuite, TextTestRunner, defaultTestLoader
|
||||||
|
|
||||||
|
from django.db import connections
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
from django.test.runner import DiscoverRunner
|
from django.test.runner import DiscoverRunner
|
||||||
from django.test.utils import captured_stdout
|
from django.test.utils import captured_stdout
|
||||||
|
@ -223,3 +224,47 @@ class DiscoverRunnerTests(SimpleTestCase):
|
||||||
with captured_stdout() as stdout:
|
with captured_stdout() as stdout:
|
||||||
runner.build_suite(['test_runner_apps.tagged.tests'])
|
runner.build_suite(['test_runner_apps.tagged.tests'])
|
||||||
self.assertIn('Excluding test tag(s): bar, foo.\n', stdout.getvalue())
|
self.assertIn('Excluding test tag(s): bar, foo.\n', stdout.getvalue())
|
||||||
|
|
||||||
|
|
||||||
|
class DiscoverRunnerGetDatabasesTests(SimpleTestCase):
|
||||||
|
runner = DiscoverRunner(verbosity=2)
|
||||||
|
skip_msg = 'Skipping setup of unused database(s): '
|
||||||
|
|
||||||
|
def get_databases(self, test_labels):
|
||||||
|
suite = self.runner.build_suite(test_labels)
|
||||||
|
with captured_stdout() as stdout:
|
||||||
|
databases = self.runner.get_databases(suite)
|
||||||
|
return databases, stdout.getvalue()
|
||||||
|
|
||||||
|
def test_mixed(self):
|
||||||
|
databases, output = self.get_databases(['test_runner_apps.databases.tests'])
|
||||||
|
self.assertEqual(databases, set(connections))
|
||||||
|
self.assertNotIn(self.skip_msg, output)
|
||||||
|
|
||||||
|
def test_all(self):
|
||||||
|
databases, output = self.get_databases(['test_runner_apps.databases.tests.AllDatabasesTests'])
|
||||||
|
self.assertEqual(databases, set(connections))
|
||||||
|
self.assertNotIn(self.skip_msg, output)
|
||||||
|
|
||||||
|
def test_default_and_other(self):
|
||||||
|
databases, output = self.get_databases([
|
||||||
|
'test_runner_apps.databases.tests.DefaultDatabaseTests',
|
||||||
|
'test_runner_apps.databases.tests.OtherDatabaseTests',
|
||||||
|
])
|
||||||
|
self.assertEqual(databases, set(connections))
|
||||||
|
self.assertNotIn(self.skip_msg, output)
|
||||||
|
|
||||||
|
def test_default_only(self):
|
||||||
|
databases, output = self.get_databases(['test_runner_apps.databases.tests.DefaultDatabaseTests'])
|
||||||
|
self.assertEqual(databases, {'default'})
|
||||||
|
self.assertIn(self.skip_msg + 'other', output)
|
||||||
|
|
||||||
|
def test_other_only(self):
|
||||||
|
databases, output = self.get_databases(['test_runner_apps.databases.tests.OtherDatabaseTests'])
|
||||||
|
self.assertEqual(databases, {'other'})
|
||||||
|
self.assertIn(self.skip_msg + 'default', output)
|
||||||
|
|
||||||
|
def test_no_databases_required(self):
|
||||||
|
databases, output = self.get_databases(['test_runner_apps.databases.tests.NoDatabaseTests'])
|
||||||
|
self.assertEqual(databases, set())
|
||||||
|
self.assertIn(self.skip_msg + 'default, other', output)
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class NoDatabaseTests(unittest.TestCase):
|
||||||
|
def test_nothing(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultDatabaseTests(NoDatabaseTests):
|
||||||
|
databases = {'default'}
|
||||||
|
|
||||||
|
|
||||||
|
class OtherDatabaseTests(NoDatabaseTests):
|
||||||
|
databases = {'other'}
|
||||||
|
|
||||||
|
|
||||||
|
class AllDatabasesTests(NoDatabaseTests):
|
||||||
|
databases = '__all__'
|
Loading…
Reference in New Issue