281 lines
6.4 KiB
Go
281 lines
6.4 KiB
Go
/*
|
|
* ============================================================================
|
|
* Projekt.....: rs2322tcp
|
|
* Datei.......: internal/client/runtime_integration_test.go
|
|
* Copyright (C) 2026 Dieter Lang
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* Beschreibung:
|
|
* Integrationstest für die clientseitige Runtime mit einem echten
|
|
* rs2322tcp-Server und einem virtuellen seriellen Client-Port.
|
|
*
|
|
* Der Test verwendet die vorhandene virtuelle serielle Test-Schnittstelle
|
|
* aus integration_test.go und prüft den vollständigen Datenweg:
|
|
*
|
|
* /dev/ttyUSB100
|
|
* ↕
|
|
* Bridge
|
|
* ↕
|
|
* TCP Data Connection
|
|
* ↕
|
|
* rs2322tcp Server
|
|
* ↕
|
|
* serielle PTY
|
|
*
|
|
* Beide Übertragungsrichtungen werden geprüft.
|
|
* ============================================================================
|
|
*/
|
|
package client_test
|
|
|
|
import (
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.lang-dieter.de/rs2322tcp/internal/client"
|
|
"git.lang-dieter.de/rs2322tcp/internal/config"
|
|
"git.lang-dieter.de/rs2322tcp/internal/server"
|
|
)
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Tests
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
func TestClientRuntimeSerialIntegration(t *testing.T) {
|
|
serialA, serialB, cleanup := startVirtualSerialPair(t)
|
|
defer cleanup()
|
|
|
|
const virtualPortPath = "/dev/ttyUSB100"
|
|
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
t.Fatalf("get user home directory: %v", err)
|
|
}
|
|
|
|
internalPortPath := filepath.Join(
|
|
home,
|
|
".rs2322tcp",
|
|
"virtual",
|
|
"ttyUSB100",
|
|
)
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Server
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
controlProbe, err := net.Listen("tcp", "127.0.0.1:0")
|
|
if err != nil {
|
|
t.Fatalf("find free control port: %v", err)
|
|
}
|
|
|
|
controlPort := controlProbe.Addr().(*net.TCPAddr).Port
|
|
|
|
if err := controlProbe.Close(); err != nil {
|
|
t.Fatalf("close control probe: %v", err)
|
|
}
|
|
|
|
serverConfig := &config.ServerConfig{
|
|
Listen: config.ListenConfig{
|
|
Address: "127.0.0.1",
|
|
Port: controlPort,
|
|
},
|
|
HardwareErrorResponse: testHardwareErrorResponse,
|
|
Devices: []config.DeviceConfig{
|
|
{
|
|
ID: "radio",
|
|
Name: "Funkgerät",
|
|
SerialPort: serialA,
|
|
BaudRate: 9600,
|
|
DataBits: 8,
|
|
Parity: "none",
|
|
StopBits: 1,
|
|
},
|
|
},
|
|
}
|
|
|
|
srv, err := server.NewControlServer(serverConfig)
|
|
if err != nil {
|
|
t.Fatalf("NewControlServer() failed: %v", err)
|
|
}
|
|
|
|
if err := srv.Listen(); err != nil {
|
|
t.Fatalf("server Listen() failed: %v", err)
|
|
}
|
|
defer srv.Close()
|
|
|
|
go func() {
|
|
_ = srv.Serve()
|
|
}()
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Client and Runtime
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
c, err := client.New(srv.Addr().String())
|
|
if err != nil {
|
|
t.Fatalf("client.New() failed: %v", err)
|
|
}
|
|
defer c.Close()
|
|
|
|
manager := client.NewVirtualPortManager(
|
|
config.VirtualPortRangeConfig{
|
|
First: 100,
|
|
Last: 199,
|
|
},
|
|
)
|
|
|
|
runtime, err := client.NewRuntime(c, manager)
|
|
if err != nil {
|
|
t.Fatalf("NewRuntime() failed: %v", err)
|
|
}
|
|
defer runtime.Close()
|
|
|
|
clientConfig := config.ClientConfig{
|
|
Server: config.ServerConnectionConfig{
|
|
Address: "127.0.0.1",
|
|
Port: controlPort,
|
|
},
|
|
VirtualPorts: []config.VirtualPortConfig{
|
|
{
|
|
Port: virtualPortPath,
|
|
RemoteDevice: "radio",
|
|
},
|
|
},
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Start Runtime
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
runtimeDone := make(chan error, 1)
|
|
|
|
go func() {
|
|
runtimeDone <- runtime.Run(clientConfig)
|
|
}()
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Open client-side virtual serial endpoint
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
var virtualSerial *os.File
|
|
|
|
deadline := time.Now().Add(2 * time.Second)
|
|
|
|
for {
|
|
virtualSerial, err = os.OpenFile(
|
|
internalPortPath,
|
|
os.O_RDWR,
|
|
0,
|
|
)
|
|
if err == nil {
|
|
break
|
|
}
|
|
|
|
if time.Now().After(deadline) {
|
|
t.Fatalf(
|
|
"timeout opening virtual serial port %s: %v",
|
|
internalPortPath,
|
|
err,
|
|
)
|
|
}
|
|
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
|
|
defer virtualSerial.Close()
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Client virtual serial -> Bridge -> Server -> serial
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
clientMessage := []byte("hello from runtime")
|
|
|
|
if _, err := virtualSerial.Write(clientMessage); err != nil {
|
|
t.Fatalf(
|
|
"write to virtual serial port failed: %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
serialReceived := make([]byte, len(clientMessage))
|
|
|
|
serialPeer, err := os.OpenFile(
|
|
serialB,
|
|
os.O_RDWR,
|
|
0,
|
|
)
|
|
if err != nil {
|
|
t.Fatalf(
|
|
"open server serial peer: %v",
|
|
err,
|
|
)
|
|
}
|
|
defer serialPeer.Close()
|
|
|
|
readExactWithTimeout(
|
|
t,
|
|
serialPeer,
|
|
serialReceived,
|
|
2*time.Second,
|
|
)
|
|
|
|
if string(serialReceived) != string(clientMessage) {
|
|
t.Fatalf(
|
|
"serial received %q, want %q",
|
|
string(serialReceived),
|
|
string(clientMessage),
|
|
)
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////
|
|
// Server serial -> Bridge -> Client virtual serial
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
serverMessage := []byte("hello from server")
|
|
|
|
if _, err := serialPeer.Write(serverMessage); err != nil {
|
|
t.Fatalf(
|
|
"write to server serial peer failed: %v",
|
|
err,
|
|
)
|
|
}
|
|
|
|
clientReceived := make([]byte, len(serverMessage))
|
|
|
|
readExactWithTimeout(
|
|
t,
|
|
virtualSerial,
|
|
clientReceived,
|
|
2*time.Second,
|
|
)
|
|
|
|
if string(clientReceived) != string(serverMessage) {
|
|
t.Fatalf(
|
|
"virtual serial received %q, want %q",
|
|
string(clientReceived),
|
|
string(serverMessage),
|
|
)
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Terminate Runtime
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
if err := virtualSerial.Close(); err != nil {
|
|
t.Fatalf("close virtual serial port: %v", err)
|
|
}
|
|
|
|
if err := runtime.Close(); err != nil {
|
|
t.Fatalf("close runtime: %v", err)
|
|
}
|
|
|
|
select {
|
|
case <-runtimeDone:
|
|
// Runtime termination is expected after Runtime.Close().
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatal("runtime did not terminate after Runtime.Close()")
|
|
}
|
|
}
|