fix: do terminal emulation in reconnecting pty tests (#9114)

It looks like it is possible for screen to use control sequences instead
of literal newlines which fails the tests.

This reuses the existing readUntil function used in other pty tests.
This commit is contained in:
Asher
2023-08-16 13:02:03 -08:00
committed by GitHub
parent 74999305b6
commit 02ee724d9f
4 changed files with 97 additions and 109 deletions

70
testutil/pty.go Normal file
View File

@ -0,0 +1,70 @@
package testutil
import (
"context"
"io"
"strings"
"testing"
"github.com/hinshun/vt10x"
)
// ReadUntilString emulates a terminal and reads one byte at a time until we
// either see the string we want, or the context expires. The PTY must be sized
// to 80x80 or there could be unexpected results.
func ReadUntilString(ctx context.Context, t *testing.T, want string, r io.Reader) error {
return ReadUntil(ctx, t, r, func(line string) bool {
return strings.TrimSpace(line) == want
})
}
// ReadUntil emulates a terminal and reads one byte at a time until the matcher
// returns true or the context expires. If the matcher is nil, read until EOF.
// The PTY must be sized to 80x80 or there could be unexpected results.
func ReadUntil(ctx context.Context, t *testing.T, r io.Reader, matcher func(line string) bool) error {
// output can contain virtual terminal sequences, so we need to parse these
// to correctly interpret getting what we want.
term := vt10x.New(vt10x.WithSize(80, 80))
readErrs := make(chan error, 1)
defer func() {
// Dump the terminal contents since they can be helpful for debugging, but
// skip empty lines since much of the terminal will usually be blank.
got := term.String()
lines := strings.Split(got, "\n")
for _, line := range lines {
if strings.TrimSpace(line) != "" {
t.Logf("got: %v", line)
}
}
}()
for {
b := make([]byte, 1)
go func() {
_, err := r.Read(b)
readErrs <- err
}()
select {
case err := <-readErrs:
if err != nil {
return err
}
_, err = term.Write(b)
if err != nil {
return err
}
case <-ctx.Done():
return ctx.Err()
}
if matcher == nil {
// A nil matcher means to read until EOF.
continue
}
got := term.String()
lines := strings.Split(got, "\n")
for _, line := range lines {
if matcher(line) {
return nil
}
}
}
}