271 lines
5.4 KiB
Go
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")
|
|
}
|
|
}
|