summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Pellerin <jpellerin@gmail.com>2006-07-21 21:59:54 +0000
committerJason Pellerin <jpellerin@gmail.com>2006-07-21 21:59:54 +0000
commitbbe357f34ee6a2bd77209dbaf7af2c37970f42e1 (patch)
tree9cd04dd5fcc98052fff93cdab9e4ae8d96d8a43f
parente655cc3cc3825775ddee2a33f4ef0960e8f65eaa (diff)
[multi-db] Added tests for thread isolation of db-related settings.
git-svn-id: http://code.djangoproject.com/svn/django/branches/multiple-db-support@3417 bcc190cf-cafb-0310-a4f2-bffc1f526a37
-rw-r--r--tests/othertests/thread_isolation.py287
1 files changed, 287 insertions, 0 deletions
diff --git a/tests/othertests/thread_isolation.py b/tests/othertests/thread_isolation.py
new file mode 100644
index 0000000000..bacbf0341c
--- /dev/null
+++ b/tests/othertests/thread_isolation.py
@@ -0,0 +1,287 @@
+# tests that db settings can be different in different threads
+#
+#
+# What's going on here:
+#
+# Simulating multiple web requests in a threaded environment, one in
+# which settings are different for each request. So we replace
+# django.conf.settings with a thread local, with different
+# configurations in each thread, and then fire off three
+# simultaneous requests (using a condition to sync them up), and
+# test that each thread sees its own settings and the models in each
+# thread attempt to connect to the correct database as per their
+# settings.
+#
+
+
+import copy
+import os
+import sys
+import threading
+from thread import get_ident
+
+from django import conf
+from django.core.handlers.wsgi import WSGIHandler
+from django.db import models, model_connection_name, _default, connection, \
+ connections
+from django.http import HttpResponse
+
+try:
+ # Only exists in Python 2.4+
+ from threading import local
+except ImportError:
+ # Import copy of _thread_local.py from Python 2.4
+ from django.utils._threading_local import local
+
+# state holder
+S = {}
+
+# models
+class MQ(models.Model):
+ val = models.CharField(maxlength=10)
+ class Meta:
+ app_label = 'ti'
+
+
+class MX(models.Model):
+ val = models.CharField(maxlength=10)
+ class Meta:
+ app_label = 'ti'
+
+
+class MY(models.Model):
+ val = models.CharField(maxlength=10)
+ class Meta:
+ app_label = 'ti'
+
+# eventused to synchronize threads so we can be sure they are running
+# together
+ev = threading.Event()
+
+
+def test_one(path, request):
+ """Start out with settings as originally configured"""
+ from django.conf import settings
+ debug("test_one: %s", settings.OTHER_DATABASES)
+
+ assert model_connection_name(MQ) == _default
+ assert model_connection_name(MX) == 'django_test_db_a', \
+ "%s != 'django_test_db_a'" % model_connection_name(MX)
+ assert MX._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME']
+ assert model_connection_name(MY) == 'django_test_db_b'
+ assert MY._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.OTHER_DATABASES['django_test_db_b']['DATABASE_NAME'], \
+ "%s != %s" % \
+ (MY._default_manager.db.connection.settings.DATABASE_NAME,
+ settings.OTHER_DATABASES['django_test_db_b']['DATABASE_NAME'])
+ assert MQ._default_manager.db.connection is \
+ connections[_default].connection
+ assert MQ._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.DATABASE_NAME
+ assert connection.settings.DATABASE_NAME == settings.DATABASE_NAME
+
+
+def test_two(path, request):
+ """Between the first and second requests, settings change to assign
+ model MY to a different connection
+ """
+ from django.conf import settings
+ debug("test_two: %s", settings.OTHER_DATABASES)
+
+ try:
+ assert model_connection_name(MQ) == _default
+ assert model_connection_name(MX) == 'django_test_db_a'
+ assert MX._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME']
+ assert model_connection_name(MY) == _default
+ assert MY._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.DATABASE_NAME, "%s != %s" % \
+ (MY._default_manager.db.connection.settings.DATABASE_NAME,
+ settings.DATABASE_NAME)
+ assert MQ._default_manager.db.connection is \
+ connections[_default].connection
+ assert MQ._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.DATABASE_NAME
+ assert connection.settings.DATABASE_NAME == settings.DATABASE_NAME
+ except:
+ S.setdefault('errors',[]).append(sys.exc_info())
+
+
+def test_three(path, request):
+ """Between the 2nd and 3rd requests, the settings at the names in
+ OTHER_DATABASES have changed.
+ """
+ from django.conf import settings
+ debug("3 %s: %s", get_ident(), settings.OTHER_DATABASES)
+ debug("3 %s: default: %s", get_ident(), settings.DATABASE_NAME)
+ debug("3 %s: conn: %s", get_ident(), connection.settings.DATABASE_NAME)
+
+ try:
+ assert model_connection_name(MQ) == _default
+ assert model_connection_name(MX) == 'django_test_db_b'
+ assert MX._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.OTHER_DATABASES['django_test_db_b']['DATABASE_NAME'],\
+ "%s != %s" % \
+ (MX._default_manager.db.connection.settings.DATABASE_NAME,
+ settings.OTHER_DATABASES['django_test_db_b']['DATABASE_NAME'])
+ assert model_connection_name(MY) == 'django_test_db_a'
+ assert MY._default_manager.db.connection.settings.DATABASE_NAME == \
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME'],\
+ "%s != %s" % \
+ (MY._default_manager.db.connection.settings.DATABASE_NAME,
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME'])
+ assert MQ._default_manager.db.connection is \
+ connections[_default].connection
+ assert connection.settings.DATABASE_NAME == \
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME'],\
+ "%s != %s" % \
+ (connection.settings.DATABASE_NAME,
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME'])
+ except:
+ S.setdefault('errors',[]).append(sys.exc_info())
+
+
+
+# helpers
+def thread_two(func, *arg):
+ def start():
+ from django.conf import settings
+ for attr in [ a for a in dir(S['base_settings']) if a == a.upper() ]:
+ setattr(settings, attr,
+ copy.deepcopy(getattr(S['base_settings'], attr)))
+
+ settings.OTHER_DATABASES['django_test_db_b']['MODELS'] = []
+ debug("t2 waiting")
+ ev.wait(2.0)
+ func(*arg)
+ debug("t2 complete")
+ t2 = threading.Thread(target=start)
+ t2.start()
+ return t2
+
+
+def thread_three(func, *arg):
+ def start():
+ from django.conf import settings
+ for attr in [ a for a in dir(S['base_settings']) if a == a.upper() ]:
+ setattr(settings, attr,
+ copy.deepcopy(getattr(S['base_settings'], attr)))
+
+ settings.OTHER_DATABASES['django_test_db_b']['MODELS'] = ['ti.MY']
+ settings.OTHER_DATABASES['django_test_db_b'], \
+ settings.OTHER_DATABASES['django_test_db_a'] = \
+ settings.OTHER_DATABASES['django_test_db_a'], \
+ settings.OTHER_DATABASES['django_test_db_b']
+
+ settings.DATABASE_NAME = \
+ settings.OTHER_DATABASES['django_test_db_a']['DATABASE_NAME']
+
+ # we just manually changed settings, so reset the default
+ # connection (normally this isn't needed
+
+ debug("3 %s: start: default: %s", get_ident(), settings.DATABASE_NAME)
+ debug("3 %s: start: conn: %s", get_ident(),
+ connection.settings.DATABASE_NAME)
+
+ debug("t3 waiting")
+ ev.wait(2.0)
+ func(*arg)
+ debug("t3 complete")
+ t3 = threading.Thread(target=start)
+ t3.start()
+ return t3
+
+
+class MockHandler(WSGIHandler):
+
+ def __init__(self, test):
+ self.test = test
+ super(MockHandler, self).__init__()
+
+ def get_response(self, path, request):
+ # debug("mock handler answering %s, %s", path, request)
+ return HttpResponse(self.test(path, request))
+
+
+def pr(*arg):
+ if S['verbosity'] >= 1:
+ msg, arg = arg[0], arg[1:]
+ print msg % arg
+
+
+def debug(*arg):
+ if S['verbosity'] >= 2:
+ msg, arg = arg[0], arg[1:]
+ print msg % arg
+
+
+def setup():
+ debug("setup")
+ S['settings'] = copy.deepcopy(conf.settings)
+ S['base_settings'] = copy.deepcopy(conf.settings)
+ S['base_settings'].OTHER_DATABASES['django_test_db_a']['MODELS'] = \
+ ['ti.MX']
+ S['base_settings'].OTHER_DATABASES['django_test_db_b']['MODELS'] = \
+ ['ti.MY']
+
+ conf.settings = local()
+ for attr in [ a for a in dir(S['base_settings']) if a == a.upper() ]:
+ setattr(conf.settings, attr,
+ copy.deepcopy(getattr(S['base_settings'], attr)))
+
+ conf.settings.__module__ = S['settings'].__module__
+ debug("Setup: %s", conf.settings.OTHER_DATABASES)
+
+
+def teardown():
+ debug("teardown")
+ conf.settings = S['settings']
+
+
+def start_response(code, headers):
+ debug("start response: %s %s", code, headers)
+ pass
+
+
+def main():
+ debug("running tests")
+
+ env = os.environ.copy()
+ env['PATH_INFO'] = '/'
+ env['REQUEST_METHOD'] = 'GET'
+
+ t2 = thread_two(MockHandler(test_two), env, start_response)
+ t3 = thread_three(MockHandler(test_three), env, start_response)
+
+ try:
+ ev.set()
+ MockHandler(test_one)(env, start_response)
+ finally:
+ t2.join()
+ t3.join()
+ err = S.get('errors', [])
+ if err:
+ import traceback
+ for e in err:
+ traceback.print_exception(*e)
+ raise AssertionError("%s thread%s failed" %
+ (len(err), len(err) > 1 and 's' or ''))
+
+def run_tests(verbosity=0):
+ S['verbosity'] = verbosity
+
+ setup()
+ try:
+ main()
+ finally:
+ teardown()
+
+
+if __name__ == '__main__':
+ from django.conf import settings
+
+ settings.DATABASE_NAME = ':memory:'
+ print "MAIN start! ", connection.settings.DATABASE_NAME
+ connection.cursor()
+ run_tests(2)