Ticket #11505: restore_cache_during_tests.v2.diff

File restore_cache_during_tests.v2.diff, 20.6 KB (added by jsdalton, 4 years ago)
  • django/test/client.py

    diff --git a/django/test/client.py b/django/test/client.py
    index a5ef075..97ef589 100644
    a b from django.core.handlers.base import BaseHandler 
    1717from django.core.handlers.wsgi import WSGIRequest
    1818from django.core.signals import got_request_exception
    1919from django.http import SimpleCookie, HttpRequest, QueryDict
     20from django.middleware.cache import (UpdateCacheMiddleware,
     21         FetchFromCacheMiddleware, CacheMiddleware)
    2022from django.template import TemplateDoesNotExist
    2123from django.test import signals
     24from django.test.utils import modified_get_cache
    2225from django.utils.functional import curry
    2326from django.utils.encoding import smart_str
    2427from django.utils.http import urlencode
    class ClientHandler(BaseHandler): 
    6265    """
    6366    def __init__(self, enforce_csrf_checks=True, *args, **kwargs):
    6467        self.enforce_csrf_checks = enforce_csrf_checks
     68        self._caches_replaced = False
    6569        super(ClientHandler, self).__init__(*args, **kwargs)
    6670
    6771    def __call__(self, environ):
    class ClientHandler(BaseHandler): 
    8185            # required for backwards compatibility with external tests against
    8286            # admin views.
    8387            request._dont_enforce_csrf_checks = not self.enforce_csrf_checks
     88
     89            # Ensure cache middleware uses resetable cache for testing
     90            if not self._caches_replaced:
     91                self.replace_cache_in_cache_middleware()
    8492            response = self.get_response(request)
    8593        finally:
    8694            signals.request_finished.disconnect(close_connection)
    class ClientHandler(BaseHandler): 
    8997
    9098        return response
    9199
     100    def replace_cache_in_cache_middleware(self):
     101        """
     102        Iterate over loaded cache middleware and replace cache with modified
     103        cache (so it can be reset between tests).
     104        """
     105        cache_middleware_classes = [
     106            UpdateCacheMiddleware, FetchFromCacheMiddleware, CacheMiddleware
     107        ]
     108        mw_methods = set(self._request_middleware + self._response_middleware)
     109        for mw_method in mw_methods:
     110            mw_instance = mw_method.im_self
     111            if any([issubclass(type(mw_instance), cls) for cls in cache_middleware_classes]):
     112                mw_instance._original_cache = mw_instance.cache
     113                mw_instance.cache = modified_get_cache(mw_instance.cache_alias)
     114        self._caches_replaced = True
     115
    92116def store_rendered_templates(store, signal, sender, template, context, **kwargs):
    93117    """
    94118    Stores templates and contexts that are rendered.
  • django/test/testcases.py

    diff --git a/django/test/testcases.py b/django/test/testcases.py
    index 010850d..ed05926 100644
    a b from django.http import QueryDict 
    2020from django.test import _doctest as doctest
    2121from django.test.client import Client
    2222from django.test.utils import (get_warnings_state, restore_warnings_state,
    23     override_settings)
     23    override_settings, _caches_used_during_test)
    2424from django.utils import simplejson, unittest as ut2
    2525from django.utils.encoding import smart_str
    2626
    class TransactionTestCase(SimpleTestCase): 
    350350              ROOT_URLCONF with it.
    351351            * Clearing the mail test outbox.
    352352        """
     353        self._cache_setup()
    353354        self._fixture_setup()
    354355        self._urlconf_setup()
    355356        mail.outbox = []
    class TransactionTestCase(SimpleTestCase): 
    376377            settings.ROOT_URLCONF = self.urls
    377378            clear_url_caches()
    378379
     380    def _cache_setup(self):
     381        """
     382        Resets the list of caches used in preparation for test
     383        """
     384        global _caches_used_during_test
     385        _caches_used_during_test.clear()
     386
    379387    def __call__(self, result=None):
    380388        """
    381389        Wrapper around default __call__ method to perform common Django test
    class TransactionTestCase(SimpleTestCase): 
    414422        """
    415423        self._fixture_teardown()
    416424        self._urlconf_teardown()
     425        self._cache_teardown()
    417426        # Some DB cursors include SQL statements as part of cursor
    418427        # creation. If you have a test that does rollback, the effect
    419428        # of these statements is lost, which can effect the operation
    class TransactionTestCase(SimpleTestCase): 
    431440        if hasattr(self, '_old_root_urlconf'):
    432441            settings.ROOT_URLCONF = self._old_root_urlconf
    433442            clear_url_caches()
     443   
     444    def _cache_teardown(self):
     445        """
     446        Clears values set in cache during test
     447        """
     448        for cache in _caches_used_during_test:
     449            cache.delete_many(list(cache._keys_set_during_test))
    434450
    435451    def assertRedirects(self, response, expected_url, status_code=302,
    436452                        target_status_code=200, host=None, msg_prefix=''):
  • django/test/utils.py

    diff --git a/django/test/utils.py b/django/test/utils.py
    index 87f2311..eb000fc 100644
    a b  
    11from __future__ import with_statement
    22
     3import types
    34import warnings
    45from django.conf import settings, UserSettingsHolder
    56from django.core import mail
     7from django.core import cache
    68from django.test.signals import template_rendered, setting_changed
    79from django.template import Template, loader, TemplateDoesNotExist
    810from django.template.loaders import cached
    def instrumented_test_render(self, context): 
    6264    return self.nodelist.render(context)
    6365
    6466
     67_caches_used_during_test = set()
     68
     69def modified_get_cache(backend, **kwargs):
     70    """
     71    Modifies the original get_cache so that we can track any keys set during a cache run and reset
     72    them at the end.
     73    """
     74    requested_cache = cache.original_get_cache(backend, **kwargs)
     75   
     76    # Add '_test_' to key_prefix to ensure pre-existing cache values don't get touched
     77    requested_cache.original_key_prefix = requested_cache.key_prefix
     78    requested_cache.key_prefix = '_test_%s' % requested_cache.original_key_prefix
     79   
     80    # Keep track of which caches we use during a test
     81    global _caches_used_during_test
     82    _caches_used_during_test.add(requested_cache)
     83   
     84    requested_cache._keys_set_during_test = set()
     85   
     86    # Modify cache.set() to collect keys in _keys_set_during_test
     87    requested_cache.original_set = requested_cache.set
     88    def modified_set(self, key, value, timeout=None, version=None):
     89        requested_cache._keys_set_during_test.add(key)
     90        requested_cache.original_set(key, value, timeout, version)
     91    requested_cache.set = types.MethodType(modified_set, requested_cache)
     92   
     93    # Modify cache.add() to collect keys in _keys_set_during_test
     94    requested_cache.original_add = requested_cache.add
     95    def modified_add(self, key, value, timeout=None, version=None):
     96        requested_cache._keys_set_during_test.add(key)
     97        return requested_cache.original_add(key, value, timeout, version)
     98    requested_cache.add = types.MethodType(modified_add, requested_cache)
     99   
     100    # Modify cache.incr() to collect keys in _keys_set_during_test
     101    requested_cache.original_incr = requested_cache.incr
     102    def modified_incr(self, key, delta=1, version=None):
     103        requested_cache._keys_set_during_test.add(key)
     104        return requested_cache.original_incr(key, delta, version)
     105    requested_cache.incr = types.MethodType(modified_incr, requested_cache)
     106   
     107    # Modify cache.decr() to collect keys in _keys_set_during_test
     108    requested_cache.original_decr = requested_cache.decr
     109    def modified_decr(self, key, delta=1, version=None):
     110        requested_cache._keys_set_during_test.add(key)
     111        return requested_cache.original_decr(key, delta, version)
     112    requested_cache.decr = types.MethodType(modified_decr, requested_cache)
     113   
     114    # Modify cache.set_many() to collect keys in _keys_set_during_test
     115    requested_cache.original_set_many = requested_cache.set_many
     116    def modified_set_many(self, data, timeout=None, version=None):
     117        requested_cache._keys_set_during_test.update(data.keys())
     118        requested_cache.original_set_many(data, timeout, version)
     119    requested_cache.set_many = types.MethodType(modified_set_many, requested_cache)
     120   
     121    return requested_cache
     122
     123
    65124def setup_test_environment():
    66125    """Perform any global pre-test setup. This involves:
    67126
    def setup_test_environment(): 
    74133
    75134    mail.original_email_backend = settings.EMAIL_BACKEND
    76135    settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
    77 
    78136    mail.outbox = []
     137   
     138    cache.original_get_cache = cache.get_cache
     139    cache.get_cache = modified_get_cache
     140   
     141    # Make sure django.core.cache.cache also uses the modified cache
     142    cache.original_cache = cache.cache
     143    cache.cache = cache.get_cache(cache.DEFAULT_CACHE_ALIAS)
    79144
    80145    deactivate()
    81146
    def teardown_test_environment(): 
    92157
    93158    settings.EMAIL_BACKEND = mail.original_email_backend
    94159    del mail.original_email_backend
    95 
    96160    del mail.outbox
     161   
     162    cache.cache = cache.original_cache
     163    del cache.original_cache
     164    cache.get_cache = cache.original_get_cache
     165    del cache.original_get_cache
    97166
    98167
    99168def get_warnings_state():
  • tests/regressiontests/cache/tests.py

    diff --git a/tests/regressiontests/cache/tests.py b/tests/regressiontests/cache/tests.py
    index 07bcab7..971d494 100644
    a b class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): 
    894894        self.assertTrue(not os.path.exists(os.path.dirname(os.path.dirname(keypath))))
    895895
    896896    def test_cull(self):
    897         self.perform_cull_test(50, 29)
     897        self.perform_cull_test(50, 28)
    898898
    899899    def test_old_initialization(self):
    900900        self.cache = get_cache('file://%s?max_entries=30' % self.dirname)
    901         self.perform_cull_test(50, 29)
     901        self.perform_cull_test(50, 28)
    902902
    903903class CustomCacheKeyValidationTests(unittest.TestCase):
    904904    """
  • new file tests/regressiontests/test_utils/templates/test_utils/simple_view.html

    diff --git a/tests/regressiontests/test_utils/templates/test_utils/simple_view.html b/tests/regressiontests/test_utils/templates/test_utils/simple_view.html
    new file mode 100644
    index 0000000..479d8d7
    - +  
     1<html><p>Howdy!</p></html>
  • tests/regressiontests/test_utils/tests.py

    diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
    index 942aa85..31fead5 100644
    a b  
    11from __future__ import with_statement
    22
     3import tempfile
    34from django.forms import EmailField
    45from django.test import SimpleTestCase, TestCase, skipUnlessDBFeature
    5 from django.utils.unittest import skip
     6from django.test.utils import override_settings
     7from django.utils.unittest import skip, skipUnless
     8from django.conf import settings
     9from django.core import management
     10from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
    611
    712from models import Person
    813
    class SkippingExtraTests(TestCase): 
    131136        pass
    132137
    133138
     139# We must set this via a function to confirm that cache set test has run
     140# before cache get test
     141_cache_set_test_has_run = False
     142
     143def cache_set_test_run():
     144    global _cache_set_test_has_run
     145    _cache_set_test_has_run = True
     146
     147def cache_get_test_finished():
     148    global _cache_set_test_has_run
     149    _cache_set_test_has_run = False
     150
     151
     152class BaseCacheReset(TestCase):
     153    @classmethod
     154    def setUpClass(cls):
     155        # Setup everything here, because setupClass is guaranteed to run first
     156        cache = cls.original_cache()
     157
     158        # Add a "pre-existing" value to cache to ensure it gets reset properly
     159        cache.set('salt', 'pepper') # We won't touch this one
     160        cache.set('sweet', 'sour') # We will touch a key with this name and check if it's restored at the end
     161       
     162        # Sanity checks
     163        assert cache.get('salt') == 'pepper'
     164        assert cache.get('sweet') == 'sour'
     165
     166        # We manually prefix the test_ here because we are not trying to pretend these are part
     167        # of the original cache. We're just making sure the values exist so that we can call incr/decr
     168        # without using cache.set() first
     169        cache.orig_key_prefix = cache.key_prefix
     170        cache.key_prefix = '_test_%s' % cache.orig_key_prefix
     171        try:
     172            cache.set('going_up', 1)
     173            cache.set('going_down', 2)
     174        finally:
     175            cache.key_prefix = cache.orig_key_prefix
     176
     177    def setUp(self):
     178        # Set up cache again at the instance level. This one will get reset
     179        self.cache = self.modified_cache()
     180
     181
     182class CacheResetTestsMixin(object):
     183    # Note test names start with a/b. This ensures test order is correct (which we're normally
     184    # not supposed to worry about)
     185    def test_a_set_cache_in_various_ways_in_one_test_method(self):
     186        """
     187        Set some cache stuff in this method, and then we'll sure it doesn't carry over to the next
     188        """
     189        sweet_result = self.cache.set('sweet', 'bitter')
     190        left_result = self.cache.set('left', 'right')
     191        loud_result = self.cache.add('loud', 'quiet')
     192       
     193        assert self.cache.get('going_up') == 1
     194        going_up_result = self.cache.incr('going_up')
     195       
     196        assert self.cache.get('going_down') == 2
     197        going_down_result = self.cache.decr('going_down')
     198       
     199        over_above_result = self.cache.set_many({'over': 'under', 'above': 'below'})
     200
     201        # Sanity checks
     202        self.assertEqual(self.cache.get('sweet'), 'bitter')
     203        self.assertEqual(sweet_result, None)
     204       
     205        self.assertEqual(self.cache.get('left'), 'right')
     206        self.assertEqual(left_result, None)
     207       
     208        self.assertEqual(self.cache.get('loud'), 'quiet')
     209        self.assertEqual(loud_result, True)
     210       
     211        self.assertEqual(self.cache.get('going_up'), 2)
     212        self.assertEqual(going_up_result, 2)
     213       
     214        self.assertEqual(self.cache.get('going_down'), 1)
     215        self.assertEqual(going_down_result, 1)
     216       
     217        self.assertEqual(self.cache.get('over'), 'under')
     218        self.assertEqual(self.cache.get('above'), 'below')
     219        self.assertEqual(over_above_result, None)
     220
     221        # Mark that set_cache tests have run
     222        cache_set_test_run()
     223
     224    def test_b_get_cache_in_another_test_method(self):
     225        # Confirm that set tests has already run
     226        if not _cache_set_test_has_run:
     227            self.skipTest("set_cache test did not run first!")
     228
     229        try:
     230            self.assertEqual(self.cache.get('left'), None)
     231            self.assertEqual(self.cache.get('loud'), None)
     232            self.assertEqual(self.cache.get('going_up'), None)
     233            self.assertEqual(self.cache.get('going_down'), None)
     234            self.assertEqual(self.cache.get('over'), None)
     235            self.assertEqual(self.cache.get('above'), None)
     236
     237            # Make sure pre-existing values are still correct
     238            original_cache = self.__class__.original_cache()
     239            self.assertEqual(original_cache.get('sweet'), 'sour')
     240            self.assertEqual(original_cache.get('salt'), 'pepper')
     241        finally:
     242            # Mark get_cache tests finished
     243            cache_get_test_finished()
     244
     245
     246class LocMemCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     247    backend_name = 'django.core.cache.backends.locmem.LocMemCache'
     248
     249    @classmethod
     250    def original_cache(cls):
     251        if not hasattr(cls, '_original_cache'):
     252            from django.core.cache import original_get_cache
     253            cls._original_cache = original_get_cache(cls.backend_name, LOCATION='test')
     254        return cls._original_cache
     255
     256    def modified_cache(self):
     257        return get_cache(self.backend_name, LOCATION='test')
     258
     259
     260class FileBasedCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     261    backend_name = 'django.core.cache.backends.filebased.FileBasedCache'
     262
     263    @classmethod
     264    def original_cache(cls):
     265        if not hasattr(cls, '_original_cache'):
     266            cls.cache_dirname = tempfile.mkdtemp()
     267            from django.core.cache import original_get_cache
     268            cls._original_cache = original_get_cache(cls.backend_name, LOCATION=cls.cache_dirname)
     269        return cls._original_cache
     270
     271    def modified_cache(self):
     272        return get_cache(self.backend_name, LOCATION=self.cache_dirname)
     273
     274
     275class DBCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     276    backend_name = 'django.core.cache.backends.db.DatabaseCache'
     277
     278    @classmethod
     279    def original_cache(cls):
     280        if not hasattr(cls, '_original_cache'):
     281            cls.cache_table_name = 'test_cache_table'
     282            management.call_command('createcachetable', cls.cache_table_name, verbosity=0, interactive=False)
     283            from django.core.cache import original_get_cache
     284            cls._original_cache = original_get_cache(cls.backend_name, LOCATION=cls.cache_table_name)
     285        return cls._original_cache
     286
     287    def modified_cache(self):
     288        return get_cache(self.backend_name, LOCATION=self.cache_table_name)
     289
     290    @classmethod
     291    def tearDownClass(cls):
     292        from django.db import connection
     293        cursor = connection.cursor()
     294        cursor.execute('DROP TABLE %s' % connection.ops.quote_name(cls.cache_table_name))
     295
     296
     297@skipUnless(settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND'].startswith('django.core.cache.backends.memcached.'), "memcached not available")
     298class MemcachedCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     299    backend_name = 'django.core.cache.backends.memcached.MemcachedCache'
     300
     301    @classmethod
     302    def original_cache(cls):
     303        if not hasattr(cls, '_original_cache'):
     304            cls.memcached_location = settings.CACHES[DEFAULT_CACHE_ALIAS]['LOCATION']
     305            from django.core.cache import original_get_cache
     306            cls._original_cache = original_get_cache(cls.backend_name, LOCATION=cls.memcached_location)
     307        return cls._original_cache
     308
     309    def modified_cache(self):
     310        return get_cache(self.backend_name, LOCATION=self.memcached_location)
     311
     312
     313class CacheMiddlewareTestingTests(TestCase):
     314    """
     315    CacheMiddleware caches should be reset between tests to avoid broken test
     316    client when views are cached.
     317    """
     318    urls = 'regressiontests.test_utils.urls'
     319
     320    def test_a_request_cached_in_view(self):
     321        """A simple view, which gets cached by cache middleware"""
     322        response = self.client.get("/test_utils/simple_view/")
     323        self.assertEqual(response.status_code, 200)
     324        self.assertEqual(response.context["foo"], "bar")
     325
     326    def test_b_request_cached_in_view(self):
     327        """
     328        This test runs after test_a above, and assures that the view is not
     329        being cached between tests.
     330        """
     331        response = self.client.get("/test_utils/simple_view/")
     332        self.assertEqual(response.status_code, 200)
     333
     334        # These assertions fail if the cache from test_a is carried over, since
     335        # the cached response doesn't have a context
     336        self.assertTrue(response.context)
     337        self.assertEqual(response.context["foo"], "bar")
     338
     339CacheMiddlewareTestingTests = override_settings(
     340    CACHES = {
     341        'default': {
     342            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
     343        },
     344    },
     345    CACHE_MIDDLEWARE_SECONDS=30,
     346    MIDDLEWARE_CLASSES = (
     347        'django.middleware.cache.UpdateCacheMiddleware',
     348        'django.middleware.common.CommonMiddleware',
     349        'django.middleware.cache.FetchFromCacheMiddleware',
     350    )
     351)(CacheMiddlewareTestingTests)
     352
     353
    134354class AssertRaisesMsgTest(SimpleTestCase):
    135355
    136356    def test_special_re_chars(self):
  • tests/regressiontests/test_utils/urls.py

    diff --git a/tests/regressiontests/test_utils/urls.py b/tests/regressiontests/test_utils/urls.py
    index 1bf0a0c..16c19ca 100644
    a b import views 
    55
    66urlpatterns = patterns('',
    77    (r'^test_utils/get_person/(\d+)/$', views.get_person),
     8    (r'^test_utils/simple_view/$', views.simple_view),
    89)
  • tests/regressiontests/test_utils/views.py

    diff --git a/tests/regressiontests/test_utils/views.py b/tests/regressiontests/test_utils/views.py
    index 62af0d9..4d68563 100644
    a b  
    11from django.http import HttpResponse
    2 from django.shortcuts import get_object_or_404
     2from django.template import RequestContext
     3from django.shortcuts import get_object_or_404, render_to_response
    34from models import Person
    45
    56def get_person(request, pk):
    67    person = get_object_or_404(Person, pk=pk)
    7     return HttpResponse(person.name)
    8  No newline at end of file
     8    return HttpResponse(person.name)
     9
     10def simple_view(request):
     11    return render_to_response('test_utils/simple_view.html', {
     12        "foo": "bar"
     13    }, context_instance=RequestContext(request))
Back to Top