13 "github.com/stretchr/testify/require"
16 func TestMemoryRestartLoadsTheSameView(t *testing.T) {
18 clock := newTestClock()
19 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
20 require.NoError(t, err)
22 memory.chat.high = 200
24 memory.cview.high = 80
27 _, err = memory.Append("user", fmt.Sprint(n))
28 require.NoError(t, err)
32 context := memory.Context(0, 100)
33 saved, err := os.ReadFile(filepath.Join(root, "view.json"))
34 require.NoError(t, err)
35 require.Less(t, len(memory.chat.lines), 100)
36 require.NoError(t, memory.Close())
38 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
39 require.NoError(t, err)
42 require.NoError(t, memory.Close())
45 require.Equal(t, view, memory.View())
46 require.Equal(t, context, memory.Context(0, 100))
48 loaded, err := os.ReadFile(filepath.Join(root, "view.json"))
49 require.NoError(t, err)
50 require.Equal(t, saved, loaded)
53 func TestMemoryOpensAfterACrashBeforeTheViewSave(t *testing.T) {
55 clock := newTestClock()
56 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
57 require.NoError(t, err)
59 _, err = memory.Append("user", "first")
60 require.NoError(t, err)
62 saved, err := os.ReadFile(filepath.Join(root, "view.json"))
63 require.NoError(t, err)
65 _, err = memory.Append("user", "second")
66 require.NoError(t, err)
69 require.NoError(t, memory.Close())
71 // The log and the tree hold message 1, but the view does not.
72 require.NoError(t, os.WriteFile(filepath.Join(root, "view.json"), saved, 0o600))
74 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
75 require.NoError(t, err)
76 require.Equal(t, view, memory.View())
77 require.NoError(t, memory.Close())
80 func TestMemoryKeepsTheCompactionViewCoarser(t *testing.T) {
81 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer())
82 require.NoError(t, err)
85 require.NoError(t, memory.Close())
88 memory.chat.high = 200
90 memory.cview.high = 80
93 _, err = memory.Append("user", fmt.Sprint(n%10))
94 require.NoError(t, err)
97 for _, line := range memory.chat.lines {
98 outer := memory.cview.lines[coarse]
99 for nodeEnd(outer.level, outer.index) <= line.index<<line.level {
101 outer = memory.cview.lines[coarse]
104 require.GreaterOrEqual(t, outer.level, line.level, "message %d", n)
109 func TestMemoryRunsAtMostEightBuilds(t *testing.T) {
110 summarizer := newFakeSummarizer()
111 summarizer.gate = make(chan struct{})
112 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
113 require.NoError(t, err)
116 require.NoError(t, memory.Close())
120 _, err = memory.Append("echo", memoryLongText(n))
121 require.NoError(t, err)
125 running := slices.Collect(maps.Keys(memory.queue.running))
126 memory.mutex.Unlock()
127 slices.SortFunc(running, func(a, b NodeKey) int {
128 return a.index - b.index
130 require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
132 require.Eventually(t, func() bool {
133 return summarizer.Active() == buildMax
134 }, 10*time.Second, time.Millisecond)
136 close(summarizer.gate)
137 require.NoError(t, memory.Wait(t.Context()))
138 require.Equal(t, buildMax, summarizer.peak)
140 for level, count := range []int{12, 6, 3, 1} {
141 for index := range count {
142 require.True(t, treeIsBuilt(t, memory.tree, level, index), "node (%d, %d)", level, index)
147 func TestMemoryStartsALeafAfterFewerThanEightUnbuilt(t *testing.T) {
148 summarizer := newFakeSummarizer()
149 summarizer.SetFailing(true)
150 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
151 require.NoError(t, err)
154 require.NoError(t, memory.Close())
158 _, err = memory.Append("echo", memoryLongText(n))
159 require.NoError(t, err)
162 require.NoError(t, memory.Wait(t.Context()))
163 for _, task := range summarizer.Tasks() {
164 require.Less(t, task.Index, 8)
167 require.False(t, treeIsBuilt(t, memory.tree, 0, 8))
168 summarizer.SetFailing(false)
170 _, err = memory.Append("user", "retry")
171 require.NoError(t, err)
172 require.NoError(t, memory.Wait(t.Context()))
173 require.True(t, treeIsBuilt(t, memory.tree, 0, 8))
174 require.True(t, treeIsBuilt(t, memory.tree, 3, 0))
177 func TestMemoryMergesAfterBothHalves(t *testing.T) {
178 summarizer := newFakeSummarizer()
179 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
180 require.NoError(t, err)
183 require.NoError(t, memory.Close())
186 _, err = memory.Append("echo", memoryLongText(0))
187 require.NoError(t, err)
188 require.NoError(t, memory.Wait(t.Context()))
190 _, err = memory.Append("echo", memoryLongText(1))
191 require.NoError(t, err)
192 require.NoError(t, memory.Wait(t.Context()))
193 require.Len(t, summarizer.Tasks(), 3)
195 left := treeReadNode(t, memory.tree, 0, 0)
196 right := treeReadNode(t, memory.tree, 0, 1)
197 merge := summarizer.Tasks()[2]
198 require.Equal(t, []string{left.Text, right.Text}, merge.Input)
199 require.Equal(t, "<chat>\n0+1|"+left.Text+"\n1+1|"+right.Text+"\n</chat>", merge.Context)
200 require.True(t, treeIsBuilt(t, memory.tree, 1, 0))
203 func TestMemoryRestartRestoresTheQueue(t *testing.T) {
205 clock := newTestClock()
206 summarizer := newFakeSummarizer()
207 summarizer.SetFailing(true)
208 memory, err := openMemory(t.Context(), root, clock.Now, summarizer)
209 require.NoError(t, err)
212 _, err = memory.Append("echo", memoryLongText(n))
213 require.NoError(t, err)
216 require.NoError(t, memory.Wait(t.Context()))
217 require.NoError(t, memory.Close())
219 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
220 require.NoError(t, err)
223 require.NoError(t, memory.Close())
226 require.NoError(t, memory.Wait(t.Context()))
227 require.True(t, treeIsBuilt(t, memory.tree, 2, 0))
230 func TestMemoryBuildsFreeParents(t *testing.T) {
231 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer())
232 require.NoError(t, err)
235 require.NoError(t, memory.Close())
238 for _, text := range []string{"a", "b", "c", "d"} {
239 _, err = memory.Append("user", text)
240 require.NoError(t, err)
243 node := treeReadNode(t, memory.tree, 2, 0)
244 require.Equal(t, "user: a\nuser: b\nuser: c\nuser: d", node.Text)
247 func TestMemoryKeepsALongMessageOutOfTheView(t *testing.T) {
248 summarizer := newFakeSummarizer()
249 summarizer.SetFailing(true)
250 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
251 require.NoError(t, err)
254 require.NoError(t, memory.Close())
257 _, err = memory.Append("echo", memoryLongText(0))
258 require.NoError(t, err)
259 require.NoError(t, memory.Wait(t.Context()))
260 require.Equal(t, "<chat>\n0+1|"+viewPending+"\n</chat>", memory.View())
262 summarizer.SetFailing(false)
264 _, err = memory.Append("user", "again")
265 require.NoError(t, err)
266 require.NoError(t, memory.Wait(t.Context()))
268 task, err := memory.task(NodeKey{0, 0})
269 require.NoError(t, err)
270 require.Equal(t, "<chat>\n0+1|"+fakeLine(task)+"\n1+1|user: again\n</chat>", memory.View())
273 func TestMemoryCoversMessagesAfterTheSavedView(t *testing.T) {
275 clock := newTestClock()
276 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
277 require.NoError(t, err)
279 _, err = memory.Append("user", "a")
280 require.NoError(t, err)
282 _, err = memory.log.Append("reply", "b")
283 require.NoError(t, err)
284 require.NoError(t, memory.Close())
286 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
287 require.NoError(t, err)
290 require.NoError(t, memory.Close())
293 require.Equal(t, "<chat>\n0+1|user: a\n1+1|reply: b\n</chat>", memory.View())
296 func TestMemoryRefusesToRebuildAMissingView(t *testing.T) {
298 clock := newTestClock()
299 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
300 require.NoError(t, err)
302 _, err = memory.Append("user", "a")
303 require.NoError(t, err)
304 require.NoError(t, memory.Close())
305 require.NoError(t, os.Remove(filepath.Join(root, "view.json")))
307 _, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
308 require.ErrorIs(t, err, errMemoryNoView)
311 // memoryLongText returns a text that is too long for its own node.
312 func memoryLongText(n int) string {
313 return fmt.Sprintf("long %d ", n) + strings.Repeat("x", nodeBytes)