+// memoryReplayRequireRestart closes memory, opens it again, and checks that the
+// views and view.json stay the same byte for byte.
+func memoryReplayRequireRestart(t *testing.T, memory *Memory, root string, clock *TestClock) *Memory {
+ t.Helper()
+
+ require.NoError(t, memory.Wait(t.Context()))
+
+ view := memory.View()
+ context := memory.Context(0, memory.log.Len())
+ saved, err := os.ReadFile(filepath.Join(root, "view.json"))
+ require.NoError(t, err)
+ require.NoError(t, memory.Close())
+
+ memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
+ require.NoError(t, err)
+ require.NoError(t, memory.Wait(t.Context()))
+ require.Equal(t, view, memory.View())
+ require.Equal(t, context, memory.Context(0, memory.log.Len()))
+
+ loaded, err := os.ReadFile(filepath.Join(root, "view.json"))
+ require.NoError(t, err)
+ require.Equal(t, saved, loaded)
+
+ return memory
+}
+
+// memoryReplayMessage returns the kind and the text of message id. Most
+// messages are tool calls and their output. Users write short lines. One output
+// in a hundred is over 30 KB.
+func memoryReplayMessage(random *rand.Rand, id int) (string, string) {
+ roll := random.IntN(100)
+ switch {
+ case roll < 5:
+ return "user", memoryReplayText(id, 10+random.IntN(200))
+ case roll < 10:
+ return "reply", memoryReplayText(id, 100+random.IntN(1500))
+ case roll < 12:
+ return "note", memoryReplayText(id, 50+random.IntN(600))
+ case roll < 13:
+ return "work", memoryReplayText(id, 300+random.IntN(3000))
+ case roll < 56:
+ return "tool", memoryReplayText(id, 60+random.IntN(400))
+ case roll < 57:
+ return "echo", memoryReplayText(id, 30000+random.IntN(30000))
+ default:
+ return "echo", memoryReplayText(id, 20+random.IntN(4000))
+ }
+}
+
+// memoryReplayViewLines returns the number of lines in the chat view of memory.
+func memoryReplayViewLines(memory *Memory) int {
+ memory.mutex.Lock()
+ defer memory.mutex.Unlock()
+
+ return len(memory.chat.lines)
+}
+
+// memoryReplayRequireViews checks the views after a new message, and with full
+// compares them with the tree. Each view is at most its high limit, and a batch
+// merges the chat view to its low limit. The leaf of the new message can be
+// built after the append, so the sizes count it as the pending mark, as the
+// append did. It returns the size of the chat view and if a batch merged it,
+// from the number of lines before the message.
+func memoryReplayRequireViews(t *testing.T, memory *Memory, lines int, long []bool, full bool) (int, bool) {
+ t.Helper()
+
+ memory.mutex.Lock()
+ defer memory.mutex.Unlock()
+
+ size := memoryReplayAppendSize(memory.chat)
+ require.LessOrEqual(t, size, viewChatHigh)
+ require.LessOrEqual(t, memoryReplayAppendSize(memory.cview), viewCompactionHigh)
+
+ merged := len(memory.chat.lines) < lines
+ if merged {
+ require.LessOrEqual(t, size, viewChatLow)
+ }
+
+ memoryReplayRequireOnlySummaries(t, memory, long, full)