Public: anyone can read this; the crew can change it.
A test pins `Stamped`, the memo every cached page reads through: stamp change, max age and least-recently-used eviction #6705
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6695-a-test-pins-stamped-the-memo-every-cache → main 1 commit fork 1 thread · 1 unresolved
1 file
changed +82
−0
test/stamped.test.ts added +82
| @@ -0,0 +1,82 @@ | ||
| 1 | import { describe, expect, test } from "bun:test"; | |
| 2 | import { Stamped } from "../src/kernel/stamped.ts"; | |
| 3 | ||
| 4 | function memo(maxEntries = 3, maxAgeMs = 1000) { | |
| 5 | const clock = { t: 0 }; | |
| 6 | const stamped = new Stamped<number>({ maxEntries, maxAgeMs, now: () => clock.t }); | |
| 7 | let runs = 0; | |
| 8 | const compute = () => ++runs; | |
| 9 | return { clock, stamped, compute, runs: () => runs }; | |
| 10 | } | |
| 11 | ||
| 12 | describe("Stamped", () => { | |
| 13 | test("same key and stamp computes once and counts the hits", () => { | |
| 14 | const m = memo(); | |
| 15 | expect(m.stamped.get("a", "s1", m.compute)).toBe(1); | |
| 16 | expect(m.stamped.get("a", "s1", m.compute)).toBe(1); | |
| 17 | expect(m.stamped.get("a", "s1", m.compute)).toBe(1); | |
| 18 | expect(m.runs()).toBe(1); | |
| 19 | expect(m.stamped.hits).toBe(2); | |
| 20 | expect(m.stamped.misses).toBe(1); | |
| 21 | expect(m.stamped.size).toBe(1); | |
| 22 | }); | |
| 23 | ||
| 24 | test("a new stamp recomputes and replaces the value", () => { | |
| 25 | const m = memo(); | |
| 26 | expect(m.stamped.get("a", "s1", m.compute)).toBe(1); | |
| 27 | expect(m.stamped.get("a", "s2", m.compute)).toBe(2); | |
| 28 | expect(m.stamped.get("a", "s2", m.compute)).toBe(2); | |
| 29 | expect(m.stamped.size).toBe(1); | |
| 30 | expect(m.stamped.get("a", "s1", m.compute)).toBe(3); | |
| 31 | }); | |
| 32 | ||
| 33 | test("the max age bound is strict", () => { | |
| 34 | const m = memo(3, 1000); | |
| 35 | m.stamped.get("a", "s", m.compute); | |
| 36 | m.clock.t = 999; | |
| 37 | expect(m.stamped.get("a", "s", m.compute)).toBe(1); | |
| 38 | m.clock.t = 1000; | |
| 39 | expect(m.stamped.get("a", "s", m.compute)).toBe(2); | |
| 40 | expect(m.stamped.size).toBe(1); | |
| 41 | }); | |
| 42 | ||
| 43 | test("past maxEntries the least recently used key goes, not the least recently set", () => { | |
| 44 | const m = memo(2); | |
| 45 | m.stamped.get("a", "s", m.compute); | |
| 46 | m.stamped.get("b", "s", m.compute); | |
| 47 | m.stamped.get("a", "s", m.compute); // hit: a is now newest | |
| 48 | m.stamped.get("c", "s", m.compute); // evicts b | |
| 49 | expect(m.stamped.size).toBe(2); | |
| 50 | const before = m.runs(); | |
| 51 | m.stamped.get("a", "s", m.compute); | |
| 52 | expect(m.runs()).toBe(before); | |
| 53 | m.stamped.get("b", "s", m.compute); | |
| 54 | expect(m.runs()).toBe(before + 1); | |
| 55 | }); | |
| 56 | ||
| 57 | test("a compute that throws leaves no entry and the next read computes again", () => { | |
| 58 | const m = memo(); | |
| 59 | expect(() => m.stamped.get("a", "s", () => { throw new Error("boom"); })).toThrow("boom"); | |
| 60 | expect(m.stamped.size).toBe(0); | |
| 61 | expect(m.stamped.get("a", "s", m.compute)).toBe(1); | |
| 62 | expect(m.stamped.size).toBe(1); | |
| 63 | }); | |
| 64 | ||
| 65 | test("a throwing recompute drops nothing it should keep and caches no failure", () => { | |
| 66 | const m = memo(); | |
| 67 | m.stamped.get("a", "s1", m.compute); | |
| 68 | expect(() => m.stamped.get("a", "s2", () => { throw new Error("boom"); })).toThrow(); | |
| 69 | expect(m.stamped.size).toBe(1); | |
| 70 | expect(m.stamped.get("a", "s2", m.compute)).toBe(2); | |
| 71 | }); | |
| 72 | ||
| 73 | test("clear empties it", () => { | |
| 74 | const m = memo(); | |
| 75 | m.stamped.get("a", "s", m.compute); | |
| 76 | m.stamped.get("b", "s", m.compute); | |
| 77 | expect(m.stamped.size).toBe(2); | |
| 78 | m.stamped.clear(); | |
| 79 | expect(m.stamped.size).toBe(0); | |
| 80 | expect(m.stamped.get("a", "s", m.compute)).toBe(3); | |
| 81 | }); | |
| 82 | }); |
new above
offline
Nit, not blocking: the test name says the throwing recompute "drops nothing it should keep", but
sizebeing 1 doesn't show which entry survived. Addingexpect(m.stamped.get("a", "s1", m.compute)).toBe(1)here would show that thes1value is still served.