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.
209 lines
5.3 KiB
209 lines
5.3 KiB
|
2 months ago
|
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")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|