15 "github.com/stretchr/testify/require"
18 func TestMemoryRestartLoadsTheSameView(t *testing.T) {
20 clock := newTestClock()
21 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
22 require.NoError(t, err)
24 memory.chat.high = 200
27 memory.cview.high = 80
30 _, err = memory.Append("user", fmt.Sprint(n))
31 require.NoError(t, err)
35 context := memory.Context(0, 100)
36 saved, err := os.ReadFile(filepath.Join(root, "view.json"))
37 require.NoError(t, err)
38 require.Less(t, len(memory.chat.lines), 100)
39 require.NoError(t, memory.Close())
41 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
42 require.NoError(t, err)
45 require.NoError(t, memory.Close())
48 require.Equal(t, view, memory.View())
49 require.Equal(t, context, memory.Context(0, 100))
51 loaded, err := os.ReadFile(filepath.Join(root, "view.json"))
52 require.NoError(t, err)
53 require.Equal(t, saved, loaded)
56 func TestMemoryOpensAfterACrashBeforeTheViewSave(t *testing.T) {
58 clock := newTestClock()
59 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
60 require.NoError(t, err)
62 _, err = memory.Append("user", "first")
63 require.NoError(t, err)
65 saved, err := os.ReadFile(filepath.Join(root, "view.json"))
66 require.NoError(t, err)
68 _, err = memory.Append("user", "second")
69 require.NoError(t, err)
72 require.NoError(t, memory.Close())
74 // The log and the tree hold message 1, but the view does not.
75 require.NoError(t, os.WriteFile(filepath.Join(root, "view.json"), saved, 0o600))
77 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
78 require.NoError(t, err)
79 require.Equal(t, view, memory.View())
80 require.NoError(t, memory.Close())
83 func TestMemoryKeepsTheCompactionViewCoarser(t *testing.T) {
84 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
85 require.NoError(t, err)
88 require.NoError(t, memory.Close())
91 memory.chat.high = 200
94 memory.cview.high = 80
97 _, err = memory.Append("user", fmt.Sprint(n%10))
98 require.NoError(t, err)
101 for _, line := range memory.chat.lines {
102 outer := memory.cview.lines[coarse]
103 for nodeEnd(outer.level, outer.index) <= line.index<<line.level {
105 outer = memory.cview.lines[coarse]
108 require.GreaterOrEqual(t, outer.level, line.level, "message %d", n)
113 func TestMemoryRunsAtMostEightBuilds(t *testing.T) {
114 summarizer := newFakeSummarizer()
115 summarizer.gate = make(chan struct{})
116 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
117 require.NoError(t, err)
120 require.NoError(t, memory.Close())
124 _, err = memory.Append("echo", memoryLongText(n))
125 require.NoError(t, err)
130 running := slices.Collect(maps.Keys(memory.queue.running))
131 memory.mutex.Unlock()
132 slices.SortFunc(running, func(a, b NodeKey) int {
133 return a.index - b.index
135 require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
137 require.Eventually(t, func() bool {
138 return summarizer.Active() == buildMax
139 }, 10*time.Second, time.Millisecond)
141 close(summarizer.gate)
142 require.NoError(t, memory.Wait(t.Context()))
143 require.Equal(t, buildMax, summarizer.peak)
145 for level, count := range []int{12, 6, 3, 1} {
146 for index := range count {
147 require.True(t, treeIsBuilt(t, memory.tree, level, index), "node (%d, %d)", level, index)
152 func TestMemorySettleWaitsForThePendingLines(t *testing.T) {
153 summarizer := newFakeSummarizer()
154 summarizer.gate = make(chan struct{})
155 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
156 require.NoError(t, err)
159 require.NoError(t, memory.Close())
162 _, err = memory.Append("echo", memoryLongText(0))
163 require.NoError(t, err)
164 require.Contains(t, memory.View(), viewPending)
166 settled := make(chan error, 1)
168 settled <- memory.Settle(t.Context())
172 case err = <-settled:
173 require.Fail(t, "Settle returned before the build", "error: %v", err)
174 case <-time.After(50 * time.Millisecond):
177 close(summarizer.gate)
178 require.NoError(t, <-settled)
179 require.NotContains(t, memory.View(), viewPending)
182 func TestMemorySettleFailsWhileABuildFailed(t *testing.T) {
183 summarizer := newFakeSummarizer()
184 summarizer.SetFailing(true)
186 logs := &SyncBuffer{}
187 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, log.New(logs, "", 0))
188 require.NoError(t, err)
191 require.NoError(t, memory.Close())
194 _, err = memory.Append("echo", memoryLongText(0))
195 require.NoError(t, err)
196 require.ErrorIs(t, memory.Settle(t.Context()), errMemoryUnsettled)
197 require.Equal(t, "summary failed, retry at next message: node (0, 0): fake failure\n", logs.String())
199 summarizer.SetFailing(false)
201 _, err = memory.Append("user", "retry")
202 require.NoError(t, err)
203 require.NoError(t, memory.Settle(t.Context()))
206 func TestMemorySettleEndsWithItsContext(t *testing.T) {
207 summarizer := newFakeSummarizer()
208 summarizer.gate = make(chan struct{})
209 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
210 require.NoError(t, err)
213 require.NoError(t, memory.Close())
216 _, err = memory.Append("echo", memoryLongText(0))
217 require.NoError(t, err)
219 ctx, cancel := context.WithTimeout(t.Context(), 50*time.Millisecond)
222 require.ErrorIs(t, memory.Settle(ctx), context.DeadlineExceeded)
225 func TestMemoryStartsALeafAfterFewerThanEightUnbuilt(t *testing.T) {
226 summarizer := newFakeSummarizer()
227 summarizer.SetFailing(true)
229 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
230 require.NoError(t, err)
233 require.NoError(t, memory.Close())
237 _, err = memory.Append("echo", memoryLongText(n))
238 require.NoError(t, err)
241 require.NoError(t, memory.Wait(t.Context()))
243 for _, task := range summarizer.Tasks() {
244 require.Less(t, task.Index, 8)
247 require.False(t, treeIsBuilt(t, memory.tree, 0, 8))
248 summarizer.SetFailing(false)
250 _, err = memory.Append("user", "retry")
251 require.NoError(t, err)
252 require.NoError(t, memory.Wait(t.Context()))
253 require.True(t, treeIsBuilt(t, memory.tree, 0, 8))
254 require.True(t, treeIsBuilt(t, memory.tree, 3, 0))
257 func TestMemoryMergesAfterBothHalves(t *testing.T) {
258 summarizer := newFakeSummarizer()
259 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
260 require.NoError(t, err)
263 require.NoError(t, memory.Close())
266 _, err = memory.Append("echo", memoryLongText(0))
267 require.NoError(t, err)
268 require.NoError(t, memory.Wait(t.Context()))
270 _, err = memory.Append("echo", memoryLongText(1))
271 require.NoError(t, err)
272 require.NoError(t, memory.Wait(t.Context()))
273 require.Len(t, summarizer.Tasks(), 3)
275 left := treeReadNode(t, memory.tree, 0, 0)
276 right := treeReadNode(t, memory.tree, 0, 1)
277 merge := summarizer.Tasks()[2]
278 require.Equal(t, []string{left.Text, right.Text}, merge.Input)
279 require.Equal(t, "<chat>\n0+1|"+left.Text+"\n1+1|"+right.Text+"\n</chat>", merge.Context)
280 require.True(t, treeIsBuilt(t, memory.tree, 1, 0))
283 func TestMemoryRestartRestoresTheQueue(t *testing.T) {
285 clock := newTestClock()
286 summarizer := newFakeSummarizer()
287 summarizer.SetFailing(true)
289 memory, err := openMemory(t.Context(), root, clock.Now, summarizer, discardLog())
290 require.NoError(t, err)
293 _, err = memory.Append("echo", memoryLongText(n))
294 require.NoError(t, err)
297 require.NoError(t, memory.Wait(t.Context()))
298 require.NoError(t, memory.Close())
300 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
301 require.NoError(t, err)
304 require.NoError(t, memory.Close())
307 require.NoError(t, memory.Wait(t.Context()))
308 require.True(t, treeIsBuilt(t, memory.tree, 2, 0))
311 func TestMemoryBuildsFreeParents(t *testing.T) {
312 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
313 require.NoError(t, err)
316 require.NoError(t, memory.Close())
319 for _, text := range []string{"a", "b", "c", "d"} {
320 _, err = memory.Append("user", text)
321 require.NoError(t, err)
324 node := treeReadNode(t, memory.tree, 2, 0)
325 require.Equal(t, "user: a\nuser: b\nuser: c\nuser: d", node.Text)
328 func TestMemoryKeepsALongMessageOutOfTheView(t *testing.T) {
329 summarizer := newFakeSummarizer()
330 summarizer.SetFailing(true)
332 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
333 require.NoError(t, err)
336 require.NoError(t, memory.Close())
339 _, err = memory.Append("echo", memoryLongText(0))
340 require.NoError(t, err)
341 require.NoError(t, memory.Wait(t.Context()))
342 require.Equal(t, "<chat>\n0+1|"+viewPending+"\n</chat>", memory.View())
344 summarizer.SetFailing(false)
346 _, err = memory.Append("user", "again")
347 require.NoError(t, err)
348 require.NoError(t, memory.Wait(t.Context()))
350 task, err := memory.task(NodeKey{0, 0})
351 require.NoError(t, err)
352 require.Equal(t, "<chat>\n0+1|"+fakeLine(task)+"\n1+1|user: again\n</chat>", memory.View())
355 func TestMemoryCoversMessagesAfterTheSavedView(t *testing.T) {
357 clock := newTestClock()
358 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
359 require.NoError(t, err)
361 _, err = memory.Append("user", "a")
362 require.NoError(t, err)
364 _, err = memory.log.Append("reply", "b")
365 require.NoError(t, err)
366 require.NoError(t, memory.Close())
368 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
369 require.NoError(t, err)
372 require.NoError(t, memory.Close())
375 require.Equal(t, "<chat>\n0+1|user: a\n1+1|reply: b\n</chat>", memory.View())
378 func TestMemoryRefusesToRebuildAMissingView(t *testing.T) {
380 clock := newTestClock()
381 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
382 require.NoError(t, err)
384 _, err = memory.Append("user", "a")
385 require.NoError(t, err)
386 require.NoError(t, memory.Close())
387 require.NoError(t, os.Remove(filepath.Join(root, "view.json")))
389 _, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
390 require.ErrorIs(t, err, errMemoryNoView)
393 // memoryLongText returns a text that is too long for its own node.
394 func memoryLongText(n int) string {
395 return fmt.Sprintf("long %d ", n) + strings.Repeat("x", nodeBytes)