// Trimming must release old descriptors without clearing surviving text. // Daemon E2E cannot inspect the TUI's retained memory. package tui import ( "fmt" "slices" "strings" "testing" ) func TestTrimSettledLinesReleasesDescriptors(t *testing.T) { for _, test := range []struct { name string rows []string kept int }{ {name: "row cap", rows: slices.Repeat([]string{"│ short"}, MaxSettledLines+3), kept: MaxSettledLines}, {name: "byte cap", rows: slices.Repeat([]string{"│ " + strings.Repeat("x", 512)}, 600), kept: MaxSettledLinesBytes / (len("│ ") + 512 + 3)}, {name: "all rows", rows: []string{"small", strings.Repeat("x", MaxSettledLinesBytes+1)}, kept: 0}, } { t.Run(test.name, func(t *testing.T) { // Fixed-width suffixes make survivor ordering observable. for i := range test.rows { test.rows[i] += fmt.Sprintf("%03d", i) } old := test.rows want := slices.Clone(old[len(old)-test.kept:]) m := ChatModel{Follow: true, settledLines: old, settledLinesBytes: rowBytes(old)} m.trimSettledLines() if !slices.Equal(m.settledLines, want) || m.settledLinesBytes != rowBytes(want) || m.droppedSettledLines != len(old)-test.kept { t.Fatalf("survivors=%d bytes=%d dropped=%d", len(m.settledLines), m.settledLinesBytes, m.droppedSettledLines) } for i, row := range old { if row != "" { t.Fatalf("old descriptor %d still holds text", i) } } if len(want) > 0 { old[len(old)-1] = "changed old slice" if !slices.Equal(m.settledLines, want) { t.Fatal("survivors share the old descriptor slice") } } }) } } func BenchmarkTrimSettledLinesFullDisplay(b *testing.B) { for _, width := range []int{80, 512} { b.Run(fmt.Sprintf("ascii%d", width), func(b *testing.B) { row := "│ " + strings.Repeat("x", width) count := min(MaxSettledLines, MaxSettledLinesBytes/len(row)) m := ChatModel{Follow: true, settledLines: slices.Repeat([]string{row}, count), settledLinesBytes: int64(count * len(row))} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { m.settledLines = append(m.settledLines, row) m.settledLinesBytes += int64(len(row)) m.trimSettledLines() } }) } }