seitime-frappe/frappe/tests/test_client_cache.py
Ankush Menat 4d1ef02e25 feat: generator support for client cache
Similar to redis, for ergonomics
2025-01-14 13:55:28 +05:30

113 lines
3.8 KiB
Python

import time
import frappe
from frappe.tests import IntegrationTestCase
from frappe.utils.redis_wrapper import ClientCache
TEST_KEY = "42"
class TestClientCache(IntegrationTestCase):
def setUp(self) -> None:
frappe.client_cache.delete_value(TEST_KEY)
return super().setUp()
def test_client_cache_is_used(self):
frappe.client_cache.set_value(TEST_KEY, 42)
frappe.client_cache.get_value(TEST_KEY)
with self.assertRedisCallCounts(0):
frappe.client_cache.get_value(TEST_KEY)
def test_client_cache_is_updated_instantly_noloop(self):
val = frappe.generate_hash()
frappe.client_cache.set_value(TEST_KEY, val)
with self.assertRedisCallCounts(0): # Locally set value should not be invalidated.
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val)
def test_invalidation_from_another_client_works(self):
frappe.client_cache.reset_statistics()
val = frappe.generate_hash()
frappe.client_cache.set_value(TEST_KEY, val)
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val)
# frappe.cache is our "another client"
val = frappe.generate_hash()
frappe.cache.set_value(TEST_KEY, val)
# This is almost instant, but obviously not as fast as running the next instruction in
# current thread. So we wait.
time.sleep(0.1)
with self.assertRedisCallCounts(1, exact=True):
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val)
self.assertEqual(frappe.client_cache.statistics.hits, 1)
self.assertEqual(frappe.client_cache.statistics.misses, 1)
self.assertEqual(frappe.client_cache.statistics.hit_ratio, 0.5)
def test_delete_invalidates(self):
val = frappe.generate_hash()
frappe.client_cache.set_value(TEST_KEY, val)
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val)
val = frappe.generate_hash()
frappe.cache.delete_value(TEST_KEY)
# This is almost instant, but obviously not as fast as running the next instruction in
# current thread. So we wait.
time.sleep(0.1)
with self.assertRedisCallCounts(1, exact=True):
self.assertIsNone(frappe.client_cache.get_value(TEST_KEY))
# Flushall should have results
frappe.client_cache.set_value(TEST_KEY, val)
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val)
frappe.cache.flushall()
time.sleep(0.1)
with self.assertRedisCallCounts(1, exact=True):
self.assertIsNone(frappe.client_cache.get_value(TEST_KEY))
# frappe.clear_cache should have same results
frappe.client_cache.set_value(TEST_KEY, val)
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), val)
frappe.clear_cache()
time.sleep(0.1)
with self.assertRedisCallCounts(1, exact=True):
self.assertIsNone(frappe.client_cache.get_value(TEST_KEY))
def test_client_local_cache_ttl(self):
c = ClientCache(ttl=1)
c.set_value(TEST_KEY, 42)
with self.assertRedisCallCounts(0):
c.get_value(TEST_KEY)
time.sleep(1)
with self.assertRedisCallCounts(1, exact=True):
c.get_value(TEST_KEY)
def test_client_cache_maxsize(self):
c = ClientCache(maxsize=2)
c.set_value(TEST_KEY, 42)
c.set_value(frappe.generate_hash(), 42)
c.set_value(frappe.generate_hash(), 42)
self.assertEqual(len(c.cache), 2)
def test_shared_keyspace(self):
val = frappe.generate_hash()
frappe.client_cache.set_value(TEST_KEY, val)
self.assertEqual(frappe.client_cache.get_value(TEST_KEY), frappe.cache.get_value(TEST_KEY))
def test_shared_keys(self):
val = frappe.generate_hash()
frappe.client_cache.set_value(TEST_KEY, val, shared=True)
with self.assertRedisCallCounts(0):
self.assertEqual(frappe.client_cache.get_value(TEST_KEY, shared=True), val)
def test_generator(self):
val = frappe.generate_hash()
with self.assertRedisCallCounts(2, exact=True):
self.assertEqual(frappe.client_cache.get_value(TEST_KEY, generator=lambda: val), val)
with self.assertRedisCallCounts(0):
self.assertEqual(frappe.client_cache.get_value(TEST_KEY, generator=lambda: val), val)