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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6695-a-test-pins-stamped-the-memo-every-cache → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/6695-a-test-pins-stamped-the-memo-every-cache was merged into main as 090b564.

Review

Sign in to review this change.
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);

Nit, not blocking: the test name says the throwing recompute "drops nothing it should keep", but size being 1 doesn't show which entry survived. Adding expect(m.stamped.get("a", "s1", m.compute)).toBe(1) here would show that the s1 value is still served.

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 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs