package main
import (
+ "context"
"fmt"
"maps"
"os"
func TestMemoryRestartLoadsTheSameView(t *testing.T) {
root := t.TempDir()
clock := newTestClock()
- memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
memory.chat.high = 200
memory.chat.low = 100
+
memory.cview.high = 80
memory.cview.low = 40
for n := range 100 {
require.Less(t, len(memory.chat.lines), 100)
require.NoError(t, memory.Close())
- memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
defer func() {
func TestMemoryOpensAfterACrashBeforeTheViewSave(t *testing.T) {
root := t.TempDir()
clock := newTestClock()
- memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
_, err = memory.Append("user", "first")
// The log and the tree hold message 1, but the view does not.
require.NoError(t, os.WriteFile(filepath.Join(root, "view.json"), saved, 0o600))
- memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
require.Equal(t, view, memory.View())
require.NoError(t, memory.Close())
}
func TestMemoryKeepsTheCompactionViewCoarser(t *testing.T) {
- memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer())
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
defer func() {
memory.chat.high = 200
memory.chat.low = 100
+
memory.cview.high = 80
memory.cview.low = 40
for n := range 64 {
func TestMemoryRunsAtMostEightBuilds(t *testing.T) {
summarizer := newFakeSummarizer()
summarizer.gate = make(chan struct{})
- memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
require.NoError(t, err)
defer func() {
}
memory.mutex.Lock()
+
running := slices.Collect(maps.Keys(memory.queue.running))
memory.mutex.Unlock()
slices.SortFunc(running, func(a, b NodeKey) int {
}
}
+func TestMemorySettleWaitsForThePendingLines(t *testing.T) {
+ summarizer := newFakeSummarizer()
+ summarizer.gate = make(chan struct{})
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
+ require.NoError(t, err)
+
+ defer func() {
+ require.NoError(t, memory.Close())
+ }()
+
+ _, err = memory.Append("echo", memoryLongText(0))
+ require.NoError(t, err)
+ require.Contains(t, memory.View(), viewPending)
+
+ settled := make(chan error, 1)
+ go func() {
+ settled <- memory.Settle(t.Context())
+ }()
+
+ select {
+ case err = <-settled:
+ require.Fail(t, "Settle returned before the build", "error: %v", err)
+ case <-time.After(50 * time.Millisecond):
+ }
+
+ close(summarizer.gate)
+ require.NoError(t, <-settled)
+ require.NotContains(t, memory.View(), viewPending)
+}
+
+func TestMemorySettleFailsWhileABuildFailed(t *testing.T) {
+ summarizer := newFakeSummarizer()
+ summarizer.SetFailing(true)
+
+ logs := &SyncBuffer{}
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, daemonTestLogger(logs))
+ require.NoError(t, err)
+
+ defer func() {
+ require.NoError(t, memory.Close())
+ }()
+
+ _, err = memory.Append("echo", memoryLongText(0))
+ require.NoError(t, err)
+ require.ErrorIs(t, memory.Settle(t.Context()), errMemoryUnsettled)
+ require.Equal(t, "<4>summary failed level=0 index=0: fake failure\n", logs.String())
+
+ summarizer.SetFailing(false)
+
+ _, err = memory.Append("user", "retry")
+ require.NoError(t, err)
+ require.NoError(t, memory.Settle(t.Context()))
+}
+
+func TestMemorySettleEndsWithItsContext(t *testing.T) {
+ summarizer := newFakeSummarizer()
+ summarizer.gate = make(chan struct{})
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
+ require.NoError(t, err)
+
+ defer func() {
+ require.NoError(t, memory.Close())
+ }()
+
+ _, err = memory.Append("echo", memoryLongText(0))
+ require.NoError(t, err)
+
+ ctx, cancel := context.WithTimeout(t.Context(), 50*time.Millisecond)
+ defer cancel()
+
+ require.ErrorIs(t, memory.Settle(ctx), context.DeadlineExceeded)
+}
+
func TestMemoryStartsALeafAfterFewerThanEightUnbuilt(t *testing.T) {
summarizer := newFakeSummarizer()
summarizer.SetFailing(true)
- memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
+
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
require.NoError(t, err)
defer func() {
}
require.NoError(t, memory.Wait(t.Context()))
+
for _, task := range summarizer.Tasks() {
require.Less(t, task.Index, 8)
}
func TestMemoryMergesAfterBothHalves(t *testing.T) {
summarizer := newFakeSummarizer()
- memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
require.NoError(t, err)
defer func() {
clock := newTestClock()
summarizer := newFakeSummarizer()
summarizer.SetFailing(true)
- memory, err := openMemory(t.Context(), root, clock.Now, summarizer)
+
+ memory, err := openMemory(t.Context(), root, clock.Now, summarizer, discardLog())
require.NoError(t, err)
for n := range 4 {
require.NoError(t, memory.Wait(t.Context()))
require.NoError(t, memory.Close())
- memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
defer func() {
}
func TestMemoryBuildsFreeParents(t *testing.T) {
- memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer())
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
defer func() {
func TestMemoryKeepsALongMessageOutOfTheView(t *testing.T) {
summarizer := newFakeSummarizer()
summarizer.SetFailing(true)
- memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer)
+
+ memory, err := openMemory(t.Context(), t.TempDir(), newTestClock().Now, summarizer, discardLog())
require.NoError(t, err)
defer func() {
func TestMemoryCoversMessagesAfterTheSavedView(t *testing.T) {
root := t.TempDir()
clock := newTestClock()
- memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
_, err = memory.Append("user", "a")
require.NoError(t, err)
require.NoError(t, memory.Close())
- memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
defer func() {
func TestMemoryRefusesToRebuildAMissingView(t *testing.T) {
root := t.TempDir()
clock := newTestClock()
- memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ memory, err := openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.NoError(t, err)
_, err = memory.Append("user", "a")
require.NoError(t, memory.Close())
require.NoError(t, os.Remove(filepath.Join(root, "view.json")))
- _, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer())
+ _, err = openMemory(t.Context(), root, clock.Now, newFakeSummarizer(), discardLog())
require.ErrorIs(t, err, errMemoryNoView)
}