summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Pellerin <jpellerin@gmail.com>2006-06-30 18:03:56 +0000
committerJason Pellerin <jpellerin@gmail.com>2006-06-30 18:03:56 +0000
commit4190c9e16f59a944625829015b53bf023ee44520 (patch)
tree95be6e575ea017e72ca940cb073e758ce8c91b6e
parent763e087de19d37d93172442bc6f29fe1e0e7a430 (diff)
[multi-db] Added initialization code to runtests.py that creates
two named test databases, 'django_test_db_a' and 'django_test_db_b', along with the default test database. Moved multiple databases test to modeltests and reorganized it to match other model tests. git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3241 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--tests/modeltests/multiple_databases/__init__.py1
-rw-r--r--tests/modeltests/multiple_databases/models.py96
-rw-r--r--tests/othertests/multiple_databases.py175
-rwxr-xr-xtests/runtests.py80
4 files changed, 145 insertions, 207 deletions
diff --git a/tests/modeltests/multiple_databases/__init__.py b/tests/modeltests/multiple_databases/__init__.py
new file mode 100644
index 0000000000..2ae28399f5
--- /dev/null
+++ b/tests/modeltests/multiple_databases/__init__.py
@@ -0,0 +1 @@
+pass
diff --git a/tests/modeltests/multiple_databases/models.py b/tests/modeltests/multiple_databases/models.py
new file mode 100644
index 0000000000..3c89c83e75
--- /dev/null
+++ b/tests/modeltests/multiple_databases/models.py
@@ -0,0 +1,96 @@
+"""
+XXX. Using multiple database connections
+
+Django normally uses only a single database connection. However,
+support is available for using any number of different, named
+connections. Multiple database support is entirely optional and has
+no impact on your application if you don't use it.
+
+Named connections are defined in your settings module. Create a
+`DATABASES` variable that is a dict, mapping connection names to their
+particulars. The particulars are defined in a dict with the same keys
+as the variable names as are used to define the default connection.
+
+Access to named connections is through `django.db.connections`, which
+behaves like a dict: you access connections by name. Connections are
+established lazily, when accessed. `django.db.connections[database]`
+holds a `ConnectionInfo` instance, with the attributes:
+`DatabaseError`, `backend`, `get_introspection_module`,
+`get_creation_module`, and `runshell`.
+
+Models can define which connection to use, by name. To use a named
+connection, set the `db_connection` property in the model's Meta class
+to the name of the connection. The name used must be a key in
+settings.DATABASES, of course.
+
+To access a model's connection, use `model._meta.connection`. To find
+the backend or other connection metadata, use
+`model._meta.connection_info`.
+"""
+
+from django.db import models
+
+class Artist(models.Model):
+ name = models.CharField(maxlength=100)
+ alive = models.BooleanField(default=True)
+
+ def __str__(self):
+ return self.name
+
+ class Meta:
+ db_connection = 'django_test_db_a'
+
+class Widget(models.Model):
+ code = models.CharField(maxlength=10, unique=True)
+ weight = models.IntegerField()
+
+ def __str__(self):
+ return self.code
+
+ class Meta:
+ db_connection = 'django_test_db_b'
+
+class Vehicle(models.Model):
+ make = models.CharField(maxlength=20)
+ model = models.CharField(maxlength=20)
+ year = models.IntegerField()
+
+ def __str__(self):
+ return "%d %s %s" % (self.year, self.make, self.model)
+
+
+API_TESTS = """
+
+# See what connections are defined. django.db.connections acts like a dict.
+>>> from django.db import connection, connections
+>>> from django.conf import settings
+>>> connections.keys()
+['django_test_db_a', 'django_test_db_b']
+
+# Each connection references its settings
+>>> connections['django_test_db_a'].settings.DATABASE_NAME == settings.DATABASES['django_test_db_a']['DATABASE_NAME']
+True
+>>> connections['django_test_db_b'].settings.DATABASE_NAME == settings.DATABASES['django_test_db_b']['DATABASE_NAME']
+True
+>>> connections['django_test_db_b'].settings.DATABASE_NAME == settings.DATABASES['django_test_db_a']['DATABASE_NAME']
+False
+
+# Invalid connection names raise ImproperlyConfigured
+>>> connections['bad']
+Traceback (most recent call last):
+ ...
+ImproperlyConfigured: No database connection 'bad' has been configured
+
+# Models can access their connections through their _meta properties
+>>> Artist._meta.connection.settings == connections['django_test_db_a'].settings
+True
+>>> Widget._meta.connection.settings == connections['django_test_db_b'].settings
+True
+>>> Vehicle._meta.connection.settings == connection.settings
+True
+>>> Artist._meta.connection.settings == Widget._meta.connection.settings
+False
+>>> Artist._meta.connection.settings == Vehicle._meta.connection.settings
+False
+
+"""
diff --git a/tests/othertests/multiple_databases.py b/tests/othertests/multiple_databases.py
deleted file mode 100644
index be52757406..0000000000
--- a/tests/othertests/multiple_databases.py
+++ /dev/null
@@ -1,175 +0,0 @@
-import copy
-import os
-import sys
-import tempfile
-from django.conf import settings
-from django import db
-from runtests import doctest, DjangoDoctestRunner, error_list
-
-# database files
-fh, db_a = tempfile.mkstemp()
-os.close(fh)
-fh, db_b = tempfile.mkstemp()
-os.close(fh)
-
-# patches
-tmp_settings = None
-tmp_connections = None
-
-# tests
-test = r"""
-XXX. Using multiple database connections
-
-Django normally uses only a single database connection. However, support is
-available for connecting to any number of different, named databases.
-
-Named connections are defined in your settings module. Create a
-`DATABASES` variable that is a dict, mapping connection names to their
-particulars. The particulars are defined in a dict with the same keys
-as the variable names as are used to define the default connection.
-
-.. note::
-
- Please note that this uses the sqlite3 backend and writes temporary
- database files to disk. This test will fail unless sqlite3 is
- installed and your temp directory is writable.
-
- >>> from django.conf import settings
- >>> settings.DATABASES = {
- ... 'a': { 'DATABASE_ENGINE': 'sqlite3',
- ... 'DATABASE_NAME': db_a
- ... },
- ... 'b': { 'DATABASE_ENGINE': 'sqlite3',
- ... 'DATABASE_NAME': db_b
- ... }}
-
-Connections are established lazily, when requested by name. When
-accessed, `connections[database]` holds a `ConnectionInfo` instance,
-with the attributes: `DatabaseError`, `backend`,
-`get_introspection_module`, `get_creation_module`, and
-`runshell`. Access connections through the `connections` property of
-the `django.db` module:
-
- >>> from django.db import connection, connections
- >>> connections['a'].settings.DATABASE_NAME == db_a
- True
- >>> connections['b'].settings.DATABASE_NAME == db_b
- True
-
-Invalid connection names raise ImproperlyConfigured:
-
- >>> connections['bad']
- Traceback (most recent call last):
- ...
- ImproperlyConfigured: No database connection 'bad' has been configured
-
-Models can define which connection to use, by name. To use a named
-connection, set the `db_connection` property in the model's Meta class
-to the name of the connection. The name used must be a key in
-settings.DATABASES, of course.
-
- >>> from django.db import models
- >>> class Artist(models.Model):
- ... name = models.CharField(maxlength=100)
- ... alive = models.BooleanField(default=True)
- ...
- ... def __str__(self):
- ... return self.name
- ...
- ... class Meta:
- ... app_label = 'mdb'
- ... db_connection = 'a'
- ...
- >>> class Widget(models.Model):
- ... code = models.CharField(maxlength=10, unique=True)
- ... weight = models.IntegerField()
- ...
- ... def __str__(self):
- ... return self.code
- ...
- ... class Meta:
- ... app_label = 'mdb'
- ... db_connection = 'b'
-
-But they don't have to. Multiple database support is entirely optional
-and has no impact on your application if you don't use it.
-
- >>> class Vehicle(models.Model):
- ... make = models.CharField(maxlength=20)
- ... model = models.CharField(maxlength=20)
- ... year = models.IntegerField()
- ...
- ... def __str__(self):
- ... return "%d %s %s" % (self.year, self.make, self.model)
- ...
- ... class Meta:
- ... app_label = 'mdb'
-
- >>> Artist._meta.connection.settings.DATABASE_NAME == \
- ... connections['a'].connection.settings.DATABASE_NAME
- True
- >>> Widget._meta.connection.settings.DATABASE_NAME == \
- ... connections['b'].connection.settings.DATABASE_NAME
- True
- >>> Vehicle._meta.connection.settings.DATABASE_NAME == \
- ... connection.settings.DATABASE_NAME
- True
- >>> Artist._meta.connection.settings.DATABASE_NAME == \
- ... Widget._meta.connection.settings.DATABASE_NAME
- False
- >>> Artist._meta.connection.settings.DATABASE_NAME == \
- ... Vehicle._meta.connection.settings.DATABASE_NAME
- False
-
-"""
-
-def cleanup():
- if os.path.exists(db_a):
- os.unlink(db_a)
- if os.path.exists(db_b):
- os.unlink(db_b)
-
-def setup():
- global tmp_settings, tmp_connections
- try:
- tmp_connections = db.connections
- db.connections = db.LazyConnectionManager()
- tmp_settings = copy.copy(settings.DATABASES)
- except AttributeError:
- pass
-
-def teardown():
- try:
- db.connections = tmp_connections
- settings.DATABASES = tmp_settings
- except AttributeError:
- pass
- cleanup()
-
-def run_tests(verbosity_level):
- setup()
- try:
- main(verbosity_level)
- finally:
- teardown()
-
-def main(verbosity_level):
- mod = sys.modules[__name__]
- p = doctest.DocTestParser()
- dtest = p.get_doctest(mod.test, mod.__dict__, __name__, None, None)
- runner = DjangoDoctestRunner(verbosity_level=verbosity_level,
- verbose=False)
- runner.run(dtest, clear_globs=True, out=sys.stdout.write)
- if error_list:
- out = []
- for d in error_list:
- out.extend([d['title'], "=" * len(d['title']),
- d['description']])
-
- raise Exception, "%s multiple_databases test%s failed:\n\n %s" \
- % (len(error_list),
- len(error_list) != 1 and 's' or '',
- '\n'.join(out))
-
-if __name__ == '__main__':
- run_tests(1)
diff --git a/tests/runtests.py b/tests/runtests.py
index 2f06081749..a4622ef9d5 100755
--- a/tests/runtests.py
+++ b/tests/runtests.py
@@ -85,39 +85,25 @@ class TestRunner:
self.verbosity_level = verbosity_level
self.which_tests = which_tests
self.created_dbs = []
- self.sqlite_memory_db_used = False
self.cleanup_files = []
+ self.old_database_name = None
+ self.old_databases = None
+ self.sqlite_memory_db_used = False
def output(self, required_level, message):
if self.verbosity_level > required_level - 1:
print message
- def create_test_db(self, connection):
+ def create_test_db(self, db_name, connection):
"""Create a test db, returning a ConnectionInfo object holding
a connection to that db.
"""
from django.db import connect
+ # settings may be a dict or settings object
settings = connection.settings
-
- # If we're using SQLite, it's more convenient to test against an
- # in-memory database. But we can only do this for the first one; after
- # that we have to use temp files.
- if settings.DATABASE_ENGINE == "sqlite3":
- if self.sqlite_memory_db_used:
- import tempfile
- fd, filename = tempfile.mkstemp()
- os.close(fd)
- db_name = filename
- self.cleanup_files.append(filename)
- else:
- db_name = ":memory:"
- self.sqlite_memory_db_used = True
- else:
- db_name = TEST_DATABASE_NAME
- if self.created_dbs:
- db_name += "_%s" % (len(self.created_dbs))
-
+
+ if settings.DATABASE_ENGINE != "sqlite3":
# Create the test database and connect to it. We need to autocommit
# if the database supports it because PostgreSQL doesn't allow
# CREATE/DROP DATABASE statements within transactions.
@@ -136,6 +122,7 @@ class TestRunner:
else:
raise Exception("Tests cancelled.")
+
settings.DATABASE_NAME = db_name
connection.close()
@@ -153,6 +140,7 @@ class TestRunner:
self.teardown()
def setup(self):
+ global TEST_DATABASE_NAME
from django.conf import settings
from django.db import connection, connections
from django.core import management
@@ -170,17 +158,38 @@ class TestRunner:
self.output(0, "Running tests with database %r" % settings.DATABASE_ENGINE)
- # Create test dbs for the default connection and any named connections
- # in settings. Remeber the original default db name so we can connect
- # there to drop the created test dbs.
- self._old_database_name = settings.DATABASE_NAME
- self.create_test_db(connection)
+ # Create test dbs for the default connection and two named connections,
+ # replacing any named connections defined in settings. All connections
+ # will use the default DATABASE_ENGINE
+ self.old_database_name = settings.DATABASE_NAME
+ self.old_databases = settings.DATABASES
+
+ db_a = TEST_DATABASE_NAME + '_a'
+ db_b = TEST_DATABASE_NAME + '_b'
+ if settings.DATABASE_ENGINE == 'sqlite3':
+ # If we're using SQLite, it's more convenient to test against an
+ # in-memory database. But we can only do this for the default;
+ # after that we have to use temp files.
+ TEST_DATABASE_NAME = ':memory:'
+ db_a_name = self._tempfile()
+ db_b_name = self._tempfile()
+ self.cleanup_files.append(db_a_name)
+ self.cleanup_files.append(db_b_name)
+ else:
+ db_a_name = db_a
+ db_b_name = db_b
- if hasattr(settings, 'DATABASES'):
- for name, info in settings.DATABASES.items():
- cx = connections[name]
- test_connection = self.create_test_db(cx.connection)
- connections[name] = test_connection
+ settings.DATABASES = {
+ db_a: { 'DATABASE_NAME': db_a_name },
+ db_b: { 'DATABASE_NAME': db_b_name }
+ }
+
+ self.create_test_db(TEST_DATABASE_NAME, connection)
+ for name, info in settings.DATABASES.items():
+ cx = connections[name]
+ test_connection = self.create_test_db(info['DATABASE_NAME'],
+ cx.connection)
+ connections[name] = test_connection
# Install the core always installed apps
for app in ALWAYS_INSTALLED_APPS:
@@ -196,7 +205,8 @@ class TestRunner:
from django.db import connection
from django.conf import settings
connection.close()
- settings.DATABASE_NAME = self._old_database_name
+ settings.DATABASE_NAME = self.old_database_name
+ settings.DATABASES = self.old_databases
for db_name, cx in self.created_dbs:
settings = cx.settings
cx.close()
@@ -334,6 +344,12 @@ class TestRunner:
elif hasattr(connection.connection, "set_isolation_level"):
connection.connection.set_isolation_level(0)
+ def _tempfile(self):
+ import tempfile
+ fd, filename = tempfile.mkstemp()
+ os.close(fd)
+ return filename
+
if __name__ == "__main__":
from optparse import OptionParser
usage = "%prog [options] [model model model ...]"