import threading import time import unittest from lru_ttl_cache import LRUTTLCache class TestLRUTTLCache(unittest.TestCase): def test_basic_put_get(self): c = LRUTTLCache(maxsize=2, ttl=10) c.put("a", 1) self.assertEqual(c.get("a"), 1) self.assertEqual(c.get("missing", "d"), "d") def test_lru_eviction(self): c = LRUTTLCache(maxsize=2, ttl=10) c.put("a", 1); c.put("b", 2) c.get("a") # refresh a c.put("c", 3) # evicts b self.assertEqual(c.get("b", None), None) self.assertEqual(c.get("a"), 1) self.assertEqual(c.get("c"), 3) def test_ttl_expiry(self): clock = {"t": 0.0} c = LRUTTLCache(maxsize=4, ttl=5, clock=lambda: clock["t"]) c.put("a", 1) self.assertTrue(c.contains("a")) clock["t"] = 6.0 self.assertFalse(c.contains("a")) self.assertEqual(c.get("a", "x"), "x") def test_put_overwrite_resets_ttl(self): clock = {"t": 0.0} c = LRUTTLCache(maxsize=4, ttl=5, clock=lambda: clock["t"]) c.put("a", 1) clock["t"] = 4.0 c.put("a", 2) # reset ttl: now expires at 9 clock["t"] = 8.0 self.assertEqual(c.get("a"), 2) clock["t"] = 10.0 self.assertEqual(c.get("a", "x"), "x") def test_stats(self): c = LRUTTLCache(maxsize=2, ttl=10) c.put("a", 1); c.put("b", 2) c.get("a"); c.get("zzz") s = c.stats self.assertEqual(s["hits"], 1) self.assertEqual(s["misses"], 1) self.assertEqual(s["size"], 2) def test_thread_safety(self): c = LRUTTLCache(maxsize=100, ttl=100) errs = [] def w(i): try: for j in range(50): c.put((i, j), i * j) c.get((i, j)) except Exception as e: # noqa errs.append(e) ts = [threading.Thread(target=w, args=(i,)) for i in range(8)] for t in ts: t.start() for t in ts: t.join() self.assertFalse(errs) def test_validation(self): with self.assertRaises(ValueError): LRUTTLCache(maxsize=0, ttl=1) with self.assertRaises(ValueError): LRUTTLCache(maxsize=1, ttl=0) if __name__ == "__main__": unittest.main()