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.
148 lines
3.1 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|