14 "github.com/stretchr/testify/require"
17 func TestMemoryRestartLoadsTheSameView(t *testing.T) {
19 clock := newTestClock()
20 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
21 require.NoError(t, err)
23 memory.chat.high = 200
26 memory.cview.high = 80
29 _, err = memory.Append("user", fmt.Sprint(n))
30 require.NoError(t, err)
34 context := memory.Context(0, 100)
35 saved, err := os.ReadFile(filepath.Join(root, "view.json"))
36 require.NoError(t, err)
37 require.Less(t, len(memory.chat.lines), 100)
38 require.NoError(t, memory.Close())
40 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
41 require.NoError(t, err)
44 require.NoError(t, memory.Close())
47 require.Equal(t, view, memory.View())
48 require.Equal(t, context, memory.Context(0, 100))
50 loaded, err := os.ReadFile(filepath.Join(root, "view.json"))
51 require.NoError(t, err)
52 require.Equal(t, saved, loaded)
55 func TestMemoryOpensAfterACrashBeforeTheViewSave(t *testing.T) {
57 clock := newTestClock()
58 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
59 require.NoError(t, err)
61 _, err = memory.Append("user", "first")
62 require.NoError(t, err)
64 saved, err := os.ReadFile(filepath.Join(root, "view.json"))
65 require.NoError(t, err)
67 _, err = memory.Append("user", "second")
68 require.NoError(t, err)
71 require.NoError(t, memory.Close())
73 // The log and the tree hold message 1, but the view does not.
74 require.NoError(t, os.WriteFile(filepath.Join(root, "view.json"), saved, 0o600))
76 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
77 require.NoError(t, err)
78 require.Equal(t, view, memory.View())
79 require.NoError(t, memory.Close())
82 func TestMemoryKeepsTheCompactionViewCoarser(t *testing.T) {
83 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
84 require.NoError(t, err)
87 require.NoError(t, memory.Close())
90 memory.chat.high = 200
93 memory.cview.high = 80
96 _, err = memory.Append("user", fmt.Sprint(n%10))
97 require.NoError(t, err)
100 for _, line := range memory.chat.lines {
101 outer := memory.cview.lines[coarse]
102 for nodeEnd(outer.level, outer.index) <= line.index<<line.level {
104 outer = memory.cview.lines[coarse]
107 require.GreaterOrEqual(t, outer.level, line.level, "message %d", n)
112 func TestMemoryRunsAtMostEightBuilds(t *testing.T) {
113 summarizer := newFakeSummarizer()
114 summarizer.gate = make(chan struct{})
115 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
116 require.NoError(t, err)
119 require.NoError(t, memory.Close())
123 _, err = memory.Append("echo", memoryLongText(n))
124 require.NoError(t, err)
129 running := slices.Collect(maps.Keys(memory.queue.running))
130 memory.mutex.Unlock()
131 slices.SortFunc(running, func(a, b NodeKey) int {
132 return a.index - b.index
134 require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
136 require.Eventually(t, func() bool {
137 return summarizer.Active() == buildMax
138 }, 10*time.Second, time.Millisecond)
140 close(summarizer.gate)
141 require.NoError(t, memory.Wait(t.Context()))
142 require.Equal(t, buildMax, summarizer.peak)
144 for level, count := range []int{12, 6, 3, 1} {
145 for index := range count {
146 require.True(t, treeIsBuilt(t, memory.tree, level, index), "node (%d, %d)", level, index)
151 func TestMemorySettleWaitsForThePendingLines(t *testing.T) {
152 summarizer := newFakeSummarizer()
153 summarizer.gate = make(chan struct{})
154 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
155 require.NoError(t, err)
158 require.NoError(t, memory.Close())
161 _, err = memory.Append("echo", memoryLongText(0))
162 require.NoError(t, err)
163 require.Contains(t, memory.View(), viewPending)
165 settled := make(chan error, 1)
167 settled <- memory.Settle(t.Context())
171 case err = <-settled:
172 require.Fail(t, "Settle returned before the build", "error: %v", err)
173 case <-time.After(50 * time.Millisecond):
176 close(summarizer.gate)
177 require.NoError(t, <-settled)
178 require.NotContains(t, memory.View(), viewPending)
181 func TestMemorySettleFailsWhileABuildFailed(t *testing.T) {
182 summarizer := newFakeSummarizer()
183 summarizer.SetFailing(true)
185 logs := &SyncBuffer{}
186 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, daemonTestLogger(logs))
187 require.NoError(t, err)
190 require.NoError(t, memory.Close())
193 _, err = memory.Append("echo", memoryLongText(0))
194 require.NoError(t, err)
195 require.ErrorIs(t, memory.Settle(t.Context()), errMemoryUnsettled)
196 require.Equal(t, "<4>summary failed level=0 index=0: fake failure\n", logs.String())
198 summarizer.SetFailing(false)
200 _, err = memory.Append("user", "retry")
201 require.NoError(t, err)
202 require.NoError(t, memory.Settle(t.Context()))
205 func TestMemorySettleEndsWithItsContext(t *testing.T) {
206 summarizer := newFakeSummarizer()
207 summarizer.gate = make(chan struct{})
208 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
209 require.NoError(t, err)
212 require.NoError(t, memory.Close())
215 _, err = memory.Append("echo", memoryLongText(0))
216 require.NoError(t, err)
218 ctx, cancel := context.WithTimeout(t.Context(), 50*time.Millisecond)
221 require.ErrorIs(t, memory.Settle(ctx), context.DeadlineExceeded)
224 func TestMemoryStartsALeafAfterFewerThanEightUnbuilt(t *testing.T) {
225 summarizer := newFakeSummarizer()
226 summarizer.SetFailing(true)
228 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
229 require.NoError(t, err)
232 require.NoError(t, memory.Close())
236 _, err = memory.Append("echo", memoryLongText(n))
237 require.NoError(t, err)
240 require.NoError(t, memory.Wait(t.Context()))
242 for _, task := range summarizer.Tasks() {
243 require.Less(t, task.Index, 8)
246 require.False(t, treeIsBuilt(t, memory.tree, 0, 8))
247 summarizer.SetFailing(false)
249 _, err = memory.Append("user", "retry")
250 require.NoError(t, err)
251 require.NoError(t, memory.Wait(t.Context()))
252 require.True(t, treeIsBuilt(t, memory.tree, 0, 8))
253 require.True(t, treeIsBuilt(t, memory.tree, 3, 0))
256 func TestMemoryMergesAfterBothHalves(t *testing.T) {
257 summarizer := newFakeSummarizer()
258 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
259 require.NoError(t, err)
262 require.NoError(t, memory.Close())
265 _, err = memory.Append("echo", memoryLongText(0))
266 require.NoError(t, err)
267 require.NoError(t, memory.Wait(t.Context()))
269 _, err = memory.Append("echo", memoryLongText(1))
270 require.NoError(t, err)
271 require.NoError(t, memory.Wait(t.Context()))
272 require.Len(t, summarizer.Tasks(), 3)
274 left := treeReadNode(t, memory.tree, 0, 0)
275 right := treeReadNode(t, memory.tree, 0, 1)
276 merge := summarizer.Tasks()[2]
277 require.Equal(t, []string{left.Text, right.Text}, merge.Input)
278 require.Equal(t, "<chat>\n0+1|"+left.Text+"\n1+1|"+right.Text+"\n</chat>", merge.Context)
279 require.True(t, treeIsBuilt(t, memory.tree, 1, 0))
282 func TestMemoryRestartRestoresTheQueue(t *testing.T) {
284 clock := newTestClock()
285 summarizer := newFakeSummarizer()
286 summarizer.SetFailing(true)
288 memory, err := openMemory(t.Context(), root, clock.Now, summarizer, discardLog())
289 require.NoError(t, err)
292 _, err = memory.Append("echo", memoryLongText(n))
293 require.NoError(t, err)
296 require.NoError(t, memory.Wait(t.Context()))
297 require.NoError(t, memory.Close())
299 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
300 require.NoError(t, err)
303 require.NoError(t, memory.Close())
306 require.NoError(t, memory.Wait(t.Context()))
307 require.True(t, treeIsBuilt(t, memory.tree, 2, 0))
310 func TestMemoryBuildsFreeParents(t *testing.T) {
311 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
312 require.NoError(t, err)
315 require.NoError(t, memory.Close())
318 for _, text := range []string{"a", "b", "c", "d"} {
319 _, err = memory.Append("user", text)
320 require.NoError(t, err)
323 node := treeReadNode(t, memory.tree, 2, 0)
324 require.Equal(t, "user: a\nuser: b\nuser: c\nuser: d", node.Text)
327 func TestMemoryKeepsALongMessageOutOfTheView(t *testing.T) {
328 summarizer := newFakeSummarizer()
329 summarizer.SetFailing(true)
331 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
332 require.NoError(t, err)
335 require.NoError(t, memory.Close())
338 _, err = memory.Append("echo", memoryLongText(0))
339 require.NoError(t, err)
340 require.NoError(t, memory.Wait(t.Context()))
341 require.Equal(t, "<chat>\n0+1|"+viewPending+"\n</chat>", memory.View())
343 summarizer.SetFailing(false)
345 _, err = memory.Append("user", "again")
346 require.NoError(t, err)
347 require.NoError(t, memory.Wait(t.Context()))
349 task, err := memory.task(NodeKey{0, 0})
350 require.NoError(t, err)
351 require.Equal(t, "<chat>\n0+1|"+fakeLine(task)+"\n1+1|user: again\n</chat>", memory.View())
354 func TestMemoryCoversMessagesAfterTheSavedView(t *testing.T) {
356 clock := newTestClock()
357 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
358 require.NoError(t, err)
360 _, err = memory.Append("user", "a")
361 require.NoError(t, err)
363 _, err = memory.log.Append("reply", "b")
364 require.NoError(t, err)
365 require.NoError(t, memory.Close())
367 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
368 require.NoError(t, err)
371 require.NoError(t, memory.Close())
374 require.Equal(t, "<chat>\n0+1|user: a\n1+1|reply: b\n</chat>", memory.View())
377 func TestMemoryRefusesToRebuildAMissingView(t *testing.T) {
379 clock := newTestClock()
380 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
381 require.NoError(t, err)
383 _, err = memory.Append("user", "a")
384 require.NoError(t, err)
385 require.NoError(t, memory.Close())
386 require.NoError(t, os.Remove(filepath.Join(root, "view.json")))
388 _, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
389 require.ErrorIs(t, err, errMemoryNoView)
392 // memoryLongText returns a text that is too long for its own node.
393 func memoryLongText(n int) string {
394 return fmt.Sprintf("long %d ", n) + strings.Repeat("x", nodeBytes)