+// replayRequireRestart closes memory, opens it again, and checks that the views
+// and view.json stay the same byte for byte.
+func replayRequireRestart(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
+}
+
+// replayMessage 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 replayMessage(random *rand.Rand, id int) (string, string) {
+ roll := random.IntN(100)
+ switch {
+ case roll < 5:
+ return "user", replayText(id, 10+random.IntN(200))
+ case roll < 10:
+ return "reply", replayText(id, 100+random.IntN(1500))
+ case roll < 12:
+ return "note", replayText(id, 50+random.IntN(600))
+ case roll < 13:
+ return "work", replayText(id, 300+random.IntN(3000))
+ case roll < 56:
+ return "tool", replayText(id, 60+random.IntN(400))
+ case roll < 57:
+ return "echo", replayText(id, 30000+random.IntN(30000))
+ default:
+ return "echo", replayText(id, 20+random.IntN(4000))
+ }
+}
+
+// replayViewLines returns the number of lines in the chat view of memory.
+func replayViewLines(memory *Memory) int {
+ memory.mutex.Lock()
+ defer memory.mutex.Unlock()
+
+ return len(memory.chat.lines)
+}
+
+// replayRequireViews 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 replayRequireViews(t *testing.T, memory *Memory, lines int, long []bool, full bool) (int, bool) {
+ t.Helper()
+
+ memory.mutex.Lock()
+ defer memory.mutex.Unlock()
+
+ size := replayAppendSize(memory.chat)
+ require.LessOrEqual(t, size, viewChatHigh)
+ require.LessOrEqual(t, replayAppendSize(memory.cview), viewCompactionHigh)
+
+ merged := len(memory.chat.lines) < lines
+ if merged {
+ require.LessOrEqual(t, size, viewChatLow)
+ }
+
+ replayRequireOnlySummaries(t, memory, long, full)