mirror of
https://github.com/denoland/std.git
synced 2024-11-21 20:50:22 +00:00
2a0f5118c2
* 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>
106 lines
3.0 KiB
TypeScript
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");
|
|
});
|
|
});
|