X-Git-Url: https://git.giacomolli.uk/onechat/blobdiff_plain/a5c83e92d7feea2e104b5515043a76c8c4c56c60..0979625154094df73577d6e2be2d0e9a0a87306c:/memory_test.go?ds=sidebyside diff --git a/memory_test.go b/memory_test.go index 6713f6d..6f76e5b 100644 --- a/memory_test.go +++ b/memory_test.go @@ -18,12 +18,12 @@ import ( ) const ( - // replayMessages is the length of the synthetic log. - replayMessages = 100000 + // memoryReplayMessages is the length of the synthetic log. + memoryReplayMessages = 100000 - // replayShortMessages is the length of the synthetic log with -short. - // It is long enough for batches of the chat view. - replayShortMessages = 5000 + // memoryReplayShortMessages is the length of the synthetic log with + // -short. It is long enough for batches of the chat view. + memoryReplayShortMessages = 5000 ) func TestMemoryRestartLoadsTheSameView(t *testing.T) { @@ -143,6 +143,7 @@ func TestMemoryRunsAtMostEightBuilds(t *testing.T) { slices.SortFunc(running, func(a, b NodeKey) int { return a.index - b.index }) + require.Equal(t, []NodeKey{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}, {0, 5}, {0, 6}, {0, 7}}, running) require.Eventually(t, func() bool { @@ -482,21 +483,22 @@ func TestQueuedAppendRefusesAnInvalidReceipt(t *testing.T) { cause string }{ { - name: "receipt points to different text", + name: "refuses when receipt points to different text", id: 0, cause: "does not match", }, { - name: "receipt skips a log position", + name: "refuses when receipt skips a log position", id: 2, cause: "beyond the log", }, { - name: "receipt has a negative position", + name: "refuses when receipt has a negative position", id: -1, cause: "read queued message", }, } + for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { _, err := memory.AppendOnce("user", "Other text.", &tc.id, func(int) error { @@ -518,9 +520,9 @@ func TestQueuedAppendRefusesAnInvalidReceipt(t *testing.T) { // log is shorter, and the test still requires a batch of the chat view. func TestReplaySyntheticLog(t *testing.T) { start := time.Now() - messages := replayMessages + messages := memoryReplayMessages if testing.Short() { - messages = replayShortMessages + messages = memoryReplayShortMessages } root := t.TempDir() @@ -539,17 +541,17 @@ func TestReplaySyntheticLog(t *testing.T) { long := make([]bool, messages) for id := range messages { if id == messages/2 { - memory = replayRequireRestart(t, memory, root, clock) + memory = memoryReplayRequireRestart(t, memory, root, clock) } clock.now = clock.now.Add(time.Minute) - kind, text := replayMessage(random, id) + kind, text := memoryReplayMessage(random, id) long[id] = len(kind)+len(": ")+len(text) > nodeBytes - lines := replayViewLines(memory) + lines := memoryReplayViewLines(memory) _, err = memory.Append(kind, text) require.NoError(t, err) - size, merged := replayRequireViews(t, memory, lines, long, id%1000 == 0) + size, merged := memoryReplayRequireViews(t, memory, lines, long, id%1000 == 0) if merged { batches++ low = min(low, size) @@ -558,7 +560,7 @@ func TestReplaySyntheticLog(t *testing.T) { require.NoError(t, memory.Wait(t.Context())) } - memory = replayRequireRestart(t, memory, root, clock) + memory = memoryReplayRequireRestart(t, memory, root, clock) require.Equal(t, messages, memory.log.Len()) require.Positive(t, batches) @@ -580,9 +582,9 @@ func memoryLongText(n int) string { return fmt.Sprintf("long %d ", n) + strings.Repeat("x", nodeBytes) } -// 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 { +// 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())) @@ -606,85 +608,85 @@ func replayRequireRestart(t *testing.T, memory *Memory, root string, clock *Test 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) { +// 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", replayText(id, 10+random.IntN(200)) + return "user", memoryReplayText(id, 10+random.IntN(200)) case roll < 10: - return "reply", replayText(id, 100+random.IntN(1500)) + return "reply", memoryReplayText(id, 100+random.IntN(1500)) case roll < 12: - return "note", replayText(id, 50+random.IntN(600)) + return "note", memoryReplayText(id, 50+random.IntN(600)) case roll < 13: - return "work", replayText(id, 300+random.IntN(3000)) + return "work", memoryReplayText(id, 300+random.IntN(3000)) case roll < 56: - return "tool", replayText(id, 60+random.IntN(400)) + return "tool", memoryReplayText(id, 60+random.IntN(400)) case roll < 57: - return "echo", replayText(id, 30000+random.IntN(30000)) + return "echo", memoryReplayText(id, 30000+random.IntN(30000)) default: - return "echo", replayText(id, 20+random.IntN(4000)) + return "echo", memoryReplayText(id, 20+random.IntN(4000)) } } -// replayViewLines returns the number of lines in the chat view of memory. -func replayViewLines(memory *Memory) int { +// 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) } -// replayRequireViews checks the views after a new message, and with full +// 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 replayRequireViews(t *testing.T, memory *Memory, lines int, long []bool, full bool) (int, bool) { +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 := replayAppendSize(memory.chat) + size := memoryReplayAppendSize(memory.chat) require.LessOrEqual(t, size, viewChatHigh) - require.LessOrEqual(t, replayAppendSize(memory.cview), viewCompactionHigh) + require.LessOrEqual(t, memoryReplayAppendSize(memory.cview), viewCompactionHigh) merged := len(memory.chat.lines) < lines if merged { require.LessOrEqual(t, size, viewChatLow) } - replayRequireOnlySummaries(t, memory, long, full) + memoryReplayRequireOnlySummaries(t, memory, long, full) return size, merged } -// replayText returns a text of about size bytes that starts with its message ID -// and holds some non-ASCII characters. -func replayText(id, size int) string { +// memoryReplayText returns a text of about size bytes that starts with its +// message ID and holds some non-ASCII characters. +func memoryReplayText(id, size int) string { head := fmt.Sprintf("m%d ", id) words := "ação data line café " return head + strings.Repeat(words, max(1, (size-len(head))/len(words))) } -// replayAppendSize returns the size of view with its last line as the pending -// mark. -func replayAppendSize(view *View) int { +// memoryReplayAppendSize returns the size of view with its last line as the +// pending mark. +func memoryReplayAppendSize(view *View) int { last := view.lines[len(view.lines)-1] return view.Size() - view.lineSize(last) + len(viewPending) } -// replayRequireOnlySummaries checks each line of the chat view as the model -// reads it. A built line holds its node, a long message has a summary of the -// fake summarizer and not its text, and an unbuilt line holds only the pending -// mark. With full, it also checks that the view keeps the node of each built -// line. The caller holds the lock of memory. -func replayRequireOnlySummaries(t *testing.T, memory *Memory, long []bool, full bool) { +// memoryReplayRequireOnlySummaries checks each line of the chat view as the +// model reads it. A built line holds its node, a long message has a summary of +// the fake summarizer and not its text, and an unbuilt line holds only the +// pending mark. With full, it also checks that the view keeps the node of each +// built line. The caller holds the lock of memory. +func memoryReplayRequireOnlySummaries(t *testing.T, memory *Memory, long []bool, full bool) { t.Helper() rendered := strings.Split(memory.chat.Render(), "\n")