1
0

lru-cache.test.ts 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. /**
  2. * LRUCache unit tests
  3. *
  4. * Covers the eviction guarantees that the resolver relies on:
  5. * - capacity is enforced (never exceeds max)
  6. * - LRU ordering: hot keys survive eviction passes
  7. * - has()/get()/set()/clear() behave like the original Map shape
  8. * - null values are storable (the fileCache uses null for "failed read")
  9. */
  10. import { describe, it, expect } from 'vitest';
  11. import { LRUCache } from '../../src/resolution/lru-cache';
  12. describe('LRUCache', () => {
  13. it('enforces capacity by evicting the oldest entry on overflow', () => {
  14. const cache = new LRUCache<string, number>(3);
  15. cache.set('a', 1);
  16. cache.set('b', 2);
  17. cache.set('c', 3);
  18. cache.set('d', 4); // evicts 'a'
  19. expect(cache.size).toBe(3);
  20. expect(cache.has('a')).toBe(false);
  21. expect(cache.get('a')).toBeUndefined();
  22. expect(cache.get('b')).toBe(2);
  23. expect(cache.get('c')).toBe(3);
  24. expect(cache.get('d')).toBe(4);
  25. });
  26. it('promotes touched keys to most-recent so they survive eviction', () => {
  27. const cache = new LRUCache<string, number>(3);
  28. cache.set('a', 1);
  29. cache.set('b', 2);
  30. cache.set('c', 3);
  31. // Touch 'a' — it should now be most-recent.
  32. expect(cache.get('a')).toBe(1);
  33. cache.set('d', 4); // evicts the LRU, which is now 'b' (not 'a')
  34. expect(cache.has('a')).toBe(true);
  35. expect(cache.has('b')).toBe(false);
  36. expect(cache.has('c')).toBe(true);
  37. expect(cache.has('d')).toBe(true);
  38. });
  39. it('overwriting an existing key refreshes its recency but does not grow size', () => {
  40. const cache = new LRUCache<string, number>(2);
  41. cache.set('a', 1);
  42. cache.set('b', 2);
  43. cache.set('a', 99); // 'a' is now most-recent
  44. expect(cache.size).toBe(2);
  45. expect(cache.get('a')).toBe(99);
  46. cache.set('c', 3); // should evict 'b', not 'a'
  47. expect(cache.has('a')).toBe(true);
  48. expect(cache.has('b')).toBe(false);
  49. expect(cache.has('c')).toBe(true);
  50. });
  51. it('stores null values (used by the file content cache)', () => {
  52. const cache = new LRUCache<string, string | null>(2);
  53. cache.set('missing.ts', null);
  54. expect(cache.has('missing.ts')).toBe(true);
  55. expect(cache.get('missing.ts')).toBeNull();
  56. });
  57. it('clear() resets the cache', () => {
  58. const cache = new LRUCache<string, number>(3);
  59. cache.set('a', 1);
  60. cache.set('b', 2);
  61. cache.clear();
  62. expect(cache.size).toBe(0);
  63. expect(cache.has('a')).toBe(false);
  64. });
  65. it('rejects non-positive capacity', () => {
  66. expect(() => new LRUCache(0)).toThrow();
  67. expect(() => new LRUCache(-1)).toThrow();
  68. expect(() => new LRUCache(NaN)).toThrow();
  69. });
  70. it('stays bounded under heavy churn (regression for OOM scenario)', () => {
  71. const cache = new LRUCache<string, number>(100);
  72. for (let i = 0; i < 10_000; i++) {
  73. cache.set(`key${i}`, i);
  74. }
  75. expect(cache.size).toBe(100);
  76. // The last 100 keys should still be present, the rest evicted.
  77. expect(cache.has('key9999')).toBe(true);
  78. expect(cache.has('key9900')).toBe(true);
  79. expect(cache.has('key0')).toBe(false);
  80. });
  81. });