package starssh import ( "bufio" "bytes" "io" "os" "testing" "time" ) type terminalInputProvider struct { io.Reader source io.Reader } func (p terminalInputProvider) TerminalInputSource() io.Reader { return p.source } type prefixedReadCloser struct { io.Reader io.Closer } func TestPrepareTerminalInputReaderBufioReaderPreservesBufferedBytes(t *testing.T) { reader, writer, err := os.Pipe() if err != nil { t.Fatalf("create pipe: %v", err) } defer reader.Close() if _, err := writer.Write([]byte("hello world")); err != nil { writer.Close() t.Fatalf("write pipe: %v", err) } if err := writer.Close(); err != nil { t.Fatalf("close writer: %v", err) } buffered := bufio.NewReaderSize(reader, 4) peeked, err := buffered.Peek(5) if err != nil { t.Fatalf("prime buffer: %v", err) } if string(peeked) != "hello" { t.Fatalf("unexpected buffered prefix: %q", string(peeked)) } prepared, cancel, cancelable, err := prepareTerminalInputReader(buffered) if err != nil { t.Fatalf("prepare input: %v", err) } defer cancel() if !cancelable { t.Fatal("expected cancelable reader") } data, err := io.ReadAll(prepared) if err != nil { t.Fatalf("read prepared input: %v", err) } if string(data) != "hello world" { t.Fatalf("unexpected prepared input: %q", string(data)) } } func TestPrepareTerminalInputReaderBufioReadWriterPreservesBufferedBytes(t *testing.T) { reader, writer, err := os.Pipe() if err != nil { t.Fatalf("create pipe: %v", err) } defer reader.Close() if _, err := writer.Write([]byte("buffered payload")); err != nil { writer.Close() t.Fatalf("write pipe: %v", err) } if err := writer.Close(); err != nil { t.Fatalf("close writer: %v", err) } readWriter := bufio.NewReadWriter(bufio.NewReaderSize(reader, 8), bufio.NewWriter(io.Discard)) if _, err := readWriter.Reader.Peek(8); err != nil { t.Fatalf("prime readwriter buffer: %v", err) } prepared, cancel, cancelable, err := prepareTerminalInputReader(readWriter) if err != nil { t.Fatalf("prepare readwriter input: %v", err) } defer cancel() if !cancelable { t.Fatal("expected cancelable readwriter input") } data, err := io.ReadAll(prepared) if err != nil { t.Fatalf("read prepared readwriter input: %v", err) } if string(data) != "buffered payload" { t.Fatalf("unexpected prepared readwriter input: %q", string(data)) } } func TestPrepareTerminalInputReaderBufioReaderFallbackKeepsData(t *testing.T) { buffered := bufio.NewReader(bytes.NewBufferString("abc123")) if _, err := buffered.Peek(3); err != nil { t.Fatalf("prime buffer: %v", err) } prepared, cancel, cancelable, err := prepareTerminalInputReader(buffered) if err != nil { t.Fatalf("prepare fallback input: %v", err) } defer cancel() if cancelable { t.Fatal("expected non-cancelable reader") } data, err := io.ReadAll(prepared) if err != nil { t.Fatalf("read fallback input: %v", err) } if string(data) != "abc123" { t.Fatalf("unexpected fallback input: %q", string(data)) } } func TestPrepareTerminalInputReaderProviderPrefersExplicitSource(t *testing.T) { reader, writer, err := os.Pipe() if err != nil { t.Fatalf("create pipe: %v", err) } defer reader.Close() if _, err := writer.Write([]byte("provider data")); err != nil { writer.Close() t.Fatalf("write pipe: %v", err) } if err := writer.Close(); err != nil { t.Fatalf("close writer: %v", err) } buffered := bufio.NewReader(reader) prefix, err := buffered.Peek(len("provider data")) if err != nil { t.Fatalf("prime buffer: %v", err) } source := prefixedReadCloser{ Reader: io.MultiReader(bytes.NewReader(append([]byte(nil), prefix...)), reader), Closer: reader, } provider := terminalInputProvider{ Reader: buffered, source: source, } prepared, cancel, cancelable, err := prepareTerminalInputReader(provider) if err != nil { t.Fatalf("prepare provider input: %v", err) } defer cancel() if !cancelable { t.Fatal("expected provider-backed input to be cancelable") } data, err := io.ReadAll(prepared) if err != nil { t.Fatalf("read provider input: %v", err) } if string(data) != "provider data" { t.Fatalf("unexpected provider input: %q", string(data)) } } func TestPrepareTerminalInputReaderProviderCancelUnblocksRead(t *testing.T) { reader, writer := io.Pipe() defer reader.Close() defer writer.Close() buffered := bufio.NewReader(reader) provider := terminalInputProvider{ Reader: buffered, source: reader, } prepared, cancel, cancelable, err := prepareTerminalInputReader(provider) if err != nil { t.Fatalf("prepare input: %v", err) } if !cancelable { t.Fatal("expected provider-backed input to be cancelable") } done := make(chan error, 1) go func() { buf := make([]byte, 1) _, readErr := prepared.Read(buf) done <- readErr }() time.Sleep(50 * time.Millisecond) cancel() select { case readErr := <-done: if readErr == nil { t.Fatal("expected cancel to interrupt blocking read") } case <-time.After(time.Second): t.Fatal("blocking read did not unblock after cancel") } } func TestPrepareTerminalInputReaderBufioReaderCancelUnblocksRead(t *testing.T) { reader, writer := io.Pipe() defer reader.Close() defer writer.Close() buffered := bufio.NewReader(reader) prepared, cancel, cancelable, err := prepareTerminalInputReader(buffered) if err != nil { t.Fatalf("prepare input: %v", err) } if !cancelable { t.Fatal("expected bufio reader to be cancelable") } done := make(chan error, 1) go func() { buf := make([]byte, 1) _, readErr := prepared.Read(buf) done <- readErr }() time.Sleep(50 * time.Millisecond) cancel() select { case readErr := <-done: if readErr == nil { t.Fatal("expected cancel to interrupt blocking read") } case <-time.After(time.Second): t.Fatal("blocking bufio reader did not unblock after cancel") } } func TestPrepareTerminalInputReaderBufioReadWriterCancelUnblocksRead(t *testing.T) { reader, writer := io.Pipe() defer reader.Close() defer writer.Close() readWriter := bufio.NewReadWriter(bufio.NewReader(reader), bufio.NewWriter(io.Discard)) prepared, cancel, cancelable, err := prepareTerminalInputReader(readWriter) if err != nil { t.Fatalf("prepare readwriter input: %v", err) } if !cancelable { t.Fatal("expected bufio readwriter to be cancelable") } done := make(chan error, 1) go func() { buf := make([]byte, 1) _, readErr := prepared.Read(buf) done <- readErr }() time.Sleep(50 * time.Millisecond) cancel() select { case readErr := <-done: if readErr == nil { t.Fatal("expected cancel to interrupt blocking read") } case <-time.After(time.Second): t.Fatal("blocking readwriter input did not unblock after cancel") } }