Skip to content

Commit 4786bd1

Browse files
committed
test: add regression tests for VipCache refCount/TTL bugs
Cover acquire/release eviction protection, deferred deletion on delete()/invalidateByPrefix() with refCount>0, set() clearing expiresAt from prior setWithTTL(), and get() not orphaning acquired expired entries.
1 parent 539cd89 commit 4786bd1

1 file changed

Lines changed: 80 additions & 0 deletions

File tree

src/vip-cache.test.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,84 @@ describe("VipCache", () => {
8989
expect(entries[0][1].accessCount).toBe(3);
9090
expect(entries[0][1].lastAccess).toBeGreaterThan(0);
9191
});
92+
93+
it("acquire/release prevents eviction", () => {
94+
const cache = new VipCache<string, number>(2, 10);
95+
cache.set("a", 1);
96+
cache.set("b", 2);
97+
98+
const val = cache.acquire("a");
99+
expect(val).toBe(1);
100+
101+
// Insert 3rd — "a" has refCount=1, should not be evicted
102+
cache.set("c", 3);
103+
expect(cache.has("a")).toBe(true);
104+
expect(cache.has("b")).toBe(false); // "b" evicted instead
105+
expect(cache.has("c")).toBe(true);
106+
107+
cache.release("a");
108+
});
109+
110+
it("delete with refCount>0 defers deletion", () => {
111+
const cache = new VipCache<string, number>(10);
112+
cache.set("a", 1);
113+
cache.acquire("a");
114+
115+
cache.delete("a");
116+
// Entry is pending eviction but still in map until release
117+
expect(cache.size).toBe(1);
118+
119+
cache.release("a");
120+
// Now actually removed
121+
expect(cache.size).toBe(0);
122+
expect(cache.has("a")).toBe(false);
123+
});
124+
125+
it("invalidateByPrefix defers deletion for acquired entries", () => {
126+
const cache = new VipCache<string, number>(10);
127+
cache.set("tbl/a", 1);
128+
cache.set("tbl/b", 2);
129+
cache.set("other", 3);
130+
131+
cache.acquire("tbl/a");
132+
const count = cache.invalidateByPrefix("tbl/");
133+
expect(count).toBe(2);
134+
135+
// "tbl/b" deleted immediately, "tbl/a" deferred
136+
expect(cache.has("tbl/b")).toBe(false);
137+
expect(cache.size).toBe(2); // "tbl/a" (pending) + "other"
138+
139+
cache.release("tbl/a");
140+
expect(cache.size).toBe(1); // only "other"
141+
});
142+
143+
it("set() clears expiresAt from previous setWithTTL()", () => {
144+
const cache = new VipCache<string, number>(10);
145+
cache.setWithTTL("a", 1, 1); // 1ms TTL
146+
147+
// Overwrite with set() — should clear TTL
148+
cache.set("a", 2);
149+
150+
// Wait past the original TTL
151+
const start = Date.now();
152+
while (Date.now() - start < 5) { /* spin */ }
153+
154+
// Entry should still be accessible (TTL cleared)
155+
expect(cache.get("a")).toBe(2);
156+
});
157+
158+
it("get() does not delete expired entry with refCount>0", () => {
159+
const cache = new VipCache<string, number>(10);
160+
cache.setWithTTL("a", 1, 1); // 1ms TTL
161+
cache.acquire("a");
162+
163+
const start = Date.now();
164+
while (Date.now() - start < 5) { /* spin */ }
165+
166+
// get() returns undefined (expired) but does not delete (refCount > 0)
167+
expect(cache.get("a")).toBeUndefined();
168+
expect(cache.size).toBe(1); // still in map
169+
170+
cache.release("a");
171+
});
92172
});

0 commit comments

Comments
 (0)