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
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
92 memory.cview.high = 80
95 _, err = memory.Append("user", fmt.Sprint(n%10))
96 require.NoError(t, err)
99 for _, line := range memory.chat.lines {
100 outer := memory.cview.lines[coarse]
101 for nodeEnd(outer.level, outer.index) <= line.index<<line.level {
103 outer = memory.cview.lines[coarse]
106 require.GreaterOrEqual(t, outer.level, line.level, "message %d", n)
111 func TestMemoryRunsAtMostEightBuilds(t *testing.T) {
112 summarizer := newFakeSummarizer()
113 summarizer.gate = make(chan struct{})
114 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
115 require.NoError(t, err)
118 require.NoError(t, memory.Close())
122 _, err = memory.Append("echo", memoryLongText(n))
123 require.NoError(t, err)
127 running := slices.Collect(maps.Keys(memory.queue.running))
128 memory.mutex.Unlock()
129 slices.SortFunc(running, func(a, b NodeKey) int {
130 return a.index - b.index
132 require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running)
134 require.Eventually(t, func() bool {
135 return summarizer.Active() == buildMax
136 }, 10*time.Second, time.Millisecond)
138 close(summarizer.gate)
139 require.NoError(t, memory.Wait(t.Context()))
140 require.Equal(t, buildMax, summarizer.peak)
142 for level, count := range []int{12, 6, 3, 1} {
143 for index := range count {
144 require.True(t, treeIsBuilt(t, memory.tree, level, index), "node (%d, %d)", level, index)
149 func TestMemorySettleWaitsForThePendingLines(t *testing.T) {
150 summarizer := newFakeSummarizer()
151 summarizer.gate = make(chan struct{})
152 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
153 require.NoError(t, err)
156 require.NoError(t, memory.Close())
159 _, err = memory.Append("echo", memoryLongText(0))
160 require.NoError(t, err)
161 require.Contains(t, memory.View(), viewPending)
163 settled := make(chan error, 1)
165 settled <- memory.Settle(t.Context())
169 case err = <-settled:
170 require.Fail(t, "Settle returned before the build", "error: %v", err)
171 case <-time.After(50 * time.Millisecond):
174 close(summarizer.gate)
175 require.NoError(t, <-settled)
176 require.NotContains(t, memory.View(), viewPending)
179 func TestMemorySettleFailsWhileABuildFailed(t *testing.T) {
180 summarizer := newFakeSummarizer()
181 summarizer.SetFailing(true)
182 logs := &SyncBuffer{}
183 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, log.New(logs, "", 0))
184 require.NoError(t, err)
187 require.NoError(t, memory.Close())
190 _, err = memory.Append("echo", memoryLongText(0))
191 require.NoError(t, err)
192 require.ErrorIs(t, memory.Settle(t.Context()), errMemoryUnsettled)
193 require.Equal(t, "a summary of the chat failed, and it is tried again at the next message: node (0, 0): fake failure\n", logs.String())
195 summarizer.SetFailing(false)
196 _, err = memory.Append("user", "retry")
197 require.NoError(t, err)
198 require.NoError(t, memory.Settle(t.Context()))
201 func TestMemorySettleEndsWithItsContext(t *testing.T) {
202 summarizer := newFakeSummarizer()
203 summarizer.gate = make(chan struct{})
204 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
205 require.NoError(t, err)
208 require.NoError(t, memory.Close())
211 _, err = memory.Append("echo", memoryLongText(0))
212 require.NoError(t, err)
214 ctx, cancel := context.WithTimeout(t.Context(), 50*time.Millisecond)
217 require.ErrorIs(t, memory.Settle(ctx), context.DeadlineExceeded)
220 func TestMemoryStartsALeafAfterFewerThanEightUnbuilt(t *testing.T) {
221 summarizer := newFakeSummarizer()
222 summarizer.SetFailing(true)
223 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
224 require.NoError(t, err)
227 require.NoError(t, memory.Close())
231 _, err = memory.Append("echo", memoryLongText(n))
232 require.NoError(t, err)
235 require.NoError(t, memory.Wait(t.Context()))
236 for _, task := range summarizer.Tasks() {
237 require.Less(t, task.Index, 8)
240 require.False(t, treeIsBuilt(t, memory.tree, 0, 8))
241 summarizer.SetFailing(false)
243 _, err = memory.Append("user", "retry")
244 require.NoError(t, err)
245 require.NoError(t, memory.Wait(t.Context()))
246 require.True(t, treeIsBuilt(t, memory.tree, 0, 8))
247 require.True(t, treeIsBuilt(t, memory.tree, 3, 0))
250 func TestMemoryMergesAfterBothHalves(t *testing.T) {
251 summarizer := newFakeSummarizer()
252 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
253 require.NoError(t, err)
256 require.NoError(t, memory.Close())
259 _, err = memory.Append("echo", memoryLongText(0))
260 require.NoError(t, err)
261 require.NoError(t, memory.Wait(t.Context()))
263 _, err = memory.Append("echo", memoryLongText(1))
264 require.NoError(t, err)
265 require.NoError(t, memory.Wait(t.Context()))
266 require.Len(t, summarizer.Tasks(), 3)
268 left := treeReadNode(t, memory.tree, 0, 0)
269 right := treeReadNode(t, memory.tree, 0, 1)
270 merge := summarizer.Tasks()[2]
271 require.Equal(t, []string{left.Text, right.Text}, merge.Input)
272 require.Equal(t, "<chat>\n0+1|"+left.Text+"\n1+1|"+right.Text+"\n</chat>", merge.Context)
273 require.True(t, treeIsBuilt(t, memory.tree, 1, 0))
276 func TestMemoryRestartRestoresTheQueue(t *testing.T) {
278 clock := newTestClock()
279 summarizer := newFakeSummarizer()
280 summarizer.SetFailing(true)
281 memory, err := openMemory(t.Context(), root, clock.Now, summarizer, discardLog())
282 require.NoError(t, err)
285 _, err = memory.Append("echo", memoryLongText(n))
286 require.NoError(t, err)
289 require.NoError(t, memory.Wait(t.Context()))
290 require.NoError(t, memory.Close())
292 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
293 require.NoError(t, err)
296 require.NoError(t, memory.Close())
299 require.NoError(t, memory.Wait(t.Context()))
300 require.True(t, treeIsBuilt(t, memory.tree, 2, 0))
303 func TestMemoryBuildsFreeParents(t *testing.T) {
304 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
305 require.NoError(t, err)
308 require.NoError(t, memory.Close())
311 for _, text := range []string{"a", "b", "c", "d"} {
312 _, err = memory.Append("user", text)
313 require.NoError(t, err)
316 node := treeReadNode(t, memory.tree, 2, 0)
317 require.Equal(t, "user: a\nuser: b\nuser: c\nuser: d", node.Text)
320 func TestMemoryKeepsALongMessageOutOfTheView(t *testing.T) {
321 summarizer := newFakeSummarizer()
322 summarizer.SetFailing(true)
323 memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
324 require.NoError(t, err)
327 require.NoError(t, memory.Close())
330 _, err = memory.Append("echo", memoryLongText(0))
331 require.NoError(t, err)
332 require.NoError(t, memory.Wait(t.Context()))
333 require.Equal(t, "<chat>\n0+1|"+viewPending+"\n</chat>", memory.View())
335 summarizer.SetFailing(false)
337 _, err = memory.Append("user", "again")
338 require.NoError(t, err)
339 require.NoError(t, memory.Wait(t.Context()))
341 task, err := memory.task(NodeKey{0, 0})
342 require.NoError(t, err)
343 require.Equal(t, "<chat>\n0+1|"+fakeLine(task)+"\n1+1|user: again\n</chat>", memory.View())
346 func TestMemoryCoversMessagesAfterTheSavedView(t *testing.T) {
348 clock := newTestClock()
349 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
350 require.NoError(t, err)
352 _, err = memory.Append("user", "a")
353 require.NoError(t, err)
355 _, err = memory.log.Append("reply", "b")
356 require.NoError(t, err)
357 require.NoError(t, memory.Close())
359 memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
360 require.NoError(t, err)
363 require.NoError(t, memory.Close())
366 require.Equal(t, "<chat>\n0+1|user: a\n1+1|reply: b\n</chat>", memory.View())
369 func TestMemoryRefusesToRebuildAMissingView(t *testing.T) {
371 clock := newTestClock()
372 memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
373 require.NoError(t, err)
375 _, err = memory.Append("user", "a")
376 require.NoError(t, err)
377 require.NoError(t, memory.Close())
378 require.NoError(t, os.Remove(filepath.Join(root, "view.json")))
380 _, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
381 require.ErrorIs(t, err, errMemoryNoView)
384 // memoryLongText returns a text that is too long for its own node.
385 func memoryLongText(n int) string {
386 return fmt.Sprintf("long %d ", n) + strings.Repeat("x", nodeBytes)