Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1 | # 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.""" |
| 7 | from __future__ import print_function |
| 8 | from __future__ import division |
| 9 | from __future__ import absolute_import |
| 10 | |
| 11 | import fakeredis |
| 12 | import unittest |
| 13 | |
| 14 | from google.appengine.api import memcache |
| 15 | from google.appengine.ext import testbed |
| 16 | |
| 17 | import settings |
| 18 | from services import caches |
| 19 | from testing import fake |
| 20 | |
| 21 | |
| 22 | class 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 | |
| 119 | class 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 | |
| 146 | class 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 | |
| 170 | class 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 | |
| 300 | class 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)) |