Ticket #11505: restore_cache_during_tests.diff

File restore_cache_during_tests.diff, 14.8 KB (added by jsdalton, 4 years ago)
  • django/core/cache/backends/dummy.py

    diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py
    index af8b62c..ca09547 100644
    a b class DummyCache(BaseCache): 
    3232        self.validate_key(key)
    3333        return False
    3434
    35     def set_many(self, data, version=None):
     35    def set_many(self, data, timeout=None, version=None):
    3636        pass
    3737
    3838    def delete_many(self, keys, version=None):
  • django/test/testcases.py

    diff --git a/django/test/testcases.py b/django/test/testcases.py
    index 02717ff..634fb09 100644
    a b from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS, 
    1616from django.http import QueryDict
    1717from django.test import _doctest as doctest
    1818from django.test.client import Client
    19 from django.test.utils import get_warnings_state, restore_warnings_state, override_settings
     19from django.test.utils import get_warnings_state, restore_warnings_state, override_settings, _caches_used_during_test
    2020from django.utils import simplejson, unittest as ut2
    2121from django.utils.encoding import smart_str
    2222
    class TransactionTestCase(ut2.TestCase): 
    251251              ROOT_URLCONF with it.
    252252            * Clearing the mail test outbox.
    253253        """
     254        self._cache_setup()
    254255        self._fixture_setup()
    255256        self._urlconf_setup()
    256257        mail.outbox = []
    class TransactionTestCase(ut2.TestCase): 
    276277            settings.ROOT_URLCONF = self.urls
    277278            clear_url_caches()
    278279
     280    def _cache_setup(self):
     281        """
     282        Resets the list of caches used in preparation for test
     283        """
     284        global _caches_used_during_test
     285        _caches_used_during_test.clear()
     286
    279287    def __call__(self, result=None):
    280288        """
    281289        Wrapper around default __call__ method to perform common Django test
    class TransactionTestCase(ut2.TestCase): 
    315323        """
    316324        self._fixture_teardown()
    317325        self._urlconf_teardown()
     326        self._cache_teardown()
    318327        # Some DB cursors include SQL statements as part of cursor
    319328        # creation. If you have a test that does rollback, the effect
    320329        # of these statements is lost, which can effect the operation
    class TransactionTestCase(ut2.TestCase): 
    332341        if hasattr(self, '_old_root_urlconf'):
    333342            settings.ROOT_URLCONF = self._old_root_urlconf
    334343            clear_url_caches()
     344   
     345    def _cache_teardown(self):
     346        """
     347        Clears values set in cache during test
     348        """
     349        for cache in _caches_used_during_test:
     350            cache.delete_many(list(cache._keys_set_during_test))
    335351
    336352    def save_warnings_state(self):
    337353        """
  • django/test/utils.py

    diff --git a/django/test/utils.py b/django/test/utils.py
    index 19a3190..47ce559 100644
    a b from __future__ import with_statement 
    22
    33import sys
    44import time
     5import types
    56import os
    67import warnings
    78from django.conf import settings, UserSettingsHolder
    8 from django.core import mail
     9from django.core import mail, cache
    910from django.core.mail.backends import locmem
    1011from django.test.signals import template_rendered, setting_changed
    1112from django.template import Template, loader, TemplateDoesNotExist
    def instrumented_test_render(self, context): 
    6667    return self.nodelist.render(context)
    6768
    6869
     70_caches_used_during_test = set()
     71
     72def modified_get_cache(backend, **kwargs):
     73    """
     74    Modifies the original get_cache so that we can track any keys set during a cache run and reset
     75    them at the end.
     76    """
     77    requested_cache = cache.original_get_cache(backend, **kwargs)
     78   
     79    # Add '_test_' to key_prefix to ensure pre-existing cache values don't get touched
     80    requested_cache.original_key_prefix = requested_cache.key_prefix
     81    requested_cache.key_prefix = '_test_%s' % requested_cache.original_key_prefix
     82   
     83    # Keep track of which caches we use during a test
     84    global _caches_used_during_test
     85    _caches_used_during_test.add(requested_cache)
     86   
     87    requested_cache._keys_set_during_test = set()
     88   
     89    # Modify cache.set() to collect keys in _keys_set_during_test
     90    requested_cache.original_set = requested_cache.set
     91    def modified_set(self, key, value, timeout=None, version=None):
     92        requested_cache._keys_set_during_test.add(key)
     93        requested_cache.original_set(key, value, timeout, version)
     94    requested_cache.set = types.MethodType(modified_set, requested_cache)
     95   
     96    # Modify cache.add() to collect keys in _keys_set_during_test
     97    requested_cache.original_add = requested_cache.add
     98    def modified_add(self, key, value, timeout=None, version=None):
     99        requested_cache._keys_set_during_test.add(key)
     100        return requested_cache.original_add(key, value, timeout, version)
     101    requested_cache.add = types.MethodType(modified_add, requested_cache)
     102   
     103    # Modify cache.incr() to collect keys in _keys_set_during_test
     104    requested_cache.original_incr = requested_cache.incr
     105    def modified_incr(self, key, delta=1, version=None):
     106        requested_cache._keys_set_during_test.add(key)
     107        return requested_cache.original_incr(key, delta, version)
     108    requested_cache.incr = types.MethodType(modified_incr, requested_cache)
     109   
     110    # Modify cache.decr() to collect keys in _keys_set_during_test
     111    requested_cache.original_decr = requested_cache.decr
     112    def modified_decr(self, key, delta=1, version=None):
     113        requested_cache._keys_set_during_test.add(key)
     114        return requested_cache.original_decr(key, delta, version)
     115    requested_cache.decr = types.MethodType(modified_decr, requested_cache)
     116   
     117    # Modify cache.set_many() to collect keys in _keys_set_during_test
     118    requested_cache.original_set_many = requested_cache.set_many
     119    def modified_set_many(self, data, timeout=None, version=None):
     120        requested_cache._keys_set_during_test.update(data.keys())
     121        requested_cache.original_set_many(data, timeout, version)
     122    requested_cache.set_many = types.MethodType(modified_set_many, requested_cache)
     123   
     124    return requested_cache
     125
     126
    69127def setup_test_environment():
    70128    """Perform any global pre-test setup. This involves:
    71129
    def setup_test_environment(): 
    78136
    79137    mail.original_email_backend = settings.EMAIL_BACKEND
    80138    settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
    81 
    82139    mail.outbox = []
     140   
     141    cache.original_get_cache = cache.get_cache
     142    cache.get_cache = modified_get_cache
     143   
     144    # Make sure django.core.cache.cache also uses the modified cache
     145    cache.original_cache = cache.cache
     146    cache.cache = cache.get_cache(cache.DEFAULT_CACHE_ALIAS)
    83147
    84148    deactivate()
    85149
    def teardown_test_environment(): 
    96160
    97161    settings.EMAIL_BACKEND = mail.original_email_backend
    98162    del mail.original_email_backend
    99 
    100163    del mail.outbox
     164   
     165    cache.cache = cache.original_cache
     166    del cache.original_cache
     167    cache.get_cache = cache.original_get_cache
     168    del cache.original_get_cache
    101169
    102170
    103171def get_warnings_state():
  • tests/regressiontests/test_utils/tests.py

    diff --git a/tests/regressiontests/test_utils/tests.py b/tests/regressiontests/test_utils/tests.py
    index 673295b..6734cad 100644
    a b  
    11from __future__ import with_statement
    22
    33import sys
     4import tempfile
    45
    56from django.test import TestCase, skipUnlessDBFeature, skipIfDBFeature
    6 from django.utils.unittest import skip
     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
    711
    812from models import Person
    913
    class SkippingExtraTests(TestCase): 
    131135        pass
    132136
    133137
     138# We must set this via a function to confirm that cache set test has run
     139# before cache get test
     140_cache_set_test_has_run = False
     141
     142def cache_set_test_run():
     143    global _cache_set_test_has_run
     144    _cache_set_test_has_run = True
     145
     146def cache_get_test_finished():
     147    global _cache_set_test_has_run
     148    _cache_set_test_has_run = False
     149
     150
     151class BaseCacheReset(TestCase):
     152    @classmethod
     153    def setUpClass(cls):
     154        # Setup everything here, because setupClass is guaranteed to run first
     155        cache = cls.original_cache()
     156
     157        # Add a "pre-existing" value to cache to ensure it gets reset properly
     158        cache.set('salt', 'pepper') # We won't touch this one
     159        cache.set('sweet', 'sour') # We will touch a key with this name and check if it's restored at the end
     160       
     161        # Sanity checks
     162        assert cache.get('salt') == 'pepper'
     163        assert cache.get('sweet') == 'sour'
     164
     165        # We manually prefix the test_ here because we are not trying to pretend these are part
     166        # of the original cache. We're just making sure the values exist so that we can call incr/decr
     167        # without using cache.set() first
     168        cache.orig_key_prefix = cache.key_prefix
     169        cache.key_prefix = '_test_%s' % cache.orig_key_prefix
     170        try:
     171            cache.set('going_up', 1)
     172            cache.set('going_down', 2)
     173        finally:
     174            cache.key_prefix = cache.orig_key_prefix
     175
     176    def setUp(self):
     177        # Set up cache again at the instance level. This one will get reset
     178        self.cache = self.modified_cache()
     179
     180
     181class CacheResetTestsMixin(object):
     182    # Note test names start with a/b. This ensures test order is correct (which we're normally
     183    # not supposed to worry about)
     184    def test_a_set_cache_in_various_ways_in_one_test_method(self):
     185        """
     186        Set some cache stuff in this method, and then we'll sure it doesn't carry over to the next
     187        """
     188        sweet_result = self.cache.set('sweet', 'bitter')
     189        left_result = self.cache.set('left', 'right')
     190        loud_result = self.cache.add('loud', 'quiet')
     191       
     192        assert self.cache.get('going_up') == 1
     193        going_up_result = self.cache.incr('going_up')
     194       
     195        assert self.cache.get('going_down') == 2
     196        going_down_result = self.cache.decr('going_down')
     197       
     198        over_above_result = self.cache.set_many({'over': 'under', 'above': 'below'})
     199
     200        # Sanity checks
     201        self.assertEqual(self.cache.get('sweet'), 'bitter')
     202        self.assertEqual(sweet_result, None)
     203       
     204        self.assertEqual(self.cache.get('left'), 'right')
     205        self.assertEqual(left_result, None)
     206       
     207        self.assertEqual(self.cache.get('loud'), 'quiet')
     208        self.assertEqual(loud_result, True)
     209       
     210        self.assertEqual(self.cache.get('going_up'), 2)
     211        self.assertEqual(going_up_result, 2)
     212       
     213        self.assertEqual(self.cache.get('going_down'), 1)
     214        self.assertEqual(going_down_result, 1)
     215       
     216        self.assertEqual(self.cache.get('over'), 'under')
     217        self.assertEqual(self.cache.get('above'), 'below')
     218        self.assertEqual(over_above_result, None)
     219
     220        # Mark that set_cache tests have run
     221        cache_set_test_run()
     222
     223    def test_b_get_cache_in_another_test_method(self):
     224        # Confirm that set tests has already run
     225        if not _cache_set_test_has_run:
     226            self.skipTest("set_cache test did not run first!")
     227
     228        try:
     229            self.assertEqual(self.cache.get('left'), None)
     230            self.assertEqual(self.cache.get('loud'), None)
     231            self.assertEqual(self.cache.get('going_up'), None)
     232            self.assertEqual(self.cache.get('going_down'), None)
     233            self.assertEqual(self.cache.get('over'), None)
     234            self.assertEqual(self.cache.get('above'), None)
     235
     236            # Make sure pre-existing values are still correct
     237            original_cache = self.__class__.original_cache()
     238            self.assertEqual(original_cache.get('sweet'), 'sour')
     239            self.assertEqual(original_cache.get('salt'), 'pepper')
     240        finally:
     241            # Mark get_cache tests finished
     242            cache_get_test_finished()
     243
     244
     245class LocMemCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     246    backend_name = 'django.core.cache.backends.locmem.LocMemCache'
     247
     248    @classmethod
     249    def original_cache(cls):
     250        if not hasattr(cls, '_original_cache'):
     251            from django.core.cache import original_get_cache
     252            cls._original_cache = original_get_cache(cls.backend_name, LOCATION='test')
     253        return cls._original_cache
     254
     255    def modified_cache(self):
     256        return get_cache(self.backend_name, LOCATION='test')
     257
     258
     259class FileBasedCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     260    backend_name = 'django.core.cache.backends.filebased.FileBasedCache'
     261
     262    @classmethod
     263    def original_cache(cls):
     264        if not hasattr(cls, '_original_cache'):
     265            cls.cache_dirname = tempfile.mkdtemp()
     266            from django.core.cache import original_get_cache
     267            cls._original_cache = original_get_cache(cls.backend_name, LOCATION=cls.cache_dirname)
     268        return cls._original_cache
     269
     270    def modified_cache(self):
     271        return get_cache(self.backend_name, LOCATION=self.cache_dirname)
     272
     273
     274class DBCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     275    backend_name = 'django.core.cache.backends.db.DatabaseCache'
     276
     277    @classmethod
     278    def original_cache(cls):
     279        if not hasattr(cls, '_original_cache'):
     280            cls.cache_table_name = 'test_cache_table'
     281            management.call_command('createcachetable', cls.cache_table_name, verbosity=0, interactive=False)
     282            from django.core.cache import original_get_cache
     283            cls._original_cache = original_get_cache(cls.backend_name, LOCATION=cls.cache_table_name)
     284        return cls._original_cache
     285
     286    def modified_cache(self):
     287        return get_cache(self.backend_name, LOCATION=self.cache_table_name)
     288
     289    @classmethod
     290    def tearDownClass(cls):
     291        from django.db import connection
     292        cursor = connection.cursor()
     293        cursor.execute('DROP TABLE %s' % connection.ops.quote_name(cls.cache_table_name))
     294
     295
     296@skipUnless(settings.CACHES[DEFAULT_CACHE_ALIAS]['BACKEND'].startswith('django.core.cache.backends.memcached.'), "memcached not available")
     297class MemcachedCacheResetTests(BaseCacheReset, CacheResetTestsMixin):
     298    backend_name = 'django.core.cache.backends.memcached.MemcachedCache'
     299
     300    @classmethod
     301    def original_cache(cls):
     302        if not hasattr(cls, '_original_cache'):
     303            cls.memcached_location = settings.CACHES[DEFAULT_CACHE_ALIAS]['LOCATION']
     304            from django.core.cache import original_get_cache
     305            cls._original_cache = original_get_cache(cls.backend_name, LOCATION=cls.memcached_location)
     306        return cls._original_cache
     307
     308    def modified_cache(self):
     309        return get_cache(self.backend_name, LOCATION=self.memcached_location)
     310
     311
    134312__test__ = {"API_TEST": r"""
    135313# Some checks of the doctest output normalizer.
    136314# Standard doctests do fairly
Back to Top