161 lines
4.7 KiB
Python
161 lines
4.7 KiB
Python
"""
|
|
Utility classes for testing django applications.
|
|
|
|
:py:class:`CacheIsolationMixin`
|
|
A mixin helping to write tests which are isolated from cached data.
|
|
|
|
:py:class:`CacheIsolationTestCase`
|
|
A TestCase baseclass that has per-test isolated caches.
|
|
"""
|
|
|
|
import copy
|
|
|
|
from django import db
|
|
from django.core.cache import caches
|
|
from django.test import TestCase, override_settings
|
|
from django.conf import settings
|
|
from django.contrib import sites
|
|
|
|
from nose.plugins import Plugin
|
|
|
|
from request_cache.middleware import RequestCache
|
|
|
|
|
|
class CacheIsolationMixin(object):
|
|
"""
|
|
This class can be used to enable specific django caches for
|
|
specific the TestCase that it's mixed into.
|
|
|
|
Usage:
|
|
|
|
Use the ENABLED_CACHES to list the names of caches that should
|
|
be enabled in the context of this TestCase. These caches will
|
|
use a loc_mem_cache with the default settings.
|
|
|
|
Set the class variable CACHES to explicitly specify the cache settings
|
|
that should be overridden. This class will insert those values into
|
|
django.conf.settings, and will reset all named caches before each
|
|
test.
|
|
|
|
If both CACHES and ENABLED_CACHES are not None, raises an error.
|
|
"""
|
|
|
|
CACHES = None
|
|
ENABLED_CACHES = None
|
|
|
|
__settings_overrides = []
|
|
__old_settings = []
|
|
|
|
@classmethod
|
|
def start_cache_isolation(cls):
|
|
"""
|
|
Start cache isolation by overriding the settings.CACHES and
|
|
flushing the cache.
|
|
"""
|
|
cache_settings = None
|
|
if cls.CACHES is not None and cls.ENABLED_CACHES is not None:
|
|
raise Exception(
|
|
"Use either CACHES or ENABLED_CACHES, but not both"
|
|
)
|
|
|
|
if cls.CACHES is not None:
|
|
cache_settings = cls.CACHES
|
|
elif cls.ENABLED_CACHES is not None:
|
|
cache_settings = {
|
|
'default': {
|
|
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
|
}
|
|
}
|
|
|
|
cache_settings.update({
|
|
cache_name: {
|
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
|
'LOCATION': cache_name,
|
|
'KEY_FUNCTION': 'util.memcache.safe_key',
|
|
} for cache_name in cls.ENABLED_CACHES
|
|
})
|
|
|
|
if cache_settings is None:
|
|
return
|
|
|
|
cls.__old_settings.append(copy.deepcopy(settings.CACHES))
|
|
override = override_settings(CACHES=cache_settings)
|
|
override.__enter__()
|
|
cls.__settings_overrides.append(override)
|
|
|
|
assert settings.CACHES == cache_settings
|
|
|
|
# Start with empty caches
|
|
cls.clear_caches()
|
|
|
|
@classmethod
|
|
def end_cache_isolation(cls):
|
|
"""
|
|
End cache isolation by flushing the cache and then returning
|
|
settings.CACHES to its original state.
|
|
"""
|
|
# Make sure that cache contents don't leak out after the isolation is ended
|
|
cls.clear_caches()
|
|
|
|
if cls.__settings_overrides:
|
|
cls.__settings_overrides.pop().__exit__(None, None, None)
|
|
assert settings.CACHES == cls.__old_settings.pop()
|
|
|
|
@classmethod
|
|
def clear_caches(cls):
|
|
"""
|
|
Clear all of the caches defined in settings.CACHES.
|
|
"""
|
|
# N.B. As of 2016-04-20, Django won't return any caches
|
|
# from django.core.cache.caches.all() that haven't been
|
|
# accessed using caches[name] previously, so we loop
|
|
# over our list of overridden caches, instead.
|
|
for cache in settings.CACHES:
|
|
caches[cache].clear()
|
|
|
|
# The sites framework caches in a module-level dictionary.
|
|
# Clear that.
|
|
sites.models.SITE_CACHE.clear()
|
|
|
|
RequestCache.clear_request_cache()
|
|
|
|
|
|
class CacheIsolationTestCase(CacheIsolationMixin, TestCase):
|
|
"""
|
|
A TestCase that isolates caches (as described in
|
|
:py:class:`CacheIsolationMixin`) at class setup, and flushes the cache
|
|
between every test.
|
|
"""
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super(CacheIsolationTestCase, cls).setUpClass()
|
|
cls.start_cache_isolation()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
cls.end_cache_isolation()
|
|
super(CacheIsolationTestCase, cls).tearDownClass()
|
|
|
|
def setUp(self):
|
|
super(CacheIsolationTestCase, self).setUp()
|
|
|
|
self.clear_caches()
|
|
self.addCleanup(self.clear_caches)
|
|
|
|
|
|
class NoseDatabaseIsolation(Plugin):
|
|
"""
|
|
nosetest plugin that resets django databases before any tests begin.
|
|
|
|
Used to make sure that tests running in multi processes aren't sharing
|
|
a database connection.
|
|
"""
|
|
name = "database-isolation"
|
|
|
|
def begin(self):
|
|
"""
|
|
Before any tests start, reset all django database connections.
|
|
"""
|
|
for db_ in db.connections.all():
|
|
db_.close()
|