rs2322tcp/internal/client/bridge_test.go
2026-08-10 17:59:35 +02:00

271 lines
5.4 KiB
Go

/*
* ============================================================================
* Projekt.....: rs2322tcp
* Datei.......: internal/client/bridge_test.go
* Copyright (C) 2026 Dieter Lang
*
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Beschreibung:
* Tests für die bidirektionale Daten-Bridge zwischen VirtualSerial und
* TCP-Verbindung.
*
* Die Tests verwenden net.Pipe und eine kleine In-Memory-Implementierung
* von VirtualSerial. Dadurch wird ausschließlich die Daten-Bridge getestet,
* ohne echte TCP-Ports oder eine reale PTY-Schnittstelle zu benötigen.
* ============================================================================
*/
package client
import (
"bytes"
"io"
"net"
"sync"
"testing"
"time"
)
///////////////////////////////////////////////////////////////////////////////
// Test VirtualSerial
///////////////////////////////////////////////////////////////////////////////
type testVirtualSerial struct {
mu sync.Mutex
reader *bytes.Reader
writes [][]byte
closed bool
}
func newTestVirtualSerial(data []byte) *testVirtualSerial {
return &testVirtualSerial{
reader: bytes.NewReader(data),
}
}
func (s *testVirtualSerial) Path() string {
return "test-serial"
}
func (s *testVirtualSerial) Read(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return 0, io.EOF
}
return s.reader.Read(p)
}
func (s *testVirtualSerial) Write(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return 0, io.ErrClosedPipe
}
cp := append([]byte(nil), p...)
s.writes = append(s.writes, cp)
return len(p), nil
}
func (s *testVirtualSerial) Written() []byte {
s.mu.Lock()
defer s.mu.Unlock()
var result []byte
for _, p := range s.writes {
result = append(result, p...)
}
return result
}
func (s *testVirtualSerial) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
s.closed = true
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Blocking test VirtualSerial
///////////////////////////////////////////////////////////////////////////////
type blockingVirtualSerial struct {
mu sync.Mutex
writes [][]byte
readCh chan struct{}
closed bool
}
func newBlockingVirtualSerial() *blockingVirtualSerial {
return &blockingVirtualSerial{
readCh: make(chan struct{}),
}
}
func (s *blockingVirtualSerial) Path() string {
return "blocking-test-serial"
}
func (s *blockingVirtualSerial) Read([]byte) (int, error) {
<-s.readCh
return 0, io.EOF
}
func (s *blockingVirtualSerial) Write(p []byte) (int, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.closed {
return 0, io.ErrClosedPipe
}
s.writes = append(s.writes, append([]byte(nil), p...))
return len(p), nil
}
func (s *blockingVirtualSerial) Written() []byte {
s.mu.Lock()
defer s.mu.Unlock()
var result []byte
for _, p := range s.writes {
result = append(result, p...)
}
return result
}
func (s *blockingVirtualSerial) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
if !s.closed {
s.closed = true
close(s.readCh)
}
return nil
}
///////////////////////////////////////////////////////////////////////////////
// Tests
///////////////////////////////////////////////////////////////////////////////
func TestNewBridge(t *testing.T) {
serial := newTestVirtualSerial(nil)
connA, connB := net.Pipe()
defer connA.Close()
defer connB.Close()
if _, err := NewBridge(nil, connA); err == nil {
t.Fatal("expected error for nil VirtualSerial")
}
if _, err := NewBridge(serial, nil); err == nil {
t.Fatal("expected error for nil TCP connection")
}
bridge, err := NewBridge(serial, connA)
if err != nil {
t.Fatalf("NewBridge: %v", err)
}
if bridge.serial != serial {
t.Fatal("bridge serial endpoint mismatch")
}
if bridge.conn != connA {
t.Fatal("bridge TCP endpoint mismatch")
}
}
func TestBridgeSerialToTCP(t *testing.T) {
serial := newTestVirtualSerial([]byte("hello"))
clientConn, bridgeConn := net.Pipe()
bridge, err := NewBridge(serial, bridgeConn)
if err != nil {
t.Fatalf("NewBridge: %v", err)
}
runDone := make(chan error, 1)
go func() {
runDone <- bridge.Run()
}()
got := make([]byte, 5)
if _, err := io.ReadFull(clientConn, got); err != nil {
t.Fatalf("read TCP data: %v", err)
}
if string(got) != "hello" {
t.Fatalf("received %q, want %q", got, "hello")
}
_ = clientConn.Close()
select {
case err := <-runDone:
if err != nil {
t.Fatalf("Bridge.Run: %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Bridge.Run did not terminate")
}
}
func TestBridgeTCPToSerial(t *testing.T) {
serial := newBlockingVirtualSerial()
clientConn, bridgeConn := net.Pipe()
bridge, err := NewBridge(serial, bridgeConn)
if err != nil {
t.Fatalf("NewBridge: %v", err)
}
runDone := make(chan error, 1)
go func() {
runDone <- bridge.Run()
}()
want := []byte{0x46, 0x41, 0x00, 0x10, 0x0D}
if _, err := clientConn.Write(want); err != nil {
t.Fatalf("write TCP data: %v", err)
}
deadline := time.Now().Add(2 * time.Second)
for {
if bytes.Equal(serial.Written(), want) {
break
}
if time.Now().After(deadline) {
t.Fatalf(
"serial received % X, want % X",
serial.Written(),
want,
)
}
time.Sleep(1 * time.Millisecond)
}
_ = clientConn.Close()
select {
case err := <-runDone:
if err != nil {
t.Fatalf("Bridge.Run: %v", err)
}
case <-time.After(2 * time.Second):
t.Fatal("Bridge.Run did not terminate")
}
}