| 1 | import assert from "node:assert/strict"; |
| 2 | import { test } from "node:test"; |
| 3 | |
| 4 | import { CONTENT_ORIGIN, type SharedCache, WeightedLru, contentCache, contentKey, weightOfLines } from "./content-cache.ts"; |
| 5 | |
| 6 | test("a content key is a hash of every part, in order, and never of a join that could collide", async () => { |
| 7 | const key = await contentKey(["typescript", "const a = 1;"]); |
| 8 | assert.match(key, /^[0-9a-f]{64}$/); |
| 9 | assert.equal(key, await contentKey(["typescript", "const a = 1;"])); |
| 10 | assert.notEqual(key, await contentKey(["tsx", "const a = 1;"])); |
| 11 | assert.notEqual(key, await contentKey(["typescript", "const a = 2;"])); |
| 12 | // The same characters split differently are different content. |
| 13 | assert.notEqual(await contentKey(["ab", "c"]), await contentKey(["a", "bc"])); |
| 14 | assert.notEqual(await contentKey(["a:b"]), await contentKey(["a", "b"])); |
| 15 | }); |
| 16 | |
| 17 | test("the isolate's memory keeps the most recently used within its weight", () => { |
| 18 | const lru = new WeightedLru<string>(100, (value) => value.length); |
| 19 | lru.set("a", "x".repeat(20)); |
| 20 | lru.set("b", "x".repeat(20)); |
| 21 | lru.set("c", "x".repeat(20)); |
| 22 | assert.equal(lru.weight, 60); |
| 23 | // Reading `a` makes it the newest; `b` is now the oldest. |
| 24 | assert.ok(lru.get("a")); |
| 25 | lru.set("d", "x".repeat(20)); |
| 26 | lru.set("e", "x".repeat(20)); |
| 27 | lru.set("f", "x".repeat(20)); |
| 28 | assert.equal(lru.get("b"), undefined); |
| 29 | assert.ok(lru.get("a")); |
| 30 | assert.ok(lru.weight <= 100); |
| 31 | // Replacing an entry does not count it twice. |
| 32 | lru.set("a", "y"); |
| 33 | assert.equal(lru.get("a"), "y"); |
| 34 | assert.ok(lru.weight <= 100); |
| 35 | // One entry heavier than a quarter of the whole is never kept. |
| 36 | lru.set("big", "x".repeat(26)); |
| 37 | assert.equal(lru.get("big"), undefined); |
| 38 | }); |
| 39 | |
| 40 | test("lines weigh their characters", () => { |
| 41 | assert.equal(weightOfLines(["ab", null, ""]), 2 + 8 + 0 + 8 + 0 + 8); |
| 42 | }); |
| 43 | |
| 44 | /** A data centre's cache in memory, as caches.default answers. */ |
| 45 | function fakeShared() { |
| 46 | const kept = new Map<string, string>(); |
| 47 | const shared: SharedCache = { |
| 48 | async match(url) { |
| 49 | const body = kept.get(url); |
| 50 | return body === undefined ? undefined : new Response(body); |
| 51 | }, |
| 52 | async put(url, response) { |
| 53 | kept.set(url, await response.text()); |
| 54 | }, |
| 55 | }; |
| 56 | return { kept, shared }; |
| 57 | } |
| 58 | |
| 59 | test("an answer is computed once, then read from the isolate, then from the data centre", async () => { |
| 60 | const { kept, shared } = fakeShared(); |
| 61 | const deferred: Promise<unknown>[] = []; |
| 62 | const options = { |
| 63 | name: "test-lines", |
| 64 | version: "v1", |
| 65 | maxWeight: 1_000, |
| 66 | weigh: weightOfLines, |
| 67 | shared: () => shared, |
| 68 | defer: (work: Promise<unknown>) => void deferred.push(work), |
| 69 | }; |
| 70 | const cached = contentCache<string[]>(options); |
| 71 | let runs = 0; |
| 72 | const compute = async () => { |
| 73 | runs += 1; |
| 74 | return ["<b>a</b>", "b"]; |
| 75 | }; |
| 76 | assert.deepEqual(await cached(["rust", "a\nb"], compute), ["<b>a</b>", "b"]); |
| 77 | await Promise.all(deferred); |
| 78 | assert.equal(runs, 1); |
| 79 | assert.equal(kept.size, 1); |
| 80 | const [url] = kept.keys(); |
| 81 | assert.ok(url!.startsWith(`${CONTENT_ORIGIN}test-lines/v1/`)); |
| 82 | // The isolate has it. |
| 83 | assert.deepEqual(await cached(["rust", "a\nb"], compute), ["<b>a</b>", "b"]); |
| 84 | assert.equal(runs, 1); |
| 85 | // Another isolate (a fresh cache) finds the data centre's copy. |
| 86 | const other = contentCache<string[]>(options); |
| 87 | assert.deepEqual(await other(["rust", "a\nb"], compute), ["<b>a</b>", "b"]); |
| 88 | assert.equal(runs, 1); |
| 89 | // Other content, or the same content for another language, is another entry. |
| 90 | await cached(["rust", "a\nc"], compute); |
| 91 | await cached(["go", "a\nb"], compute); |
| 92 | assert.equal(runs, 3); |
| 93 | }); |
| 94 | |
| 95 | test("a new version never reads what an old one kept", async () => { |
| 96 | const { shared } = fakeShared(); |
| 97 | const make = (version: string) => |
| 98 | contentCache<string[]>({ name: "test", version, maxWeight: 1_000, weigh: weightOfLines, shared: () => shared, defer: () => {} }); |
| 99 | const deferred: Promise<unknown>[] = []; |
| 100 | const first = contentCache<string[]>({ |
| 101 | name: "test", |
| 102 | version: "v1", |
| 103 | maxWeight: 1_000, |
| 104 | weigh: weightOfLines, |
| 105 | shared: () => shared, |
| 106 | defer: (work) => void deferred.push(work), |
| 107 | }); |
| 108 | await first(["x"], async () => ["old"]); |
| 109 | await Promise.all(deferred); |
| 110 | assert.deepEqual(await make("v1")(["x"], async () => ["new"]), ["old"]); |
| 111 | assert.deepEqual(await make("v2")(["x"], async () => ["new"]), ["new"]); |
| 112 | }); |
| 113 | |
| 114 | test("nothing to keep is not kept, and a broken shared entry counts as missing", async () => { |
| 115 | const { kept, shared } = fakeShared(); |
| 116 | const cached = contentCache<string[]>({ name: "t", version: "v1", maxWeight: 1_000, weigh: weightOfLines, shared: () => shared, defer: () => {} }); |
| 117 | let runs = 0; |
| 118 | assert.equal(await cached(["none"], async () => (runs++, null)), null); |
| 119 | assert.equal(await cached(["none"], async () => (runs++, null)), null); |
| 120 | assert.equal(runs, 2); |
| 121 | assert.equal(kept.size, 0); |
| 122 | |
| 123 | const url = `${CONTENT_ORIGIN}t/v1/${await contentKey(["t", "v1", "broken"])}`; |
| 124 | kept.set(url, "{not json"); |
| 125 | assert.deepEqual(await cached(["broken"], async () => ["fresh"]), ["fresh"]); |
| 126 | }); |
| 127 | |
| 128 | test("without a data centre's cache the isolate still keeps answers", async () => { |
| 129 | const cached = contentCache<string[]>({ name: "t", version: "v1", maxWeight: 1_000, weigh: weightOfLines, shared: () => null, defer: () => {} }); |
| 130 | let runs = 0; |
| 131 | await cached(["a"], async () => (runs++, ["x"])); |
| 132 | await cached(["a"], async () => (runs++, ["x"])); |
| 133 | assert.equal(runs, 1); |
| 134 | }); |