blob: 23f793c50260043874a2797926b1aa5d593ece07 [file] [log] [blame]
# Copyright 2016 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Tests for the cache classes."""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import
import six
import unittest
from google.appengine.api import memcache
from google.appengine.ext import testbed
from services import caches
from testing import fake
class RamCacheTest(unittest.TestCase):
def setUp(self):
self.cnxn = 'fake connection'
self.cache_manager = fake.CacheManager()
self.ram_cache = caches.RamCache(self.cache_manager, 'issue', max_size=3)
def testInit(self):
self.assertEqual('issue', self.ram_cache.kind)
self.assertEqual(3, self.ram_cache.max_size)
self.assertEqual(
[self.ram_cache],
self.cache_manager.cache_registry['issue'])
def testCacheItem(self):
self.ram_cache.CacheItem(123, 'foo')
self.assertEqual('foo', self.ram_cache.cache[123])
def testCacheItem_DropsOldItems(self):
self.ram_cache.CacheItem(123, 'foo')
self.ram_cache.CacheItem(234, 'foo')
self.ram_cache.CacheItem(345, 'foo')
self.ram_cache.CacheItem(456, 'foo')
# The cache does not get bigger than its limit.
self.assertEqual(3, len(self.ram_cache.cache))
# An old value is dropped, not the newly added one.
self.assertIn(456, self.ram_cache.cache)
def testCacheAll(self):
self.ram_cache.CacheAll({123: 'foo'})
self.assertEqual('foo', self.ram_cache.cache[123])
def testCacheAll_DropsOldItems(self):
self.ram_cache.CacheAll({1: 'a', 2: 'b', 3: 'c'})
self.ram_cache.CacheAll({4: 'x', 5: 'y'})
# The cache does not get bigger than its limit.
self.assertEqual(3, len(self.ram_cache.cache))
# An old value is dropped, not the newly added one.
self.assertIn(4, self.ram_cache.cache)
self.assertIn(5, self.ram_cache.cache)
self.assertEqual('y', self.ram_cache.cache[5])
def testHasItem(self):
self.ram_cache.CacheItem(123, 'foo')
self.assertTrue(self.ram_cache.HasItem(123))
self.assertFalse(self.ram_cache.HasItem(999))
def testGetItem(self):
self.ram_cache.CacheItem(123, 'foo')
self.assertEqual('foo', self.ram_cache.GetItem(123))
self.assertEqual(None, self.ram_cache.GetItem(456))
def testGetAll(self):
self.ram_cache.CacheItem(123, 'foo')
self.ram_cache.CacheItem(124, 'bar')
hits, misses = self.ram_cache.GetAll([123, 124, 999])
self.assertEqual({123: 'foo', 124: 'bar'}, hits)
self.assertEqual([999], misses)
def testLocalInvalidate(self):
self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
self.ram_cache.LocalInvalidate(124)
self.assertEqual(2, len(self.ram_cache.cache))
self.assertNotIn(124, self.ram_cache.cache)
self.ram_cache.LocalInvalidate(999)
self.assertEqual(2, len(self.ram_cache.cache))
def testInvalidate(self):
self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
self.ram_cache.Invalidate(self.cnxn, 124)
self.assertEqual(2, len(self.ram_cache.cache))
self.assertNotIn(124, self.ram_cache.cache)
self.assertEqual(self.cache_manager.last_call,
('StoreInvalidateRows', self.cnxn, 'issue', [124]))
def testInvalidateKeys(self):
self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
self.ram_cache.InvalidateKeys(self.cnxn, [124])
self.assertEqual(2, len(self.ram_cache.cache))
self.assertNotIn(124, self.ram_cache.cache)
self.assertEqual(self.cache_manager.last_call,
('StoreInvalidateRows', self.cnxn, 'issue', [124]))
def testLocalInvalidateAll(self):
self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
self.ram_cache.LocalInvalidateAll()
self.assertEqual(0, len(self.ram_cache.cache))
def testInvalidateAll(self):
self.ram_cache.CacheAll({123: 'a', 124: 'b', 125: 'c'})
self.ram_cache.InvalidateAll(self.cnxn)
self.assertEqual(0, len(self.ram_cache.cache))
self.assertEqual(self.cache_manager.last_call,
('StoreInvalidateAll', self.cnxn, 'issue'))
class ShardedRamCacheTest(unittest.TestCase):
def setUp(self):
self.cnxn = 'fake connection'
self.cache_manager = fake.CacheManager()
self.sharded_ram_cache = caches.ShardedRamCache(
self.cache_manager, 'issue', max_size=3, num_shards=3)
def testLocalInvalidate(self):
self.sharded_ram_cache.CacheAll({
(123, 0): 'a',
(123, 1): 'aa',
(123, 2): 'aaa',
(124, 0): 'b',
(124, 1): 'bb',
(124, 2): 'bbb',
})
self.sharded_ram_cache.LocalInvalidate(124)
self.assertEqual(3, len(self.sharded_ram_cache.cache))
self.assertNotIn((124, 0), self.sharded_ram_cache.cache)
self.assertNotIn((124, 1), self.sharded_ram_cache.cache)
self.assertNotIn((124, 2), self.sharded_ram_cache.cache)
self.sharded_ram_cache.LocalInvalidate(999)
self.assertEqual(3, len(self.sharded_ram_cache.cache))
class _TestableTwoLevelCache(caches.AbstractTwoLevelCache):
def __init__(self, cache_manager, kind, max_size=None):
super(_TestableTwoLevelCache, self).__init__(
cache_manager, kind, 'testable:', None, max_size=max_size)
# pylint: disable=unused-argument
def FetchItems(self, cnxn, keys, **kwargs):
"""On RAM and memcache miss, hit the database."""
return {key: key for key in keys if key < 900}
class AbstractTwoLevelCacheTest_Memcache(unittest.TestCase):
def setUp(self):
self.testbed = testbed.Testbed()
self.testbed.activate()
self.testbed.init_memcache_stub()
self.cnxn = 'fake connection'
self.cache_manager = fake.CacheManager()
self.testable_2lc = _TestableTwoLevelCache(self.cache_manager, 'issue')
def tearDown(self):
self.testbed.deactivate()
def testCacheItem(self):
self.testable_2lc.CacheItem(123, 12300)
self.assertEqual(12300, self.testable_2lc.cache.cache[123])
def testHasItem(self):
self.testable_2lc.CacheItem(123, 12300)
self.assertTrue(self.testable_2lc.HasItem(123))
self.assertFalse(self.testable_2lc.HasItem(444))
self.assertFalse(self.testable_2lc.HasItem(999))
def testWriteToMemcache_Normal(self):
retrieved_dict = {123: 12300, 124: 12400}
self.testable_2lc._WriteToMemcache(retrieved_dict)
actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
self.assertEqual(12300, actual_123[123])
actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
self.assertEqual(12400, actual_124[124])
def testWriteToMemcache_String(self):
retrieved_dict = {123: 'foo', 124: 'bar'}
self.testable_2lc._WriteToMemcache(retrieved_dict)
actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
self.assertEqual('foo', actual_123[123])
actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
self.assertEqual('bar', actual_124[124])
def testWriteToMemcache_ProtobufInt(self):
self.testable_2lc.pb_class = int
retrieved_dict = {123: 12300, 124: 12400}
self.testable_2lc._WriteToMemcache(retrieved_dict)
actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
self.assertEqual(12300, actual_123[123])
actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
self.assertEqual(12400, actual_124[124])
def testWriteToMemcache_List(self):
retrieved_dict = {123: [1, 2, 3], 124: [1, 2, 4]}
self.testable_2lc._WriteToMemcache(retrieved_dict)
actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
self.assertEqual([1, 2, 3], actual_123[123])
actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
self.assertEqual([1, 2, 4], actual_124[124])
def testWriteToMemcache_Dict(self):
retrieved_dict = {123: {'ham': 2, 'spam': 3}, 124: {'eggs': 2, 'bean': 4}}
self.testable_2lc._WriteToMemcache(retrieved_dict)
actual_123, _ = self.testable_2lc._ReadFromMemcache([123])
self.assertEqual({'ham': 2, 'spam': 3}, actual_123[123])
actual_124, _ = self.testable_2lc._ReadFromMemcache([124])
self.assertEqual({'eggs': 2, 'bean': 4}, actual_124[124])
def testWriteToMemcache_HugeValue(self):
"""If memcache refuses to store a huge value, we don't store any."""
self.testable_2lc._WriteToMemcache({124: 124999}) # Gets deleted.
huge_str = 'huge' * 260000
retrieved_dict = {123: huge_str, 124: 12400}
self.testable_2lc._WriteToMemcache(retrieved_dict)
actual_123 = memcache.get('testable:123')
self.assertEqual(None, actual_123)
actual_124 = memcache.get('testable:124')
self.assertEqual(None, actual_124)
def testGetAll_FetchGetsIt(self):
self.testable_2lc.CacheItem(123, 12300)
self.testable_2lc.CacheItem(124, 12400)
# Clear the RAM cache so that we find items in memcache.
self.testable_2lc.cache.LocalInvalidateAll()
self.testable_2lc.CacheItem(125, 12500)
hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 333, 444])
self.assertEqual({123: 12300, 124: 12400, 333: 333, 444: 444}, hits)
self.assertEqual([], misses)
# The RAM cache now has items found in memcache and DB.
six.assertCountEqual(
self, [123, 124, 125, 333, 444],
list(self.testable_2lc.cache.cache.keys()))
def testGetAll_FetchGetsItFromDB(self):
self.testable_2lc.CacheItem(123, 12300)
self.testable_2lc.CacheItem(124, 12400)
hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 333, 444])
self.assertEqual({123: 12300, 124: 12400, 333: 333, 444: 444}, hits)
self.assertEqual([], misses)
def testGetAll_FetchDoesNotFindIt(self):
self.testable_2lc.CacheItem(123, 12300)
self.testable_2lc.CacheItem(124, 12400)
hits, misses = self.testable_2lc.GetAll(self.cnxn, [123, 124, 999])
self.assertEqual({123: 12300, 124: 12400}, hits)
self.assertEqual([999], misses)
def testInvalidateKeys(self):
self.testable_2lc.CacheItem(123, 12300)
self.testable_2lc.CacheItem(124, 12400)
self.testable_2lc.CacheItem(125, 12500)
self.testable_2lc.InvalidateKeys(self.cnxn, [124])
self.assertEqual(2, len(self.testable_2lc.cache.cache))
self.assertNotIn(124, self.testable_2lc.cache.cache)
self.assertEqual(
self.cache_manager.last_call,
('StoreInvalidateRows', self.cnxn, 'issue', [124]))
def testGetAllAlreadyInRam(self):
self.testable_2lc.CacheItem(123, 12300)
self.testable_2lc.CacheItem(124, 12400)
hits, misses = self.testable_2lc.GetAllAlreadyInRam(
[123, 124, 333, 444, 999])
self.assertEqual({123: 12300, 124: 12400}, hits)
self.assertEqual([333, 444, 999], misses)
def testInvalidateAllRamEntries(self):
self.testable_2lc.CacheItem(123, 12300)
self.testable_2lc.CacheItem(124, 12400)
self.testable_2lc.InvalidateAllRamEntries(self.cnxn)
self.assertFalse(self.testable_2lc.HasItem(123))
self.assertFalse(self.testable_2lc.HasItem(124))