blob: 4ced3698c8c7009f785644e056dd559f1ce1f80c [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file or at
4# https://developers.google.com/open-source/licenses/bsd
5
6"""Tests for the cache classes."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import fakeredis
12import unittest
13
14from google.appengine.api import memcache
15from google.appengine.ext import testbed
16
17import settings
18from services import caches
19from testing import fake
20
21
22class RamCacheTest(unittest.TestCase):
23
24 def setUp(self):
25 self.cnxn = 'fake connection'
26 self.cache_manager = fake.CacheManager()
27 self.ram_cache = caches.RamCache(self.cache_manager, 'issue', max_size=3)
28
29 def testInit(self):
30 self.assertEqual('issue', self.ram_cache.kind)
31 self.assertEqual(3, self.ram_cache.max_size)
32 self.assertEqual(
33 [self.ram_cache],
34 self.cache_manager.cache_registry['issue'])
35
36 def testCacheItem(self):
37 self.ram_cache.CacheItem(123, 'foo')
38 self.assertEqual('foo', self.ram_cache.cache[123])
39
40 def testCacheItem_DropsOldItems(self):
41 self.ram_cache.CacheItem(123, 'foo')
42 self.ram_cache.CacheItem(234, 'foo')
43 self.ram_cache.CacheItem(345, 'foo')
44 self.ram_cache.CacheItem(456, 'foo')
45 # The cache does not get bigger than its limit.
46 self.assertEqual(3, len(self.ram_cache.cache))
47 # An old value is dropped, not the newly added one.
48 self.assertIn(456, self.ram_cache.cache)
49
50 def testCacheAll(self):
51 self.ram_cache.CacheAll({123: 'foo'})
52 self.assertEqual('foo', self.ram_cache.cache[123])
53
54 def testCacheAll_DropsOldItems(self):
55 self.ram_cache.CacheAll({1: 'a', 2: 'b', 3: 'c'})
56 self.ram_cache.CacheAll({4: 'x', 5: 'y'})
57 # The cache does not get bigger than its limit.
58 self.assertEqual(3, len(self.ram_cache.cache))
59 # An old value is dropped, not the newly added one.
60 self.assertIn(4, self.ram_cache.cache)
61 self.assertIn(5, self.ram_cache.cache)
62 self.assertEqual('y', self.ram_cache.cache[5])
63
64 def testHasItem(self):
65 self.ram_cache.CacheItem(123, 'foo')
66 self.assertTrue(self.ram_cache.HasItem(123))
67 self.assertFalse(self.ram_cache.HasItem(999))
68
69 def testGetItem(self):
70 self.ram_cache.CacheItem(123, 'foo')
71 self.assertEqual('foo', self.ram_cache.GetItem(123))
72 self.assertEqual(None, self.ram_cache.GetItem(456))
73
74 def testGetAll(self):
75 self.ram_cache.CacheItem(123, 'foo')
76 self.ram_cache.CacheItem(124, 'bar')
77 hits, misses = self.ram_cache.GetAll([123, 124, 999])
78 self.assertEqual({123: 'foo', 124: 'bar'}, hits)
79 self.assertEqual([999], misses)
80
81 def testLocalInvalidate(self):
82 self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
83 self.ram_cache.LocalInvalidate(124)
84 self.assertEqual(2, len(self.ram_cache.cache))
85 self.assertNotIn(124, self.ram_cache.cache)
86
87 self.ram_cache.LocalInvalidate(999)
88 self.assertEqual(2, len(self.ram_cache.cache))
89
90 def testInvalidate(self):
91 self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
92 self.ram_cache.Invalidate(self.cnxn, 124)
93 self.assertEqual(2, len(self.ram_cache.cache))
94 self.assertNotIn(124, self.ram_cache.cache)
95 self.assertEqual(self.cache_manager.last_call,
96 ('StoreInvalidateRows', self.cnxn, 'issue', [124]))
97
98 def testInvalidateKeys(self):
99 self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
100 self.ram_cache.InvalidateKeys(self.cnxn, [124])
101 self.assertEqual(2, len(self.ram_cache.cache))
102 self.assertNotIn(124, self.ram_cache.cache)
103 self.assertEqual(self.cache_manager.last_call,
104 ('StoreInvalidateRows', self.cnxn, 'issue', [124]))
105
106 def testLocalInvalidateAll(self):
107 self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
108 self.ram_cache.LocalInvalidateAll()
109 self.assertEqual(0, len(self.ram_cache.cache))
110
111 def testInvalidateAll(self):
112 self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
113 self.ram_cache.InvalidateAll(self.cnxn)
114 self.assertEqual(0, len(self.ram_cache.cache))
115 self.assertEqual(self.cache_manager.last_call,
116 ('StoreInvalidateAll', self.cnxn, 'issue'))
117
118
119class ShardedRamCacheTest(unittest.TestCase):
120
121 def setUp(self):
122 self.cnxn = 'fake connection'
123 self.cache_manager = fake.CacheManager()
124 self.sharded_ram_cache = caches.ShardedRamCache(
125 self.cache_manager, 'issue', max_size=3, num_shards=3)
126
127 def testLocalInvalidate(self):
128 self.sharded_ram_cache.CacheAll({
129 (123, 0): 'a',
130 (123, 1): 'aa',
131 (123, 2): 'aaa',
132 (124, 0): 'b',
133 (124, 1): 'bb',
134 (124, 2): 'bbb',
135 })
136 self.sharded_ram_cache.LocalInvalidate(124)
137 self.assertEqual(3, len(self.sharded_ram_cache.cache))
138 self.assertNotIn((124, 0), self.sharded_ram_cache.cache)
139 self.assertNotIn((124, 1), self.sharded_ram_cache.cache)
140 self.assertNotIn((124, 2), self.sharded_ram_cache.cache)
141
142 self.sharded_ram_cache.LocalInvalidate(999)
143 self.assertEqual(3, len(self.sharded_ram_cache.cache))
144
145
146class TestableTwoLevelCache(caches.AbstractTwoLevelCache):
147
148 def __init__(
149 self,
150 cache_manager,
151 kind,
152 max_size=None,
153 use_redis=False,
154 redis_client=None):
155 super(TestableTwoLevelCache, self).__init__(
156 cache_manager,
157 kind,
158 'testable:',
159 None,
160 max_size=max_size,
161 use_redis=use_redis,
162 redis_client=redis_client)
163
164 # pylint: disable=unused-argument
165 def FetchItems(self, cnxn, keys, **kwargs):
166 """On RAM and memcache miss, hit the database."""
167 return {key: key for key in keys if key < 900}
168
169
170class AbstractTwoLevelCacheTest_Memcache(unittest.TestCase):
171
172 def setUp(self):
173 self.testbed = testbed.Testbed()
174 self.testbed.activate()
175 self.testbed.init_memcache_stub()
176
177 self.cnxn = 'fake connection'
178 self.cache_manager = fake.CacheManager()
179 self.testable_2lc = TestableTwoLevelCache(self.cache_manager, 'issue')
180
181 def tearDown(self):
182 self.testbed.deactivate()
183
184 def testCacheItem(self):
185 self.testable_2lc.CacheItem(123, 12300)
186 self.assertEqual(12300, self.testable_2lc.cache.cache[123])
187
188 def testHasItem(self):
189 self.testable_2lc.CacheItem(123, 12300)
190 self.assertTrue(self.testable_2lc.HasItem(123))
191 self.assertFalse(self.testable_2lc.HasItem(444))
192 self.assertFalse(self.testable_2lc.HasItem(999))
193
194 def testWriteToMemcache_Normal(self):
195 retrieved_dict = {123: 12300, 124: 12400}
196 self.testable_2lc._WriteToMemcache(retrieved_dict)
197 actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
198 self.assertEqual(12300, actual_123[123])
199 actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
200 self.assertEqual(12400, actual_124[124])
201
202 def testWriteToMemcache_String(self):
203 retrieved_dict = {123: 'foo', 124: 'bar'}
204 self.testable_2lc._WriteToMemcache(retrieved_dict)
205 actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
206 self.assertEqual('foo', actual_123[123])
207 actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
208 self.assertEqual('bar', actual_124[124])
209
210 def testWriteToMemcache_ProtobufInt(self):
211 self.testable_2lc.pb_class = int
212 retrieved_dict = {123: 12300, 124: 12400}
213 self.testable_2lc._WriteToMemcache(retrieved_dict)
214 actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
215 self.assertEqual(12300, actual_123[123])
216 actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
217 self.assertEqual(12400, actual_124[124])
218
219 def testWriteToMemcache_List(self):
220 retrieved_dict = {123: [1, 2, 3], 124: [1, 2, 4]}
221 self.testable_2lc._WriteToMemcache(retrieved_dict)
222 actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
223 self.assertEqual([1, 2, 3], actual_123[123])
224 actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
225 self.assertEqual([1, 2, 4], actual_124[124])
226
227 def testWriteToMemcache_Dict(self):
228 retrieved_dict = {123: {'ham': 2, 'spam': 3}, 124: {'eggs': 2, 'bean': 4}}
229 self.testable_2lc._WriteToMemcache(retrieved_dict)
230 actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
231 self.assertEqual({'ham': 2, 'spam': 3}, actual_123[123])
232 actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
233 self.assertEqual({'eggs': 2, 'bean': 4}, actual_124[124])
234
235 def testWriteToMemcache_HugeValue(self):
236 """If memcache refuses to store a huge value, we don't store any."""
237 self.testable_2lc._WriteToMemcache({124: 124999}) # Gets deleted.
238 huge_str = 'huge' * 260000
239 retrieved_dict = {123: huge_str, 124: 12400}
240 self.testable_2lc._WriteToMemcache(retrieved_dict)
241 actual_123 = memcache.get('testable:123')
242 self.assertEqual(None, actual_123)
243 actual_124 = memcache.get('testable:124')
244 self.assertEqual(None, actual_124)
245
246 def testGetAll_FetchGetsIt(self):
247 self.testable_2lc.CacheItem(123, 12300)
248 self.testable_2lc.CacheItem(124, 12400)
249 # Clear the RAM cache so that we find items in memcache.
250 self.testable_2lc.cache.LocalInvalidateAll()
251 self.testable_2lc.CacheItem(125, 12500)
252 hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 333, 444])
253 self.assertEqual({123: 12300, 124: 12400, 333: 333, 444: 444}, hits)
254 self.assertEqual([], misses)
255 # The RAM cache now has items found in memcache and DB.
256 self.assertItemsEqual(
257 [123, 124, 125, 333, 444], list(self.testable_2lc.cache.cache.keys()))
258
259 def testGetAll_FetchGetsItFromDB(self):
260 self.testable_2lc.CacheItem(123, 12300)
261 self.testable_2lc.CacheItem(124, 12400)
262 hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 333, 444])
263 self.assertEqual({123: 12300, 124: 12400, 333: 333, 444: 444}, hits)
264 self.assertEqual([], misses)
265
266 def testGetAll_FetchDoesNotFindIt(self):
267 self.testable_2lc.CacheItem(123, 12300)
268 self.testable_2lc.CacheItem(124, 12400)
269 hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 999])
270 self.assertEqual({123: 12300, 124: 12400}, hits)
271 self.assertEqual([999], misses)
272
273 def testInvalidateKeys(self):
274 self.testable_2lc.CacheItem(123, 12300)
275 self.testable_2lc.CacheItem(124, 12400)
276 self.testable_2lc.CacheItem(125, 12500)
277 self.testable_2lc.InvalidateKeys(self.cnxn, [124])
278 self.assertEqual(2, len(self.testable_2lc.cache.cache))
279 self.assertNotIn(124, self.testable_2lc.cache.cache)
280 self.assertEqual(
281 self.cache_manager.last_call,
282 ('StoreInvalidateRows', self.cnxn, 'issue', [124]))
283
284 def testGetAllAlreadyInRam(self):
285 self.testable_2lc.CacheItem(123, 12300)
286 self.testable_2lc.CacheItem(124, 12400)
287 hits, misses = self.testable_2lc.GetAllAlreadyInRam(
288 [123, 124, 333, 444, 999])
289 self.assertEqual({123: 12300, 124: 12400}, hits)
290 self.assertEqual([333, 444, 999], misses)
291
292 def testInvalidateAllRamEntries(self):
293 self.testable_2lc.CacheItem(123, 12300)
294 self.testable_2lc.CacheItem(124, 12400)
295 self.testable_2lc.InvalidateAllRamEntries(self.cnxn)
296 self.assertFalse(self.testable_2lc.HasItem(123))
297 self.assertFalse(self.testable_2lc.HasItem(124))
298
299
300class AbstractTwoLevelCacheTest_Redis(unittest.TestCase):
301
302 def setUp(self):
303 self.cnxn = 'fake connection'
304 self.cache_manager = fake.CacheManager()
305
306 self.server = fakeredis.FakeServer()
307 self.fake_redis_client = fakeredis.FakeRedis(server=self.server)
308 self.testable_2lc = TestableTwoLevelCache(
309 self.cache_manager,
310 'issue',
311 use_redis=True,
312 redis_client=self.fake_redis_client)
313
314 def tearDown(self):
315 self.fake_redis_client.flushall()
316
317 def testCacheItem(self):
318 self.testable_2lc.CacheItem(123, 12300)
319 self.assertEqual(12300, self.testable_2lc.cache.cache[123])
320
321 def testHasItem(self):
322 self.testable_2lc.CacheItem(123, 12300)
323 self.assertTrue(self.testable_2lc.HasItem(123))
324 self.assertFalse(self.testable_2lc.HasItem(444))
325 self.assertFalse(self.testable_2lc.HasItem(999))
326
327 def testWriteToRedis_Normal(self):
328 retrieved_dict = {123: 12300, 124: 12400}
329 self.testable_2lc._WriteToRedis(retrieved_dict)
330 actual_123, _ = self.testable_2lc._ReadFromRedis([123])
331 self.assertEqual(12300, actual_123[123])
332 actual_124, _ = self.testable_2lc._ReadFromRedis([124])
333 self.assertEqual(12400, actual_124[124])
334
335 def testWriteToRedis_str(self):
336 retrieved_dict = {111: 'foo', 222: 'bar'}
337 self.testable_2lc._WriteToRedis(retrieved_dict)
338 actual_111, _ = self.testable_2lc._ReadFromRedis([111])
339 self.assertEqual('foo', actual_111[111])
340 actual_222, _ = self.testable_2lc._ReadFromRedis([222])
341 self.assertEqual('bar', actual_222[222])
342
343 def testWriteToRedis_ProtobufInt(self):
344 self.testable_2lc.pb_class = int
345 retrieved_dict = {123: 12300, 124: 12400}
346 self.testable_2lc._WriteToRedis(retrieved_dict)
347 actual_123, _ = self.testable_2lc._ReadFromRedis([123])
348 self.assertEqual(12300, actual_123[123])
349 actual_124, _ = self.testable_2lc._ReadFromRedis([124])
350 self.assertEqual(12400, actual_124[124])
351
352 def testWriteToRedis_List(self):
353 retrieved_dict = {123: [1, 2, 3], 124: [1, 2, 4]}
354 self.testable_2lc._WriteToRedis(retrieved_dict)
355 actual_123, _ = self.testable_2lc._ReadFromRedis([123])
356 self.assertEqual([1, 2, 3], actual_123[123])
357 actual_124, _ = self.testable_2lc._ReadFromRedis([124])
358 self.assertEqual([1, 2, 4], actual_124[124])
359
360 def testWriteToRedis_Dict(self):
361 retrieved_dict = {123: {'ham': 2, 'spam': 3}, 124: {'eggs': 2, 'bean': 4}}
362 self.testable_2lc._WriteToRedis(retrieved_dict)
363 actual_123, _ = self.testable_2lc._ReadFromRedis([123])
364 self.assertEqual({'ham': 2, 'spam': 3}, actual_123[123])
365 actual_124, _ = self.testable_2lc._ReadFromRedis([124])
366 self.assertEqual({'eggs': 2, 'bean': 4}, actual_124[124])
367
368 def testGetAll_FetchGetsIt(self):
369 self.testable_2lc.CacheItem(123, 12300)
370 self.testable_2lc.CacheItem(124, 12400)
371 # Clear the RAM cache so that we find items in redis.
372 self.testable_2lc.cache.LocalInvalidateAll()
373 self.testable_2lc.CacheItem(125, 12500)
374 hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 333, 444])
375 self.assertEqual({123: 12300, 124: 12400, 333: 333, 444: 444}, hits)
376 self.assertEqual([], misses)
377 # The RAM cache now has items found in redis and DB.
378 self.assertItemsEqual(
379 [123, 124, 125, 333, 444], list(self.testable_2lc.cache.cache.keys()))
380
381 def testGetAll_FetchGetsItFromDB(self):
382 self.testable_2lc.CacheItem(123, 12300)
383 self.testable_2lc.CacheItem(124, 12400)
384 hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 333, 444])
385 self.assertEqual({123: 12300, 124: 12400, 333: 333, 444: 444}, hits)
386 self.assertEqual([], misses)
387
388 def testGetAll_FetchDoesNotFindIt(self):
389 self.testable_2lc.CacheItem(123, 12300)
390 self.testable_2lc.CacheItem(124, 12400)
391 hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 999])
392 self.assertEqual({123: 12300, 124: 12400}, hits)
393 self.assertEqual([999], misses)
394
395 def testInvalidateKeys(self):
396 self.testable_2lc.CacheItem(123, 12300)
397 self.testable_2lc.CacheItem(124, 12400)
398 self.testable_2lc.CacheItem(125, 12500)
399 self.testable_2lc.InvalidateKeys(self.cnxn, [124])
400 self.assertEqual(2, len(self.testable_2lc.cache.cache))
401 self.assertNotIn(124, self.testable_2lc.cache.cache)
402 self.assertEqual(self.cache_manager.last_call,
403 ('StoreInvalidateRows', self.cnxn, 'issue', [124]))
404
405 def testGetAllAlreadyInRam(self):
406 self.testable_2lc.CacheItem(123, 12300)
407 self.testable_2lc.CacheItem(124, 12400)
408 hits, misses = self.testable_2lc.GetAllAlreadyInRam(
409 [123, 124, 333, 444, 999])
410 self.assertEqual({123: 12300, 124: 12400}, hits)
411 self.assertEqual([333, 444, 999], misses)
412
413 def testInvalidateAllRamEntries(self):
414 self.testable_2lc.CacheItem(123, 12300)
415 self.testable_2lc.CacheItem(124, 12400)
416 self.testable_2lc.InvalidateAllRamEntries(self.cnxn)
417 self.assertFalse(self.testable_2lc.HasItem(123))
418 self.assertFalse(self.testable_2lc.HasItem(124))