|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Unit tests for cache framework |
|
4 # Uses whatever cache backend is set in the test settings file. |
|
5 |
|
6 import os |
|
7 import shutil |
|
8 import tempfile |
|
9 import time |
|
10 import unittest |
|
11 import warnings |
|
12 |
|
13 from django.conf import settings |
|
14 from django.core import management |
|
15 from django.core.cache import get_cache |
|
16 from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning |
|
17 from django.http import HttpResponse, HttpRequest |
|
18 from django.middleware.cache import FetchFromCacheMiddleware, UpdateCacheMiddleware |
|
19 from django.test.utils import get_warnings_state, restore_warnings_state |
|
20 from django.utils import translation |
|
21 from django.utils.cache import patch_vary_headers, get_cache_key, learn_cache_key |
|
22 from django.utils.hashcompat import md5_constructor |
|
23 from regressiontests.cache.models import Poll, expensive_calculation |
|
24 |
|
25 # functions/classes for complex data type tests |
|
26 def f(): |
|
27 return 42 |
|
28 class C: |
|
29 def m(n): |
|
30 return 24 |
|
31 |
|
32 class DummyCacheTests(unittest.TestCase): |
|
33 # The Dummy cache backend doesn't really behave like a test backend, |
|
34 # so it has different test requirements. |
|
35 def setUp(self): |
|
36 self.cache = get_cache('dummy://') |
|
37 |
|
38 def test_simple(self): |
|
39 "Dummy cache backend ignores cache set calls" |
|
40 self.cache.set("key", "value") |
|
41 self.assertEqual(self.cache.get("key"), None) |
|
42 |
|
43 def test_add(self): |
|
44 "Add doesn't do anything in dummy cache backend" |
|
45 self.cache.add("addkey1", "value") |
|
46 result = self.cache.add("addkey1", "newvalue") |
|
47 self.assertEqual(result, True) |
|
48 self.assertEqual(self.cache.get("addkey1"), None) |
|
49 |
|
50 def test_non_existent(self): |
|
51 "Non-existent keys aren't found in the dummy cache backend" |
|
52 self.assertEqual(self.cache.get("does_not_exist"), None) |
|
53 self.assertEqual(self.cache.get("does_not_exist", "bang!"), "bang!") |
|
54 |
|
55 def test_get_many(self): |
|
56 "get_many returns nothing for the dummy cache backend" |
|
57 self.cache.set('a', 'a') |
|
58 self.cache.set('b', 'b') |
|
59 self.cache.set('c', 'c') |
|
60 self.cache.set('d', 'd') |
|
61 self.assertEqual(self.cache.get_many(['a', 'c', 'd']), {}) |
|
62 self.assertEqual(self.cache.get_many(['a', 'b', 'e']), {}) |
|
63 |
|
64 def test_delete(self): |
|
65 "Cache deletion is transparently ignored on the dummy cache backend" |
|
66 self.cache.set("key1", "spam") |
|
67 self.cache.set("key2", "eggs") |
|
68 self.assertEqual(self.cache.get("key1"), None) |
|
69 self.cache.delete("key1") |
|
70 self.assertEqual(self.cache.get("key1"), None) |
|
71 self.assertEqual(self.cache.get("key2"), None) |
|
72 |
|
73 def test_has_key(self): |
|
74 "The has_key method doesn't ever return True for the dummy cache backend" |
|
75 self.cache.set("hello1", "goodbye1") |
|
76 self.assertEqual(self.cache.has_key("hello1"), False) |
|
77 self.assertEqual(self.cache.has_key("goodbye1"), False) |
|
78 |
|
79 def test_in(self): |
|
80 "The in operator doesn't ever return True for the dummy cache backend" |
|
81 self.cache.set("hello2", "goodbye2") |
|
82 self.assertEqual("hello2" in self.cache, False) |
|
83 self.assertEqual("goodbye2" in self.cache, False) |
|
84 |
|
85 def test_incr(self): |
|
86 "Dummy cache values can't be incremented" |
|
87 self.cache.set('answer', 42) |
|
88 self.assertRaises(ValueError, self.cache.incr, 'answer') |
|
89 self.assertRaises(ValueError, self.cache.incr, 'does_not_exist') |
|
90 |
|
91 def test_decr(self): |
|
92 "Dummy cache values can't be decremented" |
|
93 self.cache.set('answer', 42) |
|
94 self.assertRaises(ValueError, self.cache.decr, 'answer') |
|
95 self.assertRaises(ValueError, self.cache.decr, 'does_not_exist') |
|
96 |
|
97 def test_data_types(self): |
|
98 "All data types are ignored equally by the dummy cache" |
|
99 stuff = { |
|
100 'string' : 'this is a string', |
|
101 'int' : 42, |
|
102 'list' : [1, 2, 3, 4], |
|
103 'tuple' : (1, 2, 3, 4), |
|
104 'dict' : {'A': 1, 'B' : 2}, |
|
105 'function' : f, |
|
106 'class' : C, |
|
107 } |
|
108 self.cache.set("stuff", stuff) |
|
109 self.assertEqual(self.cache.get("stuff"), None) |
|
110 |
|
111 def test_expiration(self): |
|
112 "Expiration has no effect on the dummy cache" |
|
113 self.cache.set('expire1', 'very quickly', 1) |
|
114 self.cache.set('expire2', 'very quickly', 1) |
|
115 self.cache.set('expire3', 'very quickly', 1) |
|
116 |
|
117 time.sleep(2) |
|
118 self.assertEqual(self.cache.get("expire1"), None) |
|
119 |
|
120 self.cache.add("expire2", "newvalue") |
|
121 self.assertEqual(self.cache.get("expire2"), None) |
|
122 self.assertEqual(self.cache.has_key("expire3"), False) |
|
123 |
|
124 def test_unicode(self): |
|
125 "Unicode values are ignored by the dummy cache" |
|
126 stuff = { |
|
127 u'ascii': u'ascii_value', |
|
128 u'unicode_ascii': u'Iñtërnâtiônàlizætiøn1', |
|
129 u'Iñtërnâtiônàlizætiøn': u'Iñtërnâtiônàlizætiøn2', |
|
130 u'ascii': {u'x' : 1 } |
|
131 } |
|
132 for (key, value) in stuff.items(): |
|
133 self.cache.set(key, value) |
|
134 self.assertEqual(self.cache.get(key), None) |
|
135 |
|
136 def test_set_many(self): |
|
137 "set_many does nothing for the dummy cache backend" |
|
138 self.cache.set_many({'a': 1, 'b': 2}) |
|
139 |
|
140 def test_delete_many(self): |
|
141 "delete_many does nothing for the dummy cache backend" |
|
142 self.cache.delete_many(['a', 'b']) |
|
143 |
|
144 def test_clear(self): |
|
145 "clear does nothing for the dummy cache backend" |
|
146 self.cache.clear() |
|
147 |
|
148 |
|
149 class BaseCacheTests(object): |
|
150 # A common set of tests to apply to all cache backends |
|
151 def tearDown(self): |
|
152 self.cache.clear() |
|
153 |
|
154 def test_simple(self): |
|
155 # Simple cache set/get works |
|
156 self.cache.set("key", "value") |
|
157 self.assertEqual(self.cache.get("key"), "value") |
|
158 |
|
159 def test_add(self): |
|
160 # A key can be added to a cache |
|
161 self.cache.add("addkey1", "value") |
|
162 result = self.cache.add("addkey1", "newvalue") |
|
163 self.assertEqual(result, False) |
|
164 self.assertEqual(self.cache.get("addkey1"), "value") |
|
165 |
|
166 def test_non_existent(self): |
|
167 # Non-existent cache keys return as None/default |
|
168 # get with non-existent keys |
|
169 self.assertEqual(self.cache.get("does_not_exist"), None) |
|
170 self.assertEqual(self.cache.get("does_not_exist", "bang!"), "bang!") |
|
171 |
|
172 def test_get_many(self): |
|
173 # Multiple cache keys can be returned using get_many |
|
174 self.cache.set('a', 'a') |
|
175 self.cache.set('b', 'b') |
|
176 self.cache.set('c', 'c') |
|
177 self.cache.set('d', 'd') |
|
178 self.assertEqual(self.cache.get_many(['a', 'c', 'd']), {'a' : 'a', 'c' : 'c', 'd' : 'd'}) |
|
179 self.assertEqual(self.cache.get_many(['a', 'b', 'e']), {'a' : 'a', 'b' : 'b'}) |
|
180 |
|
181 def test_delete(self): |
|
182 # Cache keys can be deleted |
|
183 self.cache.set("key1", "spam") |
|
184 self.cache.set("key2", "eggs") |
|
185 self.assertEqual(self.cache.get("key1"), "spam") |
|
186 self.cache.delete("key1") |
|
187 self.assertEqual(self.cache.get("key1"), None) |
|
188 self.assertEqual(self.cache.get("key2"), "eggs") |
|
189 |
|
190 def test_has_key(self): |
|
191 # The cache can be inspected for cache keys |
|
192 self.cache.set("hello1", "goodbye1") |
|
193 self.assertEqual(self.cache.has_key("hello1"), True) |
|
194 self.assertEqual(self.cache.has_key("goodbye1"), False) |
|
195 |
|
196 def test_in(self): |
|
197 # The in operator can be used to inspet cache contents |
|
198 self.cache.set("hello2", "goodbye2") |
|
199 self.assertEqual("hello2" in self.cache, True) |
|
200 self.assertEqual("goodbye2" in self.cache, False) |
|
201 |
|
202 def test_incr(self): |
|
203 # Cache values can be incremented |
|
204 self.cache.set('answer', 41) |
|
205 self.assertEqual(self.cache.incr('answer'), 42) |
|
206 self.assertEqual(self.cache.get('answer'), 42) |
|
207 self.assertEqual(self.cache.incr('answer', 10), 52) |
|
208 self.assertEqual(self.cache.get('answer'), 52) |
|
209 self.assertRaises(ValueError, self.cache.incr, 'does_not_exist') |
|
210 |
|
211 def test_decr(self): |
|
212 # Cache values can be decremented |
|
213 self.cache.set('answer', 43) |
|
214 self.assertEqual(self.cache.decr('answer'), 42) |
|
215 self.assertEqual(self.cache.get('answer'), 42) |
|
216 self.assertEqual(self.cache.decr('answer', 10), 32) |
|
217 self.assertEqual(self.cache.get('answer'), 32) |
|
218 self.assertRaises(ValueError, self.cache.decr, 'does_not_exist') |
|
219 |
|
220 def test_data_types(self): |
|
221 # Many different data types can be cached |
|
222 stuff = { |
|
223 'string' : 'this is a string', |
|
224 'int' : 42, |
|
225 'list' : [1, 2, 3, 4], |
|
226 'tuple' : (1, 2, 3, 4), |
|
227 'dict' : {'A': 1, 'B' : 2}, |
|
228 'function' : f, |
|
229 'class' : C, |
|
230 } |
|
231 self.cache.set("stuff", stuff) |
|
232 self.assertEqual(self.cache.get("stuff"), stuff) |
|
233 |
|
234 def test_cache_read_for_model_instance(self): |
|
235 # Don't want fields with callable as default to be called on cache read |
|
236 expensive_calculation.num_runs = 0 |
|
237 Poll.objects.all().delete() |
|
238 my_poll = Poll.objects.create(question="Well?") |
|
239 self.assertEqual(Poll.objects.count(), 1) |
|
240 pub_date = my_poll.pub_date |
|
241 self.cache.set('question', my_poll) |
|
242 cached_poll = self.cache.get('question') |
|
243 self.assertEqual(cached_poll.pub_date, pub_date) |
|
244 # We only want the default expensive calculation run once |
|
245 self.assertEqual(expensive_calculation.num_runs, 1) |
|
246 |
|
247 def test_cache_write_for_model_instance_with_deferred(self): |
|
248 # Don't want fields with callable as default to be called on cache write |
|
249 expensive_calculation.num_runs = 0 |
|
250 Poll.objects.all().delete() |
|
251 my_poll = Poll.objects.create(question="What?") |
|
252 self.assertEqual(expensive_calculation.num_runs, 1) |
|
253 defer_qs = Poll.objects.all().defer('question') |
|
254 self.assertEqual(defer_qs.count(), 1) |
|
255 self.assertEqual(expensive_calculation.num_runs, 1) |
|
256 self.cache.set('deferred_queryset', defer_qs) |
|
257 # cache set should not re-evaluate default functions |
|
258 self.assertEqual(expensive_calculation.num_runs, 1) |
|
259 |
|
260 def test_cache_read_for_model_instance_with_deferred(self): |
|
261 # Don't want fields with callable as default to be called on cache read |
|
262 expensive_calculation.num_runs = 0 |
|
263 Poll.objects.all().delete() |
|
264 my_poll = Poll.objects.create(question="What?") |
|
265 self.assertEqual(expensive_calculation.num_runs, 1) |
|
266 defer_qs = Poll.objects.all().defer('question') |
|
267 self.assertEqual(defer_qs.count(), 1) |
|
268 self.cache.set('deferred_queryset', defer_qs) |
|
269 self.assertEqual(expensive_calculation.num_runs, 1) |
|
270 runs_before_cache_read = expensive_calculation.num_runs |
|
271 cached_polls = self.cache.get('deferred_queryset') |
|
272 # We only want the default expensive calculation run on creation and set |
|
273 self.assertEqual(expensive_calculation.num_runs, runs_before_cache_read) |
|
274 |
|
275 def test_expiration(self): |
|
276 # Cache values can be set to expire |
|
277 self.cache.set('expire1', 'very quickly', 1) |
|
278 self.cache.set('expire2', 'very quickly', 1) |
|
279 self.cache.set('expire3', 'very quickly', 1) |
|
280 |
|
281 time.sleep(2) |
|
282 self.assertEqual(self.cache.get("expire1"), None) |
|
283 |
|
284 self.cache.add("expire2", "newvalue") |
|
285 self.assertEqual(self.cache.get("expire2"), "newvalue") |
|
286 self.assertEqual(self.cache.has_key("expire3"), False) |
|
287 |
|
288 def test_unicode(self): |
|
289 # Unicode values can be cached |
|
290 stuff = { |
|
291 u'ascii': u'ascii_value', |
|
292 u'unicode_ascii': u'Iñtërnâtiônàlizætiøn1', |
|
293 u'Iñtërnâtiônàlizætiøn': u'Iñtërnâtiônàlizætiøn2', |
|
294 u'ascii': {u'x' : 1 } |
|
295 } |
|
296 for (key, value) in stuff.items(): |
|
297 self.cache.set(key, value) |
|
298 self.assertEqual(self.cache.get(key), value) |
|
299 |
|
300 def test_binary_string(self): |
|
301 # Binary strings should be cachable |
|
302 from zlib import compress, decompress |
|
303 value = 'value_to_be_compressed' |
|
304 compressed_value = compress(value) |
|
305 self.cache.set('binary1', compressed_value) |
|
306 compressed_result = self.cache.get('binary1') |
|
307 self.assertEqual(compressed_value, compressed_result) |
|
308 self.assertEqual(value, decompress(compressed_result)) |
|
309 |
|
310 def test_set_many(self): |
|
311 # Multiple keys can be set using set_many |
|
312 self.cache.set_many({"key1": "spam", "key2": "eggs"}) |
|
313 self.assertEqual(self.cache.get("key1"), "spam") |
|
314 self.assertEqual(self.cache.get("key2"), "eggs") |
|
315 |
|
316 def test_set_many_expiration(self): |
|
317 # set_many takes a second ``timeout`` parameter |
|
318 self.cache.set_many({"key1": "spam", "key2": "eggs"}, 1) |
|
319 time.sleep(2) |
|
320 self.assertEqual(self.cache.get("key1"), None) |
|
321 self.assertEqual(self.cache.get("key2"), None) |
|
322 |
|
323 def test_delete_many(self): |
|
324 # Multiple keys can be deleted using delete_many |
|
325 self.cache.set("key1", "spam") |
|
326 self.cache.set("key2", "eggs") |
|
327 self.cache.set("key3", "ham") |
|
328 self.cache.delete_many(["key1", "key2"]) |
|
329 self.assertEqual(self.cache.get("key1"), None) |
|
330 self.assertEqual(self.cache.get("key2"), None) |
|
331 self.assertEqual(self.cache.get("key3"), "ham") |
|
332 |
|
333 def test_clear(self): |
|
334 # The cache can be emptied using clear |
|
335 self.cache.set("key1", "spam") |
|
336 self.cache.set("key2", "eggs") |
|
337 self.cache.clear() |
|
338 self.assertEqual(self.cache.get("key1"), None) |
|
339 self.assertEqual(self.cache.get("key2"), None) |
|
340 |
|
341 def test_long_timeout(self): |
|
342 ''' |
|
343 Using a timeout greater than 30 days makes memcached think |
|
344 it is an absolute expiration timestamp instead of a relative |
|
345 offset. Test that we honour this convention. Refs #12399. |
|
346 ''' |
|
347 self.cache.set('key1', 'eggs', 60*60*24*30 + 1) #30 days + 1 second |
|
348 self.assertEqual(self.cache.get('key1'), 'eggs') |
|
349 |
|
350 self.cache.add('key2', 'ham', 60*60*24*30 + 1) |
|
351 self.assertEqual(self.cache.get('key2'), 'ham') |
|
352 |
|
353 self.cache.set_many({'key3': 'sausage', 'key4': 'lobster bisque'}, 60*60*24*30 + 1) |
|
354 self.assertEqual(self.cache.get('key3'), 'sausage') |
|
355 self.assertEqual(self.cache.get('key4'), 'lobster bisque') |
|
356 |
|
357 def perform_cull_test(self, initial_count, final_count): |
|
358 """This is implemented as a utility method, because only some of the backends |
|
359 implement culling. The culling algorithm also varies slightly, so the final |
|
360 number of entries will vary between backends""" |
|
361 # Create initial cache key entries. This will overflow the cache, causing a cull |
|
362 for i in range(1, initial_count): |
|
363 self.cache.set('cull%d' % i, 'value', 1000) |
|
364 count = 0 |
|
365 # Count how many keys are left in the cache. |
|
366 for i in range(1, initial_count): |
|
367 if self.cache.has_key('cull%d' % i): |
|
368 count = count + 1 |
|
369 self.assertEqual(count, final_count) |
|
370 |
|
371 def test_invalid_keys(self): |
|
372 """ |
|
373 All the builtin backends (except memcached, see below) should warn on |
|
374 keys that would be refused by memcached. This encourages portable |
|
375 caching code without making it too difficult to use production backends |
|
376 with more liberal key rules. Refs #6447. |
|
377 |
|
378 """ |
|
379 # On Python 2.6+ we could use the catch_warnings context |
|
380 # manager to test this warning nicely. Since we can't do that |
|
381 # yet, the cleanest option is to temporarily ask for |
|
382 # CacheKeyWarning to be raised as an exception. |
|
383 _warnings_state = get_warnings_state() |
|
384 warnings.simplefilter("error", CacheKeyWarning) |
|
385 |
|
386 try: |
|
387 # memcached does not allow whitespace or control characters in keys |
|
388 self.assertRaises(CacheKeyWarning, self.cache.set, 'key with spaces', 'value') |
|
389 # memcached limits key length to 250 |
|
390 self.assertRaises(CacheKeyWarning, self.cache.set, 'a' * 251, 'value') |
|
391 finally: |
|
392 restore_warnings_state(_warnings_state) |
|
393 |
|
394 class DBCacheTests(unittest.TestCase, BaseCacheTests): |
|
395 def setUp(self): |
|
396 # Spaces are used in the table name to ensure quoting/escaping is working |
|
397 self._table_name = 'test cache table' |
|
398 management.call_command('createcachetable', self._table_name, verbosity=0, interactive=False) |
|
399 self.cache = get_cache('db://%s?max_entries=30' % self._table_name) |
|
400 |
|
401 def tearDown(self): |
|
402 from django.db import connection |
|
403 cursor = connection.cursor() |
|
404 cursor.execute('DROP TABLE %s' % connection.ops.quote_name(self._table_name)) |
|
405 |
|
406 def test_cull(self): |
|
407 self.perform_cull_test(50, 29) |
|
408 |
|
409 class LocMemCacheTests(unittest.TestCase, BaseCacheTests): |
|
410 def setUp(self): |
|
411 self.cache = get_cache('locmem://?max_entries=30') |
|
412 |
|
413 def test_cull(self): |
|
414 self.perform_cull_test(50, 29) |
|
415 |
|
416 # memcached backend isn't guaranteed to be available. |
|
417 # To check the memcached backend, the test settings file will |
|
418 # need to contain a CACHE_BACKEND setting that points at |
|
419 # your memcache server. |
|
420 if settings.CACHE_BACKEND.startswith('memcached://'): |
|
421 class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): |
|
422 def setUp(self): |
|
423 self.cache = get_cache(settings.CACHE_BACKEND) |
|
424 |
|
425 def test_invalid_keys(self): |
|
426 """ |
|
427 On memcached, we don't introduce a duplicate key validation |
|
428 step (for speed reasons), we just let the memcached API |
|
429 library raise its own exception on bad keys. Refs #6447. |
|
430 |
|
431 In order to be memcached-API-library agnostic, we only assert |
|
432 that a generic exception of some kind is raised. |
|
433 |
|
434 """ |
|
435 # memcached does not allow whitespace or control characters in keys |
|
436 self.assertRaises(Exception, self.cache.set, 'key with spaces', 'value') |
|
437 # memcached limits key length to 250 |
|
438 self.assertRaises(Exception, self.cache.set, 'a' * 251, 'value') |
|
439 |
|
440 |
|
441 class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): |
|
442 """ |
|
443 Specific test cases for the file-based cache. |
|
444 """ |
|
445 def setUp(self): |
|
446 self.dirname = tempfile.mkdtemp() |
|
447 self.cache = get_cache('file://%s?max_entries=30' % self.dirname) |
|
448 |
|
449 def test_hashing(self): |
|
450 """Test that keys are hashed into subdirectories correctly""" |
|
451 self.cache.set("foo", "bar") |
|
452 keyhash = md5_constructor("foo").hexdigest() |
|
453 keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) |
|
454 self.assert_(os.path.exists(keypath)) |
|
455 |
|
456 def test_subdirectory_removal(self): |
|
457 """ |
|
458 Make sure that the created subdirectories are correctly removed when empty. |
|
459 """ |
|
460 self.cache.set("foo", "bar") |
|
461 keyhash = md5_constructor("foo").hexdigest() |
|
462 keypath = os.path.join(self.dirname, keyhash[:2], keyhash[2:4], keyhash[4:]) |
|
463 self.assert_(os.path.exists(keypath)) |
|
464 |
|
465 self.cache.delete("foo") |
|
466 self.assert_(not os.path.exists(keypath)) |
|
467 self.assert_(not os.path.exists(os.path.dirname(keypath))) |
|
468 self.assert_(not os.path.exists(os.path.dirname(os.path.dirname(keypath)))) |
|
469 |
|
470 def test_cull(self): |
|
471 self.perform_cull_test(50, 28) |
|
472 |
|
473 class CustomCacheKeyValidationTests(unittest.TestCase): |
|
474 """ |
|
475 Tests for the ability to mixin a custom ``validate_key`` method to |
|
476 a custom cache backend that otherwise inherits from a builtin |
|
477 backend, and override the default key validation. Refs #6447. |
|
478 |
|
479 """ |
|
480 def test_custom_key_validation(self): |
|
481 cache = get_cache('regressiontests.cache.liberal_backend://') |
|
482 |
|
483 # this key is both longer than 250 characters, and has spaces |
|
484 key = 'some key with spaces' * 15 |
|
485 val = 'a value' |
|
486 cache.set(key, val) |
|
487 self.assertEqual(cache.get(key), val) |
|
488 |
|
489 class CacheUtils(unittest.TestCase): |
|
490 """TestCase for django.utils.cache functions.""" |
|
491 |
|
492 def setUp(self): |
|
493 self.path = '/cache/test/' |
|
494 self.old_settings_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX |
|
495 self.old_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS |
|
496 self.orig_use_i18n = settings.USE_I18N |
|
497 settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix' |
|
498 settings.CACHE_MIDDLEWARE_SECONDS = 1 |
|
499 settings.USE_I18N = False |
|
500 |
|
501 def tearDown(self): |
|
502 settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.old_settings_key_prefix |
|
503 settings.CACHE_MIDDLEWARE_SECONDS = self.old_middleware_seconds |
|
504 settings.USE_I18N = self.orig_use_i18n |
|
505 |
|
506 def _get_request(self, path): |
|
507 request = HttpRequest() |
|
508 request.META = { |
|
509 'SERVER_NAME': 'testserver', |
|
510 'SERVER_PORT': 80, |
|
511 } |
|
512 request.path = request.path_info = "/cache/%s" % path |
|
513 return request |
|
514 |
|
515 def test_patch_vary_headers(self): |
|
516 headers = ( |
|
517 # Initial vary, new headers, resulting vary. |
|
518 (None, ('Accept-Encoding',), 'Accept-Encoding'), |
|
519 ('Accept-Encoding', ('accept-encoding',), 'Accept-Encoding'), |
|
520 ('Accept-Encoding', ('ACCEPT-ENCODING',), 'Accept-Encoding'), |
|
521 ('Cookie', ('Accept-Encoding',), 'Cookie, Accept-Encoding'), |
|
522 ('Cookie, Accept-Encoding', ('Accept-Encoding',), 'Cookie, Accept-Encoding'), |
|
523 ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'), |
|
524 (None, ('Accept-Encoding', 'COOKIE'), 'Accept-Encoding, COOKIE'), |
|
525 ('Cookie, Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'), |
|
526 ('Cookie , Accept-Encoding', ('Accept-Encoding', 'cookie'), 'Cookie, Accept-Encoding'), |
|
527 ) |
|
528 for initial_vary, newheaders, resulting_vary in headers: |
|
529 response = HttpResponse() |
|
530 if initial_vary is not None: |
|
531 response['Vary'] = initial_vary |
|
532 patch_vary_headers(response, newheaders) |
|
533 self.assertEqual(response['Vary'], resulting_vary) |
|
534 |
|
535 def test_get_cache_key(self): |
|
536 request = self._get_request(self.path) |
|
537 response = HttpResponse() |
|
538 key_prefix = 'localprefix' |
|
539 # Expect None if no headers have been set yet. |
|
540 self.assertEqual(get_cache_key(request), None) |
|
541 # Set headers to an empty list. |
|
542 learn_cache_key(request, response) |
|
543 self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') |
|
544 # Verify that a specified key_prefix is taken in to account. |
|
545 learn_cache_key(request, response, key_prefix=key_prefix) |
|
546 self.assertEqual(get_cache_key(request, key_prefix=key_prefix), 'views.decorators.cache.cache_page.localprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') |
|
547 |
|
548 def test_learn_cache_key(self): |
|
549 request = self._get_request(self.path) |
|
550 response = HttpResponse() |
|
551 response['Vary'] = 'Pony' |
|
552 # Make sure that the Vary header is added to the key hash |
|
553 learn_cache_key(request, response) |
|
554 self.assertEqual(get_cache_key(request), 'views.decorators.cache.cache_page.settingsprefix.a8c87a3d8c44853d7f79474f7ffe4ad5.d41d8cd98f00b204e9800998ecf8427e') |
|
555 |
|
556 class CacheI18nTest(unittest.TestCase): |
|
557 |
|
558 def setUp(self): |
|
559 self.orig_cache_middleware_seconds = settings.CACHE_MIDDLEWARE_SECONDS |
|
560 self.orig_cache_middleware_key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX |
|
561 self.orig_cache_backend = settings.CACHE_BACKEND |
|
562 self.orig_use_i18n = settings.USE_I18N |
|
563 self.orig_languages = settings.LANGUAGES |
|
564 settings.LANGUAGES = ( |
|
565 ('en', 'English'), |
|
566 ('es', 'Spanish'), |
|
567 ) |
|
568 settings.CACHE_MIDDLEWARE_KEY_PREFIX = 'settingsprefix' |
|
569 self.path = '/cache/test/' |
|
570 |
|
571 def tearDown(self): |
|
572 settings.CACHE_MIDDLEWARE_SECONDS = self.orig_cache_middleware_seconds |
|
573 settings.CACHE_MIDDLEWARE_KEY_PREFIX = self.orig_cache_middleware_key_prefix |
|
574 settings.CACHE_BACKEND = self.orig_cache_backend |
|
575 settings.USE_I18N = self.orig_use_i18n |
|
576 settings.LANGUAGES = self.orig_languages |
|
577 translation.deactivate() |
|
578 |
|
579 def _get_request(self): |
|
580 request = HttpRequest() |
|
581 request.META = { |
|
582 'SERVER_NAME': 'testserver', |
|
583 'SERVER_PORT': 80, |
|
584 } |
|
585 request.path = request.path_info = self.path |
|
586 return request |
|
587 |
|
588 def _get_request_cache(self): |
|
589 request = HttpRequest() |
|
590 request.META = { |
|
591 'SERVER_NAME': 'testserver', |
|
592 'SERVER_PORT': 80, |
|
593 } |
|
594 request.path = request.path_info = self.path |
|
595 request._cache_update_cache = True |
|
596 request.method = 'GET' |
|
597 request.session = {} |
|
598 return request |
|
599 |
|
600 def test_cache_key_i18n(self): |
|
601 settings.USE_I18N = True |
|
602 request = self._get_request() |
|
603 lang = translation.get_language() |
|
604 response = HttpResponse() |
|
605 key = learn_cache_key(request, response) |
|
606 self.assertTrue(key.endswith(lang), "Cache keys should include the language name when i18n is active") |
|
607 key2 = get_cache_key(request) |
|
608 self.assertEqual(key, key2) |
|
609 |
|
610 def test_cache_key_no_i18n (self): |
|
611 settings.USE_I18N = False |
|
612 request = self._get_request() |
|
613 lang = translation.get_language() |
|
614 response = HttpResponse() |
|
615 key = learn_cache_key(request, response) |
|
616 self.assertFalse(key.endswith(lang), "Cache keys shouldn't include the language name when i18n is inactive") |
|
617 |
|
618 def test_middleware(self): |
|
619 def set_cache(request, lang, msg): |
|
620 translation.activate(lang) |
|
621 response = HttpResponse() |
|
622 response.content= msg |
|
623 return UpdateCacheMiddleware().process_response(request, response) |
|
624 |
|
625 settings.CACHE_MIDDLEWARE_SECONDS = 60 |
|
626 settings.CACHE_MIDDLEWARE_KEY_PREFIX="test" |
|
627 settings.CACHE_BACKEND='locmem:///' |
|
628 settings.USE_I18N = True |
|
629 en_message ="Hello world!" |
|
630 es_message ="Hola mundo!" |
|
631 |
|
632 request = self._get_request_cache() |
|
633 set_cache(request, 'en', en_message) |
|
634 get_cache_data = FetchFromCacheMiddleware().process_request(request) |
|
635 # Check that we can recover the cache |
|
636 self.assertNotEqual(get_cache_data.content, None) |
|
637 self.assertEqual(en_message, get_cache_data.content) |
|
638 # change the session language and set content |
|
639 request = self._get_request_cache() |
|
640 set_cache(request, 'es', es_message) |
|
641 # change again the language |
|
642 translation.activate('en') |
|
643 # retrieve the content from cache |
|
644 get_cache_data = FetchFromCacheMiddleware().process_request(request) |
|
645 self.assertEqual(get_cache_data.content, en_message) |
|
646 # change again the language |
|
647 translation.activate('es') |
|
648 get_cache_data = FetchFromCacheMiddleware().process_request(request) |
|
649 self.assertEqual(get_cache_data.content, es_message) |
|
650 |
|
651 if __name__ == '__main__': |
|
652 unittest.main() |