Files
patterm/internal/app/ring_test.go
Harry Bayliss 53f06b604f Normalize whitespace in grid get_process_output to save tokens
Grid snapshots pad every row to the full terminal width and leave the
bottom of the screen blank, so MCP grid reads carried a lot of dead
whitespace. Add normalizeGridText (CRLF/lone-CR to LF, right-trim each
line, collapse blank runs to a single blank, drop leading/trailing
blanks) and apply it to the grid branch of GetProcessOutput only.
Stream output, raw output, and WaitForPattern matching are untouched.

Resolves the terminal-read newline/token-waste TODO item.
2026-05-25 12:33:59 +01:00

148 lines
3.1 KiB
Go

package app
import (
"bytes"
"testing"
)
func newRingChild() *Child {
return newChildEntry("id", "name", KindCommand, nil, nil, "", "", "")
}
func TestRingShortWrite(t *testing.T) {
c := newRingChild()
c.recordWrite([]byte("hello"))
b, end := c.StreamRead(0)
if end != 5 {
t.Fatalf("end=%d want 5", end)
}
if string(b) != "hello" {
t.Fatalf("got %q want %q", b, "hello")
}
// Read past the head returns nil, same end.
b, end = c.StreamRead(5)
if end != 5 || b != nil {
t.Fatalf("re-read: end=%d b=%v", end, b)
}
}
func TestRingIncrementalRead(t *testing.T) {
c := newRingChild()
c.recordWrite([]byte("abc"))
c.recordWrite([]byte("def"))
b, end := c.StreamRead(3)
if end != 6 || string(b) != "def" {
t.Fatalf("got %q end=%d", b, end)
}
}
func TestRingWrapAround(t *testing.T) {
c := newRingChild()
// Write more than ringCap to force wrap. Use a pattern we can
// verify: bytes equal to (i mod 256).
total := ringCap + 1000
src := make([]byte, total)
for i := range src {
src[i] = byte(i)
}
// Write in pieces to exercise the wrap copy in recordWrite.
for i := 0; i < total; i += 7777 {
end := i + 7777
if end > total {
end = total
}
c.recordWrite(src[i:end])
}
// The freshest ringCap bytes should be readable.
b, head := c.StreamRead(0)
if head != int64(total) {
t.Fatalf("head=%d want %d", head, total)
}
if len(b) != ringCap {
t.Fatalf("len(b)=%d want %d", len(b), ringCap)
}
want := src[total-ringCap:]
if !bytes.Equal(b, want) {
t.Fatalf("ring contents diverge from source tail")
}
}
func TestRingChunkLargerThanCap(t *testing.T) {
c := newRingChild()
src := make([]byte, ringCap+500)
for i := range src {
src[i] = byte(i + 1)
}
c.recordWrite(src)
b, head := c.StreamRead(0)
if head != int64(len(src)) {
t.Fatalf("head=%d want %d", head, len(src))
}
if len(b) != ringCap {
t.Fatalf("len(b)=%d want %d", len(b), ringCap)
}
if !bytes.Equal(b, src[500:]) {
t.Fatalf("ring tail mismatch")
}
}
func TestStripANSIBytesEquivalence(t *testing.T) {
cases := []string{
"hello world",
"\x1b[31mred\x1b[0m text",
"line1\nline2\r\nline3",
"bell\x07ish",
"weird \x1bA escape",
"truncated \x1b[1;",
"",
}
for _, in := range cases {
want := stripANSI(in)
got := string(stripANSIBytes(nil, []byte(in)))
if got != want {
t.Errorf("stripANSIBytes(%q) = %q want %q", in, got, want)
}
}
}
func TestNormalizeGridText(t *testing.T) {
cases := []struct {
name string
in string
want string
}{
{
name: "line endings",
in: "one\r\ntwo\rthree",
want: "one\ntwo\nthree",
},
{
name: "trailing whitespace",
in: "one \ntwo\t\t\nthree",
want: "one\ntwo\nthree",
},
{
name: "collapse blank runs",
in: "one\n\n\n two\n \n\t\nthree",
want: "one\n\n two\n\nthree",
},
{
name: "trim leading and trailing blanks",
in: "\n \n\t\none\n\n",
want: "one",
},
{
name: "already clean",
in: "one\n\ntwo\nthree",
want: "one\n\ntwo\nthree",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := normalizeGridText(tc.in); got != tc.want {
t.Fatalf("normalizeGridText(%q) = %q, want %q", tc.in, got, tc.want)
}
})
}
}