std/cache/ttl_cache_test.ts
lionel-rowe 2a0f5118c2
feat(cache/unstable): TtlCache (#5662)
* feat(cache/unstable): add TTL (time-to-live) cache

* Implement code review changes

* Simplify timeout logic

* Update cache/ttl_cache.ts

Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>

* Properly handle cleanup of timeouts

* chore: add experimental note

* Add test for [Symbol.toStringTag]

* tweak

---------

Co-authored-by: Asher Gomez <ashersaupingomez@gmail.com>
Co-authored-by: Yoshiya Hinosawa <stibium121@gmail.com>
2024-08-21 10:45:16 +00:00

106 lines
3.0 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
import { TtlCache } from "./ttl_cache.ts";
import { assertEquals } from "@std/assert";
import { FakeTime } from "@std/testing/time";
const UNSET = Symbol("UNSET");
// check `has()`, `get()`, `forEach()`
function assertEntries<K, V>(
cache: TtlCache<K, V>,
entries: [key: K, value: V | typeof UNSET][],
) {
for (const [key, value] of entries) {
assertEquals(cache.has(key), value !== UNSET);
assertEquals(cache.get(key), value === UNSET ? undefined : value);
}
cache.forEach((v, k) => assertEquals(v, entries.find(([x]) => x === k)![1]));
assertContentfulEntries(cache, entries.filter(([, v]) => v !== UNSET));
}
// check `size`, `entries()`, `keys()`, `values()`, `[Symbol.iterator]()`
function assertContentfulEntries<K, V>(
cache: TtlCache<K, V>,
entries: [key: K, value: V][],
) {
const keys = entries.map(([key]) => key);
const values = entries.map(([, value]) => value);
assertEquals(cache.size, entries.length);
assertEquals([...cache.entries()], entries);
assertEquals([...cache.keys()], keys);
assertEquals([...cache.values()], values);
assertEquals([...cache], entries);
}
Deno.test("TtlCache deletes entries", async (t) => {
await t.step("after the default TTL, passed in constructor", () => {
using time = new FakeTime(0);
const cache = new TtlCache<number, string>(10);
cache.set(1, "one");
cache.set(2, "two");
time.now = 1;
assertEntries(cache, [[1, "one"], [2, "two"]]);
time.now = 5;
assertEntries(cache, [[1, "one"], [2, "two"]]);
// setting again resets TTL countdown for key 1
cache.set(1, "one");
time.now = 10;
assertEntries(cache, [[1, "one"], [2, UNSET]]);
time.now = 15;
assertEntries(cache, [[1, UNSET], [2, UNSET]]);
});
await t.step("after a custom TTL, passed in set()", () => {
using time = new FakeTime(0);
const cache = new TtlCache<number, string>(10);
cache.set(1, "one");
cache.set(2, "two", 3);
time.now = 1;
assertEntries(cache, [[1, "one"], [2, "two"]]);
time.now = 3;
assertEntries(cache, [[1, "one"], [2, UNSET]]);
time.now = 10;
assertEntries(cache, [[1, UNSET], [2, UNSET]]);
});
await t.step("after manually calling delete()", () => {
const cache = new TtlCache<number, string>(10);
cache.set(1, "one");
assertEntries(cache, [[1, "one"]]);
assertEquals(cache.delete(1), true);
assertEntries(cache, [[1, UNSET]]);
assertEquals(cache.delete(1), false);
assertEntries(cache, [[1, UNSET]]);
});
await t.step("after manually calling clear()", () => {
const cache = new TtlCache<number, string>(10);
cache.set(1, "one");
assertEntries(cache, [[1, "one"]]);
cache.clear();
assertEntries(cache, [[1, UNSET]]);
});
// this test will fail with `error: Leaks detected` if the timeouts are not cleared
await t.step("[Symbol.dispose]() clears all remaining timeouts", () => {
using cache = new TtlCache<number, string>(10);
cache.set(1, "one");
});
});