2008-08-11 20:11:25 +08:00
|
|
|
import sys
|
|
|
|
import time
|
|
|
|
|
2015-01-13 04:20:40 +08:00
|
|
|
from django.apps import apps
|
2008-08-11 20:11:25 +08:00
|
|
|
from django.conf import settings
|
2015-01-13 04:20:40 +08:00
|
|
|
from django.core import serializers
|
|
|
|
from django.db import router
|
2014-06-09 10:30:15 +08:00
|
|
|
from django.utils.six import StringIO
|
2015-01-13 04:20:40 +08:00
|
|
|
from django.utils.six.moves import input
|
2008-08-11 20:11:25 +08:00
|
|
|
|
|
|
|
# The prefix to put on the default database name when creating
|
|
|
|
# the test database.
|
|
|
|
TEST_DATABASE_PREFIX = 'test_'
|
|
|
|
|
2012-01-30 16:27:50 +08:00
|
|
|
|
2008-08-11 20:11:25 +08:00
|
|
|
class BaseDatabaseCreation(object):
|
2007-09-15 00:44:53 +08:00
|
|
|
"""
|
|
|
|
This class encapsulates all backend-specific differences that pertain to
|
2014-12-31 00:39:30 +08:00
|
|
|
creation and destruction of the test database.
|
2007-09-15 00:44:53 +08:00
|
|
|
"""
|
2008-08-11 20:11:25 +08:00
|
|
|
def __init__(self, connection):
|
|
|
|
self.connection = connection
|
2008-08-13 00:06:55 +08:00
|
|
|
|
2014-12-09 22:27:20 +08:00
|
|
|
@property
|
2013-11-09 16:41:03 +08:00
|
|
|
def _nodb_connection(self):
|
|
|
|
"""
|
2014-12-09 22:27:20 +08:00
|
|
|
Used to be defined here, now moved to DatabaseWrapper.
|
2013-11-09 16:41:03 +08:00
|
|
|
"""
|
2014-12-09 22:27:20 +08:00
|
|
|
return self.connection._nodb_connection
|
2013-11-09 16:41:03 +08:00
|
|
|
|
2014-06-09 23:12:28 +08:00
|
|
|
def create_test_db(self, verbosity=1, autoclobber=False, serialize=True, keepdb=False):
|
2008-08-11 20:11:25 +08:00
|
|
|
"""
|
|
|
|
Creates a test database, prompting the user for confirmation if the
|
|
|
|
database already exists. Returns the name of the test database created.
|
|
|
|
"""
|
2010-12-22 05:32:59 +08:00
|
|
|
# Don't import django.core.management if it isn't needed.
|
|
|
|
from django.core.management import call_command
|
|
|
|
|
2010-12-09 07:48:28 +08:00
|
|
|
test_database_name = self._get_test_db_name()
|
2008-08-11 20:11:25 +08:00
|
|
|
|
2010-11-22 01:18:26 +08:00
|
|
|
if verbosity >= 1:
|
|
|
|
test_db_repr = ''
|
2014-05-28 05:13:08 +08:00
|
|
|
action = 'Creating'
|
2010-11-22 01:18:26 +08:00
|
|
|
if verbosity >= 2:
|
|
|
|
test_db_repr = " ('%s')" % test_database_name
|
2014-05-28 05:13:08 +08:00
|
|
|
if keepdb:
|
|
|
|
action = "Using existing"
|
2010-11-22 01:18:26 +08:00
|
|
|
|
2014-05-28 05:13:08 +08:00
|
|
|
print("%s test database for alias '%s'%s..." % (
|
|
|
|
action, self.connection.alias, test_db_repr))
|
|
|
|
|
|
|
|
# We could skip this call if keepdb is True, but we instead
|
|
|
|
# give it the keepdb param. This is to handle the case
|
|
|
|
# where the test DB doesn't exist, in which case we need to
|
|
|
|
# create it, then just not destroy it. If we instead skip
|
|
|
|
# this, we will get an exception.
|
|
|
|
self._create_test_db(verbosity, autoclobber, keepdb)
|
2010-12-09 07:48:28 +08:00
|
|
|
|
2008-08-11 20:11:25 +08:00
|
|
|
self.connection.close()
|
2012-11-27 04:45:21 +08:00
|
|
|
settings.DATABASES[self.connection.alias]["NAME"] = test_database_name
|
2009-12-22 23:18:51 +08:00
|
|
|
self.connection.settings_dict["NAME"] = test_database_name
|
2010-10-11 20:55:17 +08:00
|
|
|
|
2014-06-09 10:30:15 +08:00
|
|
|
# We report migrate messages at one level lower than that requested.
|
2010-08-07 14:58:14 +08:00
|
|
|
# This ensures we don't get flooded with messages during testing
|
2014-06-09 10:30:15 +08:00
|
|
|
# (unless you really ask to be flooded).
|
|
|
|
call_command(
|
|
|
|
'migrate',
|
2011-01-19 00:43:01 +08:00
|
|
|
verbosity=max(verbosity - 1, 0),
|
|
|
|
interactive=False,
|
|
|
|
database=self.connection.alias,
|
2014-12-27 02:56:08 +08:00
|
|
|
run_syncdb=True,
|
2014-06-09 10:30:15 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
# We then serialize the current state of the database into a string
|
|
|
|
# and store it on the connection. This slightly horrific process is so people
|
|
|
|
# who are testing on databases without transactions or who are using
|
|
|
|
# a TransactionTestCase still get a clean database on every test run.
|
|
|
|
if serialize:
|
|
|
|
self.connection._test_serialized_contents = self.serialize_db_to_string()
|
|
|
|
|
2013-01-06 06:43:01 +08:00
|
|
|
call_command('createcachetable', database=self.connection.alias)
|
2008-08-11 20:11:25 +08:00
|
|
|
|
2014-01-09 23:05:15 +08:00
|
|
|
# Ensure a connection for the side effect of initializing the test database.
|
|
|
|
self.connection.ensure_connection()
|
2008-08-11 20:11:25 +08:00
|
|
|
|
|
|
|
return test_database_name
|
|
|
|
|
2015-06-04 23:24:31 +08:00
|
|
|
def set_as_test_mirror(self, primary_settings_dict):
|
|
|
|
"""
|
|
|
|
Set this database up to be used in testing as a mirror of a primary database
|
|
|
|
whose settings are given
|
|
|
|
"""
|
|
|
|
self.connection.settings_dict['NAME'] = primary_settings_dict['NAME']
|
|
|
|
|
2014-06-09 10:30:15 +08:00
|
|
|
def serialize_db_to_string(self):
|
|
|
|
"""
|
|
|
|
Serializes all data in the database into a JSON string.
|
|
|
|
Designed only for test runner usage; will not handle large
|
|
|
|
amounts of data.
|
|
|
|
"""
|
|
|
|
# Build list of all apps to serialize
|
|
|
|
from django.db.migrations.loader import MigrationLoader
|
|
|
|
loader = MigrationLoader(self.connection)
|
|
|
|
app_list = []
|
|
|
|
for app_config in apps.get_app_configs():
|
|
|
|
if (
|
|
|
|
app_config.models_module is not None and
|
|
|
|
app_config.label in loader.migrated_apps and
|
|
|
|
app_config.name not in settings.TEST_NON_SERIALIZED_APPS
|
|
|
|
):
|
|
|
|
app_list.append((app_config, None))
|
2014-06-09 23:12:28 +08:00
|
|
|
|
2014-06-09 10:30:15 +08:00
|
|
|
# Make a function to iteratively return every object
|
|
|
|
def get_objects():
|
2014-10-15 23:37:23 +08:00
|
|
|
for model in serializers.sort_dependencies(app_list):
|
2015-04-05 00:07:46 +08:00
|
|
|
if (model._meta.can_migrate(self.connection) and
|
2015-02-19 15:27:58 +08:00
|
|
|
router.allow_migrate_model(self.connection.alias, model)):
|
2014-06-09 10:30:15 +08:00
|
|
|
queryset = model._default_manager.using(self.connection.alias).order_by(model._meta.pk.name)
|
|
|
|
for obj in queryset.iterator():
|
|
|
|
yield obj
|
2015-01-20 22:54:12 +08:00
|
|
|
# Serialize to a string
|
2014-06-09 10:30:15 +08:00
|
|
|
out = StringIO()
|
|
|
|
serializers.serialize("json", get_objects(), indent=None, stream=out)
|
|
|
|
return out.getvalue()
|
|
|
|
|
|
|
|
def deserialize_db_from_string(self, data):
|
|
|
|
"""
|
|
|
|
Reloads the database with data from a string generated by
|
|
|
|
the serialize_db_to_string method.
|
|
|
|
"""
|
|
|
|
data = StringIO(data)
|
|
|
|
for obj in serializers.deserialize("json", data, using=self.connection.alias):
|
|
|
|
obj.save()
|
|
|
|
|
2010-12-09 07:48:28 +08:00
|
|
|
def _get_test_db_name(self):
|
|
|
|
"""
|
2010-12-21 09:36:09 +08:00
|
|
|
Internal implementation - returns the name of the test DB that will be
|
2010-12-09 07:48:28 +08:00
|
|
|
created. Only useful when called from create_test_db() and
|
|
|
|
_create_test_db() and when no external munging is done with the 'NAME'
|
|
|
|
or 'TEST_NAME' settings.
|
|
|
|
"""
|
2014-01-20 08:45:29 +08:00
|
|
|
if self.connection.settings_dict['TEST']['NAME']:
|
|
|
|
return self.connection.settings_dict['TEST']['NAME']
|
2010-12-09 07:48:28 +08:00
|
|
|
return TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
|
|
|
|
|
2014-05-28 05:13:08 +08:00
|
|
|
def _create_test_db(self, verbosity, autoclobber, keepdb=False):
|
2012-01-30 16:27:50 +08:00
|
|
|
"""
|
|
|
|
Internal implementation - creates the test db tables.
|
|
|
|
"""
|
2008-08-11 20:11:25 +08:00
|
|
|
suffix = self.sql_table_creation_suffix()
|
2008-08-13 00:06:55 +08:00
|
|
|
|
2010-12-09 07:48:28 +08:00
|
|
|
test_database_name = self._get_test_db_name()
|
2008-08-11 20:11:25 +08:00
|
|
|
|
|
|
|
qn = self.connection.ops.quote_name
|
|
|
|
|
2013-03-06 03:40:57 +08:00
|
|
|
# Create the test database and connect to it.
|
2014-01-09 23:05:15 +08:00
|
|
|
with self._nodb_connection.cursor() as cursor:
|
|
|
|
try:
|
|
|
|
cursor.execute(
|
|
|
|
"CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
|
|
|
|
except Exception as e:
|
2014-05-28 05:13:08 +08:00
|
|
|
# if we want to keep the db, then no need to do any of the below,
|
|
|
|
# just return and skip it all.
|
|
|
|
if keepdb:
|
|
|
|
return test_database_name
|
|
|
|
|
2014-01-09 23:05:15 +08:00
|
|
|
sys.stderr.write(
|
|
|
|
"Got an error creating the test database: %s\n" % e)
|
|
|
|
if not autoclobber:
|
|
|
|
confirm = input(
|
|
|
|
"Type 'yes' if you would like to try deleting the test "
|
|
|
|
"database '%s', or 'no' to cancel: " % test_database_name)
|
|
|
|
if autoclobber or confirm == 'yes':
|
|
|
|
try:
|
|
|
|
if verbosity >= 1:
|
|
|
|
print("Destroying old test database '%s'..."
|
|
|
|
% self.connection.alias)
|
|
|
|
cursor.execute(
|
|
|
|
"DROP DATABASE %s" % qn(test_database_name))
|
|
|
|
cursor.execute(
|
|
|
|
"CREATE DATABASE %s %s" % (qn(test_database_name),
|
|
|
|
suffix))
|
|
|
|
except Exception as e:
|
|
|
|
sys.stderr.write(
|
|
|
|
"Got an error recreating the test database: %s\n" % e)
|
|
|
|
sys.exit(2)
|
|
|
|
else:
|
|
|
|
print("Tests cancelled.")
|
|
|
|
sys.exit(1)
|
2008-08-13 00:06:55 +08:00
|
|
|
|
2008-08-11 20:11:25 +08:00
|
|
|
return test_database_name
|
2009-03-01 16:13:38 +08:00
|
|
|
|
2014-06-06 05:42:08 +08:00
|
|
|
def destroy_test_db(self, old_database_name, verbosity=1, keepdb=False):
|
2008-08-11 20:11:25 +08:00
|
|
|
"""
|
|
|
|
Destroy a test database, prompting the user for confirmation if the
|
2012-02-29 05:23:45 +08:00
|
|
|
database already exists.
|
2008-08-11 20:11:25 +08:00
|
|
|
"""
|
|
|
|
self.connection.close()
|
2009-12-22 23:18:51 +08:00
|
|
|
test_database_name = self.connection.settings_dict['NAME']
|
2010-11-22 01:18:26 +08:00
|
|
|
if verbosity >= 1:
|
|
|
|
test_db_repr = ''
|
2014-06-06 05:42:08 +08:00
|
|
|
action = 'Destroying'
|
2010-11-22 01:18:26 +08:00
|
|
|
if verbosity >= 2:
|
|
|
|
test_db_repr = " ('%s')" % test_database_name
|
2014-06-06 05:42:08 +08:00
|
|
|
if keepdb:
|
|
|
|
action = 'Preserving'
|
|
|
|
print("%s test database for alias '%s'%s..." % (
|
|
|
|
action, self.connection.alias, test_db_repr))
|
2012-01-30 16:27:50 +08:00
|
|
|
|
2014-06-06 05:42:08 +08:00
|
|
|
# if we want to preserve the database
|
|
|
|
# skip the actual destroying piece.
|
|
|
|
if not keepdb:
|
|
|
|
self._destroy_test_db(test_database_name, verbosity)
|
2008-08-13 00:06:55 +08:00
|
|
|
|
2014-10-24 20:53:58 +08:00
|
|
|
# Restore the original database name
|
|
|
|
settings.DATABASES[self.connection.alias]["NAME"] = old_database_name
|
|
|
|
self.connection.settings_dict["NAME"] = old_database_name
|
|
|
|
|
2008-08-11 20:11:25 +08:00
|
|
|
def _destroy_test_db(self, test_database_name, verbosity):
|
2012-01-30 16:27:50 +08:00
|
|
|
"""
|
|
|
|
Internal implementation - remove the test db tables.
|
|
|
|
"""
|
2008-08-11 20:11:25 +08:00
|
|
|
# Remove the test database to clean up after
|
|
|
|
# ourselves. Connect to the previous database (not the test database)
|
|
|
|
# to do so, because it's not allowed to delete a database while being
|
|
|
|
# connected to it.
|
2015-05-15 01:27:31 +08:00
|
|
|
with self.connection._nodb_connection.cursor() as cursor:
|
2014-01-09 23:05:15 +08:00
|
|
|
# Wait to avoid "database is being accessed by other users" errors.
|
|
|
|
time.sleep(1)
|
|
|
|
cursor.execute("DROP DATABASE %s"
|
|
|
|
% self.connection.ops.quote_name(test_database_name))
|
2008-08-11 20:11:25 +08:00
|
|
|
|
|
|
|
def sql_table_creation_suffix(self):
|
2012-01-30 16:27:50 +08:00
|
|
|
"""
|
|
|
|
SQL to append to the end of the test table creation statements.
|
|
|
|
"""
|
2008-08-11 20:11:25 +08:00
|
|
|
return ''
|
2011-02-02 22:02:14 +08:00
|
|
|
|
|
|
|
def test_db_signature(self):
|
|
|
|
"""
|
|
|
|
Returns a tuple with elements of self.connection.settings_dict (a
|
|
|
|
DATABASES setting value) that uniquely identify a database
|
|
|
|
accordingly to the RDBMS particularities.
|
|
|
|
"""
|
|
|
|
settings_dict = self.connection.settings_dict
|
|
|
|
return (
|
|
|
|
settings_dict['HOST'],
|
|
|
|
settings_dict['PORT'],
|
|
|
|
settings_dict['ENGINE'],
|
|
|
|
settings_dict['NAME']
|
|
|
|
)
|