You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

208 lines
5.3 KiB

package serial
import (
"context"
"errors"
"io"
"net"
"path/filepath"
"reflect"
"sync/atomic"
"testing"
"time"
)
func TestStreamSocketClientTransfersArbitraryBinaryBothDirections(t *testing.T) {
resolver := newFakeStreamResolver()
peers := make(chan net.Conn, 1)
manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{
Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) {
serialSide, peer := net.Pipe()
peers <- peer
return serialSide, nil
}),
})
if err != nil {
t.Fatal(err)
}
socketPath, stop := startStreamSocketServer(t, manager)
defer stop()
client := NewStreamSocketClient(socketPath, "sessiond-test")
transport, err := client.Open(context.Background(), "CH01")
if err != nil {
t.Fatalf("open stream client: %v", err)
}
defer transport.Close()
peer := <-peers
defer peer.Close()
tx := []byte{0x00, 0x3a, 0xff, '\n'}
txDone := make(chan error, 1)
go func() {
_, err := transport.Write(tx)
txDone <- err
}()
gotTX := make([]byte, len(tx))
if _, err := io.ReadFull(peer, gotTX); err != nil {
t.Fatal(err)
}
if err := <-txDone; err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gotTX, tx) {
t.Fatalf("tx = %v", gotTX)
}
rx := []byte{0xfe, 0x00, 0x7f}
rxDone := make(chan error, 1)
go func() {
_, err := peer.Write(rx)
rxDone <- err
}()
gotRX := make([]byte, len(rx))
if _, err := io.ReadFull(transport, gotRX); err != nil {
t.Fatal(err)
}
if err := <-rxDone; err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(gotRX, rx) {
t.Fatalf("rx = %v", gotRX)
}
}
func TestStreamSocketRejectsNonCanonicalAndDuplicateLease(t *testing.T) {
resolver := newFakeStreamResolver()
peers := make(chan net.Conn, 2)
manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{
Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) {
serialSide, peer := net.Pipe()
peers <- peer
return serialSide, nil
}),
})
if err != nil {
t.Fatal(err)
}
socketPath, stop := startStreamSocketServer(t, manager)
defer stop()
client := NewStreamSocketClient(socketPath, "sessiond-test")
if _, err := client.Open(context.Background(), "ch01"); err == nil {
t.Fatal("expected non-canonical channel rejection")
}
first, err := client.Open(context.Background(), "CH01")
if err != nil {
t.Fatal(err)
}
peer := <-peers
defer peer.Close()
if _, err := client.Open(context.Background(), "CH01"); !errors.Is(err, ErrStreamLeaseBusy) {
t.Fatalf("duplicate lease error = %v", err)
}
if err := first.Close(); err != nil {
t.Fatal(err)
}
deadline := time.Now().Add(time.Second)
for resolver.releaseCount("CH01") == 0 && time.Now().Before(deadline) {
time.Sleep(time.Millisecond)
}
if resolver.releaseCount("CH01") != 1 {
t.Fatal("lease was not released after client disconnect")
}
}
func TestStreamSocketStatusReportsBoundedLeaseCounters(t *testing.T) {
resolver := newFakeStreamResolver()
manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{
Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) {
serialSide, peer := net.Pipe()
go peer.Close()
return serialSide, nil
}),
})
if err != nil {
t.Fatal(err)
}
socketPath, stop := startStreamSocketServer(t, manager)
defer stop()
client := NewStreamSocketClient(socketPath, "sessiond-health")
status, err := client.Status(context.Background())
if err != nil {
t.Fatalf("status: %v", err)
}
if status.ActiveLeases != 0 || status.RXBytes != 0 || status.TXBytes != 0 {
t.Fatalf("status = %#v", status)
}
}
func TestStreamSocketSendsBreakOnlyForActiveLease(t *testing.T) {
resolver := newFakeStreamResolver()
peers := make(chan net.Conn, 1)
var device *breakableStreamDevice
manager, err := NewStreamLeaseManager(resolver, StreamLeaseOptions{
Opener: streamOpenFunc(func(string) (io.ReadWriteCloser, error) {
serialSide, peer := net.Pipe()
device = &breakableStreamDevice{Conn: serialSide}
peers <- peer
return device, nil
}),
})
if err != nil {
t.Fatal(err)
}
socketPath, stop := startStreamSocketServer(t, manager)
defer stop()
client := NewStreamSocketClient(socketPath, "sessiond-break-test")
transport, err := client.Open(context.Background(), "CH01")
if err != nil {
t.Fatal(err)
}
defer transport.Close()
peer := <-peers
defer peer.Close()
sender, ok := transport.(interface{ SendBreak(context.Context) error })
if !ok {
t.Fatalf("transport %T does not expose SendBreak", transport)
}
if err := sender.SendBreak(context.Background()); err != nil {
t.Fatalf("SendBreak() error = %v", err)
}
if got := device.breaks.Load(); got != 1 {
t.Fatalf("break count = %d, want 1", got)
}
}
type breakableStreamDevice struct {
net.Conn
breaks atomic.Int32
}
func (d *breakableStreamDevice) SendBreak() error {
d.breaks.Add(1)
return nil
}
func startStreamSocketServer(t *testing.T, manager *StreamLeaseManager) (string, func()) {
t.Helper()
socketPath := filepath.Join(t.TempDir(), "serial-stream.sock")
listener, err := net.Listen("unix", socketPath)
if err != nil {
t.Skipf("unix sockets unavailable: %v", err)
}
server := StreamSocketServer{Manager: manager}
done := make(chan error, 1)
go func() { done <- server.Serve(listener) }()
return socketPath, func() {
_ = listener.Close()
select {
case err := <-done:
if err != nil {
t.Errorf("stream server stop: %v", err)
}
case <-time.After(time.Second):
t.Error("stream server did not stop")
}
}
}